1055f5df5SYangtao Li // SPDX-License-Identifier: GPL-2.0+ 23d0b16a6SMaxime Ripard /* 33d0b16a6SMaxime Ripard * Allwinner sunXi SoCs Security ID support. 43d0b16a6SMaxime Ripard * 53d0b16a6SMaxime Ripard * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> 63d0b16a6SMaxime Ripard * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> 73d0b16a6SMaxime Ripard */ 83d0b16a6SMaxime Ripard 93d0b16a6SMaxime Ripard #include <linux/device.h> 103d0b16a6SMaxime Ripard #include <linux/io.h> 111a963642SIcenowy Zheng #include <linux/iopoll.h> 123d0b16a6SMaxime Ripard #include <linux/module.h> 133d0b16a6SMaxime Ripard #include <linux/nvmem-provider.h> 143d0b16a6SMaxime Ripard #include <linux/of.h> 154a72cda5SIcenowy Zheng #include <linux/of_device.h> 163d0b16a6SMaxime Ripard #include <linux/platform_device.h> 173d0b16a6SMaxime Ripard #include <linux/slab.h> 183d0b16a6SMaxime Ripard #include <linux/random.h> 193d0b16a6SMaxime Ripard 201a963642SIcenowy Zheng /* Registers and special values for doing register-based SID readout on H3 */ 211a963642SIcenowy Zheng #define SUN8I_SID_PRCTL 0x40 221a963642SIcenowy Zheng #define SUN8I_SID_RDKEY 0x60 231a963642SIcenowy Zheng 241a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_MASK 0x1FF 251a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_SHIFT 16 261a963642SIcenowy Zheng #define SUN8I_SID_OP_LOCK (0xAC << 8) 271a963642SIcenowy Zheng #define SUN8I_SID_READ BIT(1) 281a963642SIcenowy Zheng 294a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 301a963642SIcenowy Zheng u32 value_offset; 314a72cda5SIcenowy Zheng u32 size; 321a963642SIcenowy Zheng bool need_register_readout; 334a72cda5SIcenowy Zheng }; 344a72cda5SIcenowy Zheng 353d0b16a6SMaxime Ripard struct sunxi_sid { 363d0b16a6SMaxime Ripard void __iomem *base; 371a963642SIcenowy Zheng u32 value_offset; 383d0b16a6SMaxime Ripard }; 393d0b16a6SMaxime Ripard 409c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 419c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 423d0b16a6SMaxime Ripard { 433d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 44*c151d5edSSamuel Holland u32 word; 453d0b16a6SMaxime Ripard 46*c151d5edSSamuel Holland /* .stride = 4 so offset is guaranteed to be aligned */ 47*c151d5edSSamuel Holland __ioread32_copy(val, sid->base + sid->value_offset + offset, bytes / 4); 48*c151d5edSSamuel Holland 49*c151d5edSSamuel Holland val += round_down(bytes, 4); 50*c151d5edSSamuel Holland offset += round_down(bytes, 4); 51*c151d5edSSamuel Holland bytes = bytes % 4; 52*c151d5edSSamuel Holland 53*c151d5edSSamuel Holland if (!bytes) 54*c151d5edSSamuel Holland return 0; 55*c151d5edSSamuel Holland 56*c151d5edSSamuel Holland /* Handle any trailing bytes */ 57*c151d5edSSamuel Holland word = readl_relaxed(sid->base + sid->value_offset + offset); 58*c151d5edSSamuel Holland memcpy(val, &word, bytes); 593d0b16a6SMaxime Ripard 603d0b16a6SMaxime Ripard return 0; 613d0b16a6SMaxime Ripard } 623d0b16a6SMaxime Ripard 631a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 640ab09d65SIcenowy Zheng const unsigned int offset, 650ab09d65SIcenowy Zheng u32 *out) 661a963642SIcenowy Zheng { 671a963642SIcenowy Zheng u32 reg_val; 681a963642SIcenowy Zheng int ret; 691a963642SIcenowy Zheng 701a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 710ab09d65SIcenowy Zheng reg_val = (offset & SUN8I_SID_OFFSET_MASK) 721a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 731a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 741a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 751a963642SIcenowy Zheng 761a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 771a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 781a963642SIcenowy Zheng if (ret) 791a963642SIcenowy Zheng return ret; 801a963642SIcenowy Zheng 810ab09d65SIcenowy Zheng if (out) 820ab09d65SIcenowy Zheng *out = readl(sid->base + SUN8I_SID_RDKEY); 830ab09d65SIcenowy Zheng 841a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 850ab09d65SIcenowy Zheng 860ab09d65SIcenowy Zheng return 0; 870ab09d65SIcenowy Zheng } 880ab09d65SIcenowy Zheng 890ab09d65SIcenowy Zheng /* 900ab09d65SIcenowy Zheng * On Allwinner H3, the value on the 0x200 offset of the SID controller seems 910ab09d65SIcenowy Zheng * to be not reliable at all. 920ab09d65SIcenowy Zheng * Read by the registers instead. 930ab09d65SIcenowy Zheng */ 940ab09d65SIcenowy Zheng static int sun8i_sid_read_by_reg(void *context, unsigned int offset, 950ab09d65SIcenowy Zheng void *val, size_t bytes) 960ab09d65SIcenowy Zheng { 970ab09d65SIcenowy Zheng struct sunxi_sid *sid = context; 98de2a3eaeSChen-Yu Tsai u32 word; 990ab09d65SIcenowy Zheng int ret; 1000ab09d65SIcenowy Zheng 101de2a3eaeSChen-Yu Tsai /* .stride = 4 so offset is guaranteed to be aligned */ 102de2a3eaeSChen-Yu Tsai while (bytes >= 4) { 103de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, val); 1040ab09d65SIcenowy Zheng if (ret) 1050ab09d65SIcenowy Zheng return ret; 106de2a3eaeSChen-Yu Tsai 107de2a3eaeSChen-Yu Tsai val += 4; 108de2a3eaeSChen-Yu Tsai offset += 4; 109de2a3eaeSChen-Yu Tsai bytes -= 4; 1100ab09d65SIcenowy Zheng } 1110ab09d65SIcenowy Zheng 112de2a3eaeSChen-Yu Tsai if (!bytes) 113de2a3eaeSChen-Yu Tsai return 0; 114de2a3eaeSChen-Yu Tsai 115de2a3eaeSChen-Yu Tsai /* Handle any trailing bytes */ 116de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, &word); 117de2a3eaeSChen-Yu Tsai if (ret) 118de2a3eaeSChen-Yu Tsai return ret; 119de2a3eaeSChen-Yu Tsai 120de2a3eaeSChen-Yu Tsai memcpy(val, &word, bytes); 121de2a3eaeSChen-Yu Tsai 1221a963642SIcenowy Zheng return 0; 1231a963642SIcenowy Zheng } 1241a963642SIcenowy Zheng 1253d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1263d0b16a6SMaxime Ripard { 1273d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1283d0b16a6SMaxime Ripard struct resource *res; 1297fa5ad23SChen-Yu Tsai struct nvmem_config *nvmem_cfg; 1303d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1313d0b16a6SMaxime Ripard struct sunxi_sid *sid; 1329c4adfb5SChen-Yu Tsai int size; 1333d0b16a6SMaxime Ripard char *randomness; 1344a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1353d0b16a6SMaxime Ripard 1363d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1373d0b16a6SMaxime Ripard if (!sid) 1383d0b16a6SMaxime Ripard return -ENOMEM; 1393d0b16a6SMaxime Ripard 1404a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1414a72cda5SIcenowy Zheng if (!cfg) 1424a72cda5SIcenowy Zheng return -EINVAL; 1431a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1444a72cda5SIcenowy Zheng 1453d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1463d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1473d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1483d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1493d0b16a6SMaxime Ripard 1504a72cda5SIcenowy Zheng size = cfg->size; 1514a72cda5SIcenowy Zheng 1527fa5ad23SChen-Yu Tsai nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL); 1537fa5ad23SChen-Yu Tsai if (!nvmem_cfg) 1547fa5ad23SChen-Yu Tsai return -ENOMEM; 1557fa5ad23SChen-Yu Tsai 1567fa5ad23SChen-Yu Tsai nvmem_cfg->dev = dev; 1577fa5ad23SChen-Yu Tsai nvmem_cfg->name = "sunxi-sid"; 15878a005a2SSamuel Holland nvmem_cfg->type = NVMEM_TYPE_OTP; 1597fa5ad23SChen-Yu Tsai nvmem_cfg->read_only = true; 1607fa5ad23SChen-Yu Tsai nvmem_cfg->size = cfg->size; 1617fa5ad23SChen-Yu Tsai nvmem_cfg->word_size = 1; 1627fa5ad23SChen-Yu Tsai nvmem_cfg->stride = 4; 1637fa5ad23SChen-Yu Tsai nvmem_cfg->priv = sid; 1640ab09d65SIcenowy Zheng if (cfg->need_register_readout) 1657fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sun8i_sid_read_by_reg; 1660ab09d65SIcenowy Zheng else 1677fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sunxi_sid_read; 1687fa5ad23SChen-Yu Tsai 1697fa5ad23SChen-Yu Tsai nvmem = devm_nvmem_register(dev, nvmem_cfg); 1703d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1713d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1723d0b16a6SMaxime Ripard 1736396bb22SKees Cook randomness = kzalloc(size, GFP_KERNEL); 1746eed8dd9SBartosz Golaszewski if (!randomness) 1756eed8dd9SBartosz Golaszewski return -ENOMEM; 176fb727077SMaxime Ripard 1777fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read(sid, 0, randomness, size); 1783d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1793d0b16a6SMaxime Ripard kfree(randomness); 1803d0b16a6SMaxime Ripard 1813d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1823d0b16a6SMaxime Ripard 1833d0b16a6SMaxime Ripard return 0; 1843d0b16a6SMaxime Ripard } 1853d0b16a6SMaxime Ripard 1864a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 1874a72cda5SIcenowy Zheng .size = 0x10, 1884a72cda5SIcenowy Zheng }; 1894a72cda5SIcenowy Zheng 1904a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 1914a72cda5SIcenowy Zheng .size = 0x200, 1924a72cda5SIcenowy Zheng }; 1934a72cda5SIcenowy Zheng 1941a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 1951a963642SIcenowy Zheng .value_offset = 0x200, 1961a963642SIcenowy Zheng .size = 0x100, 1971a963642SIcenowy Zheng .need_register_readout = true, 1981a963642SIcenowy Zheng }; 1991a963642SIcenowy Zheng 20007ae4fdeSSamuel Holland static const struct sunxi_sid_cfg sun20i_d1_cfg = { 20107ae4fdeSSamuel Holland .value_offset = 0x200, 20207ae4fdeSSamuel Holland .size = 0x100, 20307ae4fdeSSamuel Holland }; 20407ae4fdeSSamuel Holland 205b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 206b7fe57b8SIcenowy Zheng .value_offset = 0x200, 207b7fe57b8SIcenowy Zheng .size = 0x100, 2082ac00e34SStefan Mavrodiev .need_register_readout = true, 209b7fe57b8SIcenowy Zheng }; 210b7fe57b8SIcenowy Zheng 211fc1eb6ebSYangtao Li static const struct sunxi_sid_cfg sun50i_h6_cfg = { 212fc1eb6ebSYangtao Li .value_offset = 0x200, 213fc1eb6ebSYangtao Li .size = 0x200, 214fc1eb6ebSYangtao Li }; 215fc1eb6ebSYangtao Li 2163d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2174a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2184a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 219da75b890SChen-Yu Tsai { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg }, 2201a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 22107ae4fdeSSamuel Holland { .compatible = "allwinner,sun20i-d1-sid", .data = &sun20i_d1_cfg }, 222b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 223da75b890SChen-Yu Tsai { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg }, 224fc1eb6ebSYangtao Li { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg }, 2253d0b16a6SMaxime Ripard {/* sentinel */}, 2263d0b16a6SMaxime Ripard }; 2273d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2283d0b16a6SMaxime Ripard 2293d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2303d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2313d0b16a6SMaxime Ripard .driver = { 2323d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2333d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2343d0b16a6SMaxime Ripard }, 2353d0b16a6SMaxime Ripard }; 2363d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2373d0b16a6SMaxime Ripard 2383d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2393d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2403d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 241