blob: 9d59b6f8e1c96a05bc3b9e79098a57f3237d025d [file] [log] [blame]
/*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* EXYNOS - CHIP ID support
* Author: Pankaj Dubey <pankaj.dubey@samsung.com>
*
* 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 <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include <linux/soc/samsung/exynos-soc.h>
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 EXYNOS9820_SOC_ID:
soc_name = "EXYNOS9820";
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 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,
},
{},
};
static char lot_id[6];
static u32 chipid_reverse_value(u32 val, u32 bitcnt)
{
u32 temp, ret = 0;
u32 i;
for (i = 0; i < bitcnt; i++) {
temp = (val >> i) & 0x1;
ret += temp << ((bitcnt - 1) - i);
}
return ret;
}
static void chipid_dec_to_36(u32 in, char *p)
{
const struct exynos_chipid_variant *data = exynos_soc_info.drv_data;
u32 mod;
u32 i;
u32 val;
for (i = 4; i >= 1; i--) {
mod = in % 36;
in /= 36;
p[i] = (mod < 10) ? (mod + '0') : (mod - 10 + 'A');
}
val = __raw_readl(exynos_soc_info.reg + data->unique_id_reg + 0x4);
val = (val >> 10) & 0x3;
switch (val) {
case 0:
p[0] = 'N';
break;
case 1:
p[0] = 'S';
break;
case 2:
p[0] = 'A';
break;
case 3:
default:
break;
}
p[5] = 0;
}
static void __init exynos_chipid_get_chipid_info(void)
{
const struct exynos_chipid_variant *data = exynos_soc_info.drv_data;
u64 val;
u32 temp;
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;
temp = __raw_readl(exynos_soc_info.reg + data->unique_id_reg);
temp = chipid_reverse_value(temp, 32);
temp = (temp >> 11) & EXYNOS_LOTID_MASK;
chipid_dec_to_36(temp, lot_id);
exynos_soc_info.lot_id2 = lot_id;
}
#if defined(CONFIG_SEC_FACTORY)
#define POWER_BASE 0x15860000
#define DREX_CAL6 0x09B8
struct exynos_ddr_info {
unsigned int lot_id;
unsigned int rev;
};
struct exynos_ddr_info ddr_info;
static void __init exynos_chipid_get_dram_info(void)
{
unsigned int reg;
void __iomem *rev_addr;
rev_addr = ioremap(POWER_BASE, SZ_32);
reg = readl(rev_addr + DREX_CAL6);
ddr_info.lot_id = (reg & 0x3f)*100 + ((reg >> 6) & 0x3f)* 10 + ((reg >> 12) & 0xf);
ddr_info.rev = (reg >> 16) & 0xffff;
iounmap(rev_addr);
return;
}
#else
static void __init exynos_chipid_get_dram_info(void)
{
return;
}
#endif
/**
* 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();
exynos_chipid_get_dram_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_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_lot_id2_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, 14, "%s\n", exynos_soc_info.lot_id2);
}
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 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);
}
#ifdef CONFIG_SEC_FACTORY
static ssize_t chipid_ddr_lot_id_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, 14, "%d\n", ddr_info.lot_id);
}
static ssize_t chipid_ddr_rev_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return snprintf(buf, 14, "%d\n", ddr_info.rev);
}
#endif
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_lot_id2_attr =
__ATTR(lot_id2, 0644, chipid_lot_id2_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);
#ifdef CONFIG_SEC_FACTORY
static struct kobj_attribute chipid_ddr_lot_id_attr =
__ATTR(ddr_lot_id, 0644, chipid_ddr_lot_id_show, NULL);
static struct kobj_attribute chipid_ddr_rev_attr =
__ATTR(ddr_rev, 0644, chipid_ddr_rev_show, NULL);
#endif
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_lot_id2_attr.attr,
&chipid_revision_attr.attr,
&chipid_evt_ver_attr.attr,
#if defined(CONFIG_SEC_FACTORY)
&chipid_ddr_lot_id_attr.attr,
&chipid_ddr_rev_attr.attr,
#endif
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);