/* * Copyright (c) 2014 Samsung Electronics Co., Ltd. * http://www.samsung.com/ * * EXYNOS - CHIP ID support * Author: Pankaj Dubey * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include struct exynos_chipid_info exynos_soc_info; EXPORT_SYMBOL(exynos_soc_info); static const char *soc_ap_id; static const char * __init product_id_to_name(unsigned int product_id) { const char *soc_name; unsigned int soc_id = product_id; switch (soc_id) { case EXYNOS3250_SOC_ID: soc_name = "EXYNOS3250"; break; case EXYNOS4210_SOC_ID: soc_name = "EXYNOS4210"; break; case EXYNOS4212_SOC_ID: soc_name = "EXYNOS4212"; break; case EXYNOS4412_SOC_ID: soc_name = "EXYNOS4412"; break; case EXYNOS4415_SOC_ID: soc_name = "EXYNOS4415"; break; case EXYNOS5250_SOC_ID: soc_name = "EXYNOS5250"; break; case EXYNOS5260_SOC_ID: soc_name = "EXYNOS5260"; break; case EXYNOS5420_SOC_ID: soc_name = "EXYNOS5420"; break; case EXYNOS5440_SOC_ID: soc_name = "EXYNOS5440"; break; case EXYNOS5800_SOC_ID: soc_name = "EXYNOS5800"; break; case EXYNOS7872_SOC_ID: soc_name = "EXYNOS7872"; break; case EXYNOS8890_SOC_ID: soc_name = "EXYNOS8890"; break; case EXYNOS8895_SOC_ID: soc_name = "EXYNOS8895"; break; case EXYNOS9810_SOC_ID: soc_name = "EXYNOS9810"; break; case EXYNOS9610_SOC_ID: soc_name = "EXYNOS9610"; break; default: soc_name = "UNKNOWN"; } return soc_name; } static const struct exynos_chipid_variant drv_data_exynos8890 = { .product_ver = 1, .unique_id_reg = 0x14, .rev_reg = 0x0, .main_rev_bit = 0, .sub_rev_bit = 4, }; static const struct exynos_chipid_variant drv_data_exynos8895 = { .product_ver = 1, .unique_id_reg = 0x04, .rev_reg = 0x10, .main_rev_bit = 20, .sub_rev_bit = 16, }; static const struct exynos_chipid_variant drv_data_exynos7872 = { .product_ver = 2, .unique_id_reg = 0x04, .rev_reg = 0x10, .main_rev_bit = 20, .sub_rev_bit = 16, }; static const struct exynos_chipid_variant drv_data_exynos9810 = { .product_ver = 1, .unique_id_reg = 0x04, .rev_reg = 0x10, .main_rev_bit = 20, .sub_rev_bit = 16, }; static const struct exynos_chipid_variant drv_data_exynos9 = { .product_ver = 1, .unique_id_reg = 0x04, .rev_reg = 0x10, .main_rev_bit = 20, .sub_rev_bit = 16, }; static const struct of_device_id of_exynos_chipid_ids[] = { { .compatible = "samsung,exynos8890-chipid", .data = &drv_data_exynos8890, }, { .compatible = "samsung,exynos8895-chipid", .data = &drv_data_exynos8895, }, { .compatible = "samsung,exynos7872-chipid", .data = &drv_data_exynos8895, }, { .compatible = "samsung,exynos9810-chipid", .data = &drv_data_exynos9810, }, { .compatible = "samsung,exynos9-chipid", .data = &drv_data_exynos9, }, {}, }; static void __init exynos_chipid_get_chipid_info(void) { const struct exynos_chipid_variant *data = exynos_soc_info.drv_data; u64 val; val = __raw_readl(exynos_soc_info.reg); switch (data->product_ver) { case 2: exynos_soc_info.product_id = val & EXYNOS_SOC_MASK_V2; break; case 1: default: exynos_soc_info.product_id = val & EXYNOS_SOC_MASK; break; } val = __raw_readl(exynos_soc_info.reg + data->rev_reg); exynos_soc_info.main_rev = (val >> data->main_rev_bit) & EXYNOS_REV_MASK; exynos_soc_info.sub_rev = (val >> data->sub_rev_bit) & EXYNOS_REV_MASK; exynos_soc_info.revision = (exynos_soc_info.main_rev << 4) | exynos_soc_info.sub_rev; val = __raw_readl(exynos_soc_info.reg + data->unique_id_reg); val |= (u64)__raw_readl(exynos_soc_info.reg + data->unique_id_reg + 4) << 32UL; exynos_soc_info.unique_id = val; exynos_soc_info.lot_id = val & EXYNOS_LOTID_MASK; } /** * exynos_chipid_early_init: Early chipid initialization * @dev: pointer to chipid device */ void __init exynos_chipid_early_init(void) { struct device_node *np; const struct of_device_id *match; if (exynos_soc_info.reg) return; np = of_find_matching_node_and_match(NULL, of_exynos_chipid_ids, &match); if (!np || !match) panic("%s, failed to find chipid node or match\n", __func__); exynos_soc_info.drv_data = (struct exynos_chipid_variant *)match->data; exynos_soc_info.reg = of_iomap(np, 0); if (!exynos_soc_info.reg) panic("%s: failed to map registers\n", __func__); exynos_chipid_get_chipid_info(); } static int __init exynos_chipid_probe(struct platform_device *pdev) { struct soc_device_attribute *soc_dev_attr; struct soc_device *soc_dev; struct device_node *root; int ret; soc_dev_attr = kzalloc(sizeof(*soc_dev_attr), GFP_KERNEL); if (!soc_dev_attr) return -ENODEV; soc_dev_attr->family = "Samsung Exynos"; root = of_find_node_by_path("/"); ret = of_property_read_string(root, "model", &soc_dev_attr->machine); of_node_put(root); if (ret) goto free_soc; soc_dev_attr->revision = kasprintf(GFP_KERNEL, "%d", exynos_soc_info.revision); if (!soc_dev_attr->revision) goto free_soc; soc_dev_attr->soc_id = product_id_to_name(exynos_soc_info.product_id); soc_ap_id = product_id_to_name(exynos_soc_info.product_id); soc_dev = soc_device_register(soc_dev_attr); if (IS_ERR(soc_dev)) goto free_rev; soc_device_to_device(soc_dev); dev_info(&pdev->dev, "Exynos: CPU[%s] CPU_REV[0x%x] Detected\n", product_id_to_name(exynos_soc_info.product_id), exynos_soc_info.revision); return 0; free_rev: kfree(soc_dev_attr->revision); free_soc: kfree(soc_dev_attr); return -EINVAL; } static struct platform_driver exynos_chipid_driver __refdata = { .driver = { .name = "exynos-chipid", .of_match_table = of_exynos_chipid_ids, }, .probe = exynos_chipid_probe, }; static int __init exynos_chipid_init(void) { exynos_chipid_early_init(); return platform_driver_register(&exynos_chipid_driver); } core_initcall(exynos_chipid_init); /* * sysfs implementation for exynos-snapshot * you can access the sysfs of exynos-snapshot to /sys/devices/system/chip-id * path. */ static struct bus_type chipid_subsys = { .name = "chip-id", .dev_name = "chip-id", }; static ssize_t chipid_product_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 10, "%08X\n", exynos_soc_info.product_id); } static ssize_t chipid_ap_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { if (exynos_soc_info.revision == 0) return snprintf(buf, 30, "%s EVT0\n", soc_ap_id); else return snprintf(buf, 30, "%s EVT%1X.%1X\n", soc_ap_id, exynos_soc_info.main_rev, exynos_soc_info.sub_rev); } static ssize_t chipid_unique_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 20, "%010LX\n", exynos_soc_info.unique_id); } static ssize_t chipid_lot_id_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 14, "%08X\n", exynos_soc_info.lot_id); } static ssize_t chipid_revision_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 14, "%08X\n", exynos_soc_info.revision); } static ssize_t chipid_evt_ver_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { if (exynos_soc_info.revision == 0) return snprintf(buf, 14, "EVT0\n"); else return snprintf(buf, 14, "EVT%1X.%1X\n", exynos_soc_info.main_rev, exynos_soc_info.sub_rev); } static struct kobj_attribute chipid_product_id_attr = __ATTR(product_id, 0644, chipid_product_id_show, NULL); static struct kobj_attribute chipid_ap_id_attr = __ATTR(ap_id, 0644, chipid_ap_id_show, NULL); static struct kobj_attribute chipid_unique_id_attr = __ATTR(unique_id, 0644, chipid_unique_id_show, NULL); static struct kobj_attribute chipid_lot_id_attr = __ATTR(lot_id, 0644, chipid_lot_id_show, NULL); static struct kobj_attribute chipid_revision_attr = __ATTR(revision, 0644, chipid_revision_show, NULL); static struct kobj_attribute chipid_evt_ver_attr = __ATTR(evt_ver, 0644, chipid_evt_ver_show, NULL); static struct attribute *chipid_sysfs_attrs[] = { &chipid_product_id_attr.attr, &chipid_ap_id_attr.attr, &chipid_unique_id_attr.attr, &chipid_lot_id_attr.attr, &chipid_revision_attr.attr, &chipid_evt_ver_attr.attr, NULL, }; static struct attribute_group chipid_sysfs_group = { .attrs = chipid_sysfs_attrs, }; static const struct attribute_group *chipid_sysfs_groups[] = { &chipid_sysfs_group, NULL, }; static ssize_t svc_ap_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return snprintf(buf, 20, "%010llX\n", (exynos_soc_info.unique_id)); } static struct kobj_attribute svc_ap_attr = __ATTR(SVC_AP, 0644, svc_ap_show, NULL); extern struct kset *devices_kset; void sysfs_create_svc_ap(void) { struct kernfs_node *svc_sd; struct kobject *data; struct kobject *ap; /* To find svc kobject */ svc_sd = sysfs_get_dirent(devices_kset->kobj.sd, "svc"); if (IS_ERR_OR_NULL(svc_sd)) { /* try to create svc kobject */ data = kobject_create_and_add("svc", &devices_kset->kobj); if (IS_ERR_OR_NULL(data)) pr_info("Existing path sys/devices/svc : 0x%pK\n", data); else pr_info("Created sys/devices/svc svc : 0x%pK\n", data); } else { data = (struct kobject *)svc_sd->priv; pr_info("Found svc_sd : 0x%pK svc : 0x%pK\n", svc_sd, data); } ap = kobject_create_and_add("AP", data); if (IS_ERR_OR_NULL(ap)) pr_info("Failed to create sys/devices/svc/AP : 0x%pK\n", ap); else pr_info("Success to create sys/devices/svc/AP : 0x%pK\n", ap); if (sysfs_create_file(ap, &svc_ap_attr.attr) < 0) { pr_err("failed to create sys/devices/svc/AP/SVC_AP, %s\n", svc_ap_attr.attr.name); } } static int __init chipid_sysfs_init(void) { int ret = 0; ret = subsys_system_register(&chipid_subsys, chipid_sysfs_groups); if (ret) pr_err("fail to register exynos-snapshop subsys\n"); sysfs_create_svc_ap(); return ret; } late_initcall(chipid_sysfs_init);