162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2019 Samsung Electronics Co., Ltd.
462306a36Sopenharmony_ci *	      http://www.samsung.com/
562306a36Sopenharmony_ci * Copyright (c) 2020 Krzysztof Kozlowski <krzk@kernel.org>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Exynos - CHIP ID support
862306a36Sopenharmony_ci * Author: Pankaj Dubey <pankaj.dubey@samsung.com>
962306a36Sopenharmony_ci * Author: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>
1062306a36Sopenharmony_ci * Author: Krzysztof Kozlowski <krzk@kernel.org>
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Samsung Exynos SoC Adaptive Supply Voltage and Chip ID support
1362306a36Sopenharmony_ci */
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include <linux/device.h>
1662306a36Sopenharmony_ci#include <linux/errno.h>
1762306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <linux/of.h>
2062306a36Sopenharmony_ci#include <linux/platform_device.h>
2162306a36Sopenharmony_ci#include <linux/regmap.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/soc/samsung/exynos-chipid.h>
2462306a36Sopenharmony_ci#include <linux/sys_soc.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "exynos-asv.h"
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistruct exynos_chipid_variant {
2962306a36Sopenharmony_ci	unsigned int rev_reg;		/* revision register offset */
3062306a36Sopenharmony_ci	unsigned int main_rev_shift;	/* main revision offset in rev_reg */
3162306a36Sopenharmony_ci	unsigned int sub_rev_shift;	/* sub revision offset in rev_reg */
3262306a36Sopenharmony_ci};
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct exynos_chipid_info {
3562306a36Sopenharmony_ci	u32 product_id;
3662306a36Sopenharmony_ci	u32 revision;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic const struct exynos_soc_id {
4062306a36Sopenharmony_ci	const char *name;
4162306a36Sopenharmony_ci	unsigned int id;
4262306a36Sopenharmony_ci} soc_ids[] = {
4362306a36Sopenharmony_ci	/* List ordered by SoC name */
4462306a36Sopenharmony_ci	/* Compatible with: samsung,exynos4210-chipid */
4562306a36Sopenharmony_ci	{ "EXYNOS3250", 0xE3472000 },
4662306a36Sopenharmony_ci	{ "EXYNOS4210", 0x43200000 },	/* EVT0 revision */
4762306a36Sopenharmony_ci	{ "EXYNOS4210", 0x43210000 },
4862306a36Sopenharmony_ci	{ "EXYNOS4212", 0x43220000 },
4962306a36Sopenharmony_ci	{ "EXYNOS4412", 0xE4412000 },
5062306a36Sopenharmony_ci	{ "EXYNOS5250", 0x43520000 },
5162306a36Sopenharmony_ci	{ "EXYNOS5260", 0xE5260000 },
5262306a36Sopenharmony_ci	{ "EXYNOS5410", 0xE5410000 },
5362306a36Sopenharmony_ci	{ "EXYNOS5420", 0xE5420000 },
5462306a36Sopenharmony_ci	{ "EXYNOS5433", 0xE5433000 },
5562306a36Sopenharmony_ci	{ "EXYNOS5440", 0xE5440000 },
5662306a36Sopenharmony_ci	{ "EXYNOS5800", 0xE5422000 },
5762306a36Sopenharmony_ci	{ "EXYNOS7420", 0xE7420000 },
5862306a36Sopenharmony_ci	/* Compatible with: samsung,exynos850-chipid */
5962306a36Sopenharmony_ci	{ "EXYNOS7885", 0xE7885000 },
6062306a36Sopenharmony_ci	{ "EXYNOS850", 0xE3830000 },
6162306a36Sopenharmony_ci	{ "EXYNOSAUTOV9", 0xAAA80000 },
6262306a36Sopenharmony_ci};
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic const char *product_id_to_soc_id(unsigned int product_id)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	int i;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(soc_ids); i++)
6962306a36Sopenharmony_ci		if (product_id == soc_ids[i].id)
7062306a36Sopenharmony_ci			return soc_ids[i].name;
7162306a36Sopenharmony_ci	return NULL;
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int exynos_chipid_get_chipid_info(struct regmap *regmap,
7562306a36Sopenharmony_ci		const struct exynos_chipid_variant *data,
7662306a36Sopenharmony_ci		struct exynos_chipid_info *soc_info)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	int ret;
7962306a36Sopenharmony_ci	unsigned int val, main_rev, sub_rev;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	ret = regmap_read(regmap, EXYNOS_CHIPID_REG_PRO_ID, &val);
8262306a36Sopenharmony_ci	if (ret < 0)
8362306a36Sopenharmony_ci		return ret;
8462306a36Sopenharmony_ci	soc_info->product_id = val & EXYNOS_MASK;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (data->rev_reg != EXYNOS_CHIPID_REG_PRO_ID) {
8762306a36Sopenharmony_ci		ret = regmap_read(regmap, data->rev_reg, &val);
8862306a36Sopenharmony_ci		if (ret < 0)
8962306a36Sopenharmony_ci			return ret;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci	main_rev = (val >> data->main_rev_shift) & EXYNOS_REV_PART_MASK;
9262306a36Sopenharmony_ci	sub_rev = (val >> data->sub_rev_shift) & EXYNOS_REV_PART_MASK;
9362306a36Sopenharmony_ci	soc_info->revision = (main_rev << EXYNOS_REV_PART_SHIFT) | sub_rev;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return 0;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_cistatic int exynos_chipid_probe(struct platform_device *pdev)
9962306a36Sopenharmony_ci{
10062306a36Sopenharmony_ci	const struct exynos_chipid_variant *drv_data;
10162306a36Sopenharmony_ci	struct exynos_chipid_info soc_info;
10262306a36Sopenharmony_ci	struct soc_device_attribute *soc_dev_attr;
10362306a36Sopenharmony_ci	struct soc_device *soc_dev;
10462306a36Sopenharmony_ci	struct device_node *root;
10562306a36Sopenharmony_ci	struct regmap *regmap;
10662306a36Sopenharmony_ci	int ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	drv_data = of_device_get_match_data(&pdev->dev);
10962306a36Sopenharmony_ci	if (!drv_data)
11062306a36Sopenharmony_ci		return -EINVAL;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	regmap = device_node_to_regmap(pdev->dev.of_node);
11362306a36Sopenharmony_ci	if (IS_ERR(regmap))
11462306a36Sopenharmony_ci		return PTR_ERR(regmap);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	ret = exynos_chipid_get_chipid_info(regmap, drv_data, &soc_info);
11762306a36Sopenharmony_ci	if (ret < 0)
11862306a36Sopenharmony_ci		return ret;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	soc_dev_attr = devm_kzalloc(&pdev->dev, sizeof(*soc_dev_attr),
12162306a36Sopenharmony_ci				    GFP_KERNEL);
12262306a36Sopenharmony_ci	if (!soc_dev_attr)
12362306a36Sopenharmony_ci		return -ENOMEM;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	soc_dev_attr->family = "Samsung Exynos";
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	root = of_find_node_by_path("/");
12862306a36Sopenharmony_ci	of_property_read_string(root, "model", &soc_dev_attr->machine);
12962306a36Sopenharmony_ci	of_node_put(root);
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	soc_dev_attr->revision = devm_kasprintf(&pdev->dev, GFP_KERNEL,
13262306a36Sopenharmony_ci						"%x", soc_info.revision);
13362306a36Sopenharmony_ci	soc_dev_attr->soc_id = product_id_to_soc_id(soc_info.product_id);
13462306a36Sopenharmony_ci	if (!soc_dev_attr->soc_id) {
13562306a36Sopenharmony_ci		pr_err("Unknown SoC\n");
13662306a36Sopenharmony_ci		return -ENODEV;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	/* please note that the actual registration will be deferred */
14062306a36Sopenharmony_ci	soc_dev = soc_device_register(soc_dev_attr);
14162306a36Sopenharmony_ci	if (IS_ERR(soc_dev))
14262306a36Sopenharmony_ci		return PTR_ERR(soc_dev);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	ret = exynos_asv_init(&pdev->dev, regmap);
14562306a36Sopenharmony_ci	if (ret)
14662306a36Sopenharmony_ci		goto err;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	platform_set_drvdata(pdev, soc_dev);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	dev_info(&pdev->dev, "Exynos: CPU[%s] PRO_ID[0x%x] REV[0x%x] Detected\n",
15162306a36Sopenharmony_ci		 soc_dev_attr->soc_id, soc_info.product_id, soc_info.revision);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	return 0;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cierr:
15662306a36Sopenharmony_ci	soc_device_unregister(soc_dev);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	return ret;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cistatic int exynos_chipid_remove(struct platform_device *pdev)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	struct soc_device *soc_dev = platform_get_drvdata(pdev);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	soc_device_unregister(soc_dev);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return 0;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic const struct exynos_chipid_variant exynos4210_chipid_drv_data = {
17162306a36Sopenharmony_ci	.rev_reg	= 0x0,
17262306a36Sopenharmony_ci	.main_rev_shift	= 4,
17362306a36Sopenharmony_ci	.sub_rev_shift	= 0,
17462306a36Sopenharmony_ci};
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_cistatic const struct exynos_chipid_variant exynos850_chipid_drv_data = {
17762306a36Sopenharmony_ci	.rev_reg	= 0x10,
17862306a36Sopenharmony_ci	.main_rev_shift	= 20,
17962306a36Sopenharmony_ci	.sub_rev_shift	= 16,
18062306a36Sopenharmony_ci};
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_cistatic const struct of_device_id exynos_chipid_of_device_ids[] = {
18362306a36Sopenharmony_ci	{
18462306a36Sopenharmony_ci		.compatible	= "samsung,exynos4210-chipid",
18562306a36Sopenharmony_ci		.data		= &exynos4210_chipid_drv_data,
18662306a36Sopenharmony_ci	}, {
18762306a36Sopenharmony_ci		.compatible	= "samsung,exynos850-chipid",
18862306a36Sopenharmony_ci		.data		= &exynos850_chipid_drv_data,
18962306a36Sopenharmony_ci	},
19062306a36Sopenharmony_ci	{ }
19162306a36Sopenharmony_ci};
19262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, exynos_chipid_of_device_ids);
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_cistatic struct platform_driver exynos_chipid_driver = {
19562306a36Sopenharmony_ci	.driver = {
19662306a36Sopenharmony_ci		.name = "exynos-chipid",
19762306a36Sopenharmony_ci		.of_match_table = exynos_chipid_of_device_ids,
19862306a36Sopenharmony_ci	},
19962306a36Sopenharmony_ci	.probe	= exynos_chipid_probe,
20062306a36Sopenharmony_ci	.remove	= exynos_chipid_remove,
20162306a36Sopenharmony_ci};
20262306a36Sopenharmony_cimodule_platform_driver(exynos_chipid_driver);
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ciMODULE_DESCRIPTION("Samsung Exynos ChipID controller and ASV driver");
20562306a36Sopenharmony_ciMODULE_AUTHOR("Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com>");
20662306a36Sopenharmony_ciMODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
20762306a36Sopenharmony_ciMODULE_AUTHOR("Pankaj Dubey <pankaj.dubey@samsung.com>");
20862306a36Sopenharmony_ciMODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>");
20962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
210