18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Allwinner sunXi SoCs Security ID support.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl>
68c2ecf20Sopenharmony_ci * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include <linux/device.h>
108c2ecf20Sopenharmony_ci#include <linux/io.h>
118c2ecf20Sopenharmony_ci#include <linux/iopoll.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/nvmem-provider.h>
148c2ecf20Sopenharmony_ci#include <linux/of.h>
158c2ecf20Sopenharmony_ci#include <linux/of_device.h>
168c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <linux/random.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/* Registers and special values for doing register-based SID readout on H3 */
218c2ecf20Sopenharmony_ci#define SUN8I_SID_PRCTL		0x40
228c2ecf20Sopenharmony_ci#define SUN8I_SID_RDKEY		0x60
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#define SUN8I_SID_OFFSET_MASK	0x1FF
258c2ecf20Sopenharmony_ci#define SUN8I_SID_OFFSET_SHIFT	16
268c2ecf20Sopenharmony_ci#define SUN8I_SID_OP_LOCK	(0xAC << 8)
278c2ecf20Sopenharmony_ci#define SUN8I_SID_READ		BIT(1)
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistruct sunxi_sid_cfg {
308c2ecf20Sopenharmony_ci	u32	value_offset;
318c2ecf20Sopenharmony_ci	u32	size;
328c2ecf20Sopenharmony_ci	bool	need_register_readout;
338c2ecf20Sopenharmony_ci};
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistruct sunxi_sid {
368c2ecf20Sopenharmony_ci	void __iomem		*base;
378c2ecf20Sopenharmony_ci	u32			value_offset;
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int sunxi_sid_read(void *context, unsigned int offset,
418c2ecf20Sopenharmony_ci			  void *val, size_t bytes)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct sunxi_sid *sid = context;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	memcpy_fromio(val, sid->base + sid->value_offset + offset, bytes);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	return 0;
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic int sun8i_sid_register_readout(const struct sunxi_sid *sid,
518c2ecf20Sopenharmony_ci				      const unsigned int offset,
528c2ecf20Sopenharmony_ci				      u32 *out)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	u32 reg_val;
558c2ecf20Sopenharmony_ci	int ret;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* Set word, lock access, and set read command */
588c2ecf20Sopenharmony_ci	reg_val = (offset & SUN8I_SID_OFFSET_MASK)
598c2ecf20Sopenharmony_ci		  << SUN8I_SID_OFFSET_SHIFT;
608c2ecf20Sopenharmony_ci	reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ;
618c2ecf20Sopenharmony_ci	writel(reg_val, sid->base + SUN8I_SID_PRCTL);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val,
648c2ecf20Sopenharmony_ci				 !(reg_val & SUN8I_SID_READ), 100, 250000);
658c2ecf20Sopenharmony_ci	if (ret)
668c2ecf20Sopenharmony_ci		return ret;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (out)
698c2ecf20Sopenharmony_ci		*out = readl(sid->base + SUN8I_SID_RDKEY);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	writel(0, sid->base + SUN8I_SID_PRCTL);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	return 0;
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci/*
778c2ecf20Sopenharmony_ci * On Allwinner H3, the value on the 0x200 offset of the SID controller seems
788c2ecf20Sopenharmony_ci * to be not reliable at all.
798c2ecf20Sopenharmony_ci * Read by the registers instead.
808c2ecf20Sopenharmony_ci */
818c2ecf20Sopenharmony_cistatic int sun8i_sid_read_by_reg(void *context, unsigned int offset,
828c2ecf20Sopenharmony_ci				 void *val, size_t bytes)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct sunxi_sid *sid = context;
858c2ecf20Sopenharmony_ci	u32 word;
868c2ecf20Sopenharmony_ci	int ret;
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ci	/* .stride = 4 so offset is guaranteed to be aligned */
898c2ecf20Sopenharmony_ci	while (bytes >= 4) {
908c2ecf20Sopenharmony_ci		ret = sun8i_sid_register_readout(sid, offset, val);
918c2ecf20Sopenharmony_ci		if (ret)
928c2ecf20Sopenharmony_ci			return ret;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci		val += 4;
958c2ecf20Sopenharmony_ci		offset += 4;
968c2ecf20Sopenharmony_ci		bytes -= 4;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	if (!bytes)
1008c2ecf20Sopenharmony_ci		return 0;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	/* Handle any trailing bytes */
1038c2ecf20Sopenharmony_ci	ret = sun8i_sid_register_readout(sid, offset, &word);
1048c2ecf20Sopenharmony_ci	if (ret)
1058c2ecf20Sopenharmony_ci		return ret;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	memcpy(val, &word, bytes);
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	return 0;
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int sunxi_sid_probe(struct platform_device *pdev)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct device *dev = &pdev->dev;
1158c2ecf20Sopenharmony_ci	struct resource *res;
1168c2ecf20Sopenharmony_ci	struct nvmem_config *nvmem_cfg;
1178c2ecf20Sopenharmony_ci	struct nvmem_device *nvmem;
1188c2ecf20Sopenharmony_ci	struct sunxi_sid *sid;
1198c2ecf20Sopenharmony_ci	int size;
1208c2ecf20Sopenharmony_ci	char *randomness;
1218c2ecf20Sopenharmony_ci	const struct sunxi_sid_cfg *cfg;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL);
1248c2ecf20Sopenharmony_ci	if (!sid)
1258c2ecf20Sopenharmony_ci		return -ENOMEM;
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci	cfg = of_device_get_match_data(dev);
1288c2ecf20Sopenharmony_ci	if (!cfg)
1298c2ecf20Sopenharmony_ci		return -EINVAL;
1308c2ecf20Sopenharmony_ci	sid->value_offset = cfg->value_offset;
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1338c2ecf20Sopenharmony_ci	sid->base = devm_ioremap_resource(dev, res);
1348c2ecf20Sopenharmony_ci	if (IS_ERR(sid->base))
1358c2ecf20Sopenharmony_ci		return PTR_ERR(sid->base);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	size = cfg->size;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL);
1408c2ecf20Sopenharmony_ci	if (!nvmem_cfg)
1418c2ecf20Sopenharmony_ci		return -ENOMEM;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	nvmem_cfg->dev = dev;
1448c2ecf20Sopenharmony_ci	nvmem_cfg->name = "sunxi-sid";
1458c2ecf20Sopenharmony_ci	nvmem_cfg->read_only = true;
1468c2ecf20Sopenharmony_ci	nvmem_cfg->size = cfg->size;
1478c2ecf20Sopenharmony_ci	nvmem_cfg->word_size = 1;
1488c2ecf20Sopenharmony_ci	nvmem_cfg->stride = 4;
1498c2ecf20Sopenharmony_ci	nvmem_cfg->priv = sid;
1508c2ecf20Sopenharmony_ci	if (cfg->need_register_readout)
1518c2ecf20Sopenharmony_ci		nvmem_cfg->reg_read = sun8i_sid_read_by_reg;
1528c2ecf20Sopenharmony_ci	else
1538c2ecf20Sopenharmony_ci		nvmem_cfg->reg_read = sunxi_sid_read;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	nvmem = devm_nvmem_register(dev, nvmem_cfg);
1568c2ecf20Sopenharmony_ci	if (IS_ERR(nvmem))
1578c2ecf20Sopenharmony_ci		return PTR_ERR(nvmem);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	randomness = kzalloc(size, GFP_KERNEL);
1608c2ecf20Sopenharmony_ci	if (!randomness)
1618c2ecf20Sopenharmony_ci		return -ENOMEM;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	nvmem_cfg->reg_read(sid, 0, randomness, size);
1648c2ecf20Sopenharmony_ci	add_device_randomness(randomness, size);
1658c2ecf20Sopenharmony_ci	kfree(randomness);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, nvmem);
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	return 0;
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic const struct sunxi_sid_cfg sun4i_a10_cfg = {
1738c2ecf20Sopenharmony_ci	.size = 0x10,
1748c2ecf20Sopenharmony_ci};
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cistatic const struct sunxi_sid_cfg sun7i_a20_cfg = {
1778c2ecf20Sopenharmony_ci	.size = 0x200,
1788c2ecf20Sopenharmony_ci};
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic const struct sunxi_sid_cfg sun8i_h3_cfg = {
1818c2ecf20Sopenharmony_ci	.value_offset = 0x200,
1828c2ecf20Sopenharmony_ci	.size = 0x100,
1838c2ecf20Sopenharmony_ci	.need_register_readout = true,
1848c2ecf20Sopenharmony_ci};
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_cistatic const struct sunxi_sid_cfg sun50i_a64_cfg = {
1878c2ecf20Sopenharmony_ci	.value_offset = 0x200,
1888c2ecf20Sopenharmony_ci	.size = 0x100,
1898c2ecf20Sopenharmony_ci	.need_register_readout = true,
1908c2ecf20Sopenharmony_ci};
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cistatic const struct sunxi_sid_cfg sun50i_h6_cfg = {
1938c2ecf20Sopenharmony_ci	.value_offset = 0x200,
1948c2ecf20Sopenharmony_ci	.size = 0x200,
1958c2ecf20Sopenharmony_ci};
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic const struct of_device_id sunxi_sid_of_match[] = {
1988c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg },
1998c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
2008c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg },
2018c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
2028c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg },
2038c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg },
2048c2ecf20Sopenharmony_ci	{ .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg },
2058c2ecf20Sopenharmony_ci	{/* sentinel */},
2068c2ecf20Sopenharmony_ci};
2078c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
2088c2ecf20Sopenharmony_ci
2098c2ecf20Sopenharmony_cistatic struct platform_driver sunxi_sid_driver = {
2108c2ecf20Sopenharmony_ci	.probe = sunxi_sid_probe,
2118c2ecf20Sopenharmony_ci	.driver = {
2128c2ecf20Sopenharmony_ci		.name = "eeprom-sunxi-sid",
2138c2ecf20Sopenharmony_ci		.of_match_table = sunxi_sid_of_match,
2148c2ecf20Sopenharmony_ci	},
2158c2ecf20Sopenharmony_ci};
2168c2ecf20Sopenharmony_cimodule_platform_driver(sunxi_sid_driver);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ciMODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
2198c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Allwinner sunxi security id driver");
2208c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
221