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