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> 153d0b16a6SMaxime Ripard #include <linux/platform_device.h> 163d0b16a6SMaxime Ripard #include <linux/slab.h> 173d0b16a6SMaxime Ripard #include <linux/random.h> 183d0b16a6SMaxime Ripard 191a963642SIcenowy Zheng /* Registers and special values for doing register-based SID readout on H3 */ 201a963642SIcenowy Zheng #define SUN8I_SID_PRCTL 0x40 211a963642SIcenowy Zheng #define SUN8I_SID_RDKEY 0x60 221a963642SIcenowy Zheng 231a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_MASK 0x1FF 241a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_SHIFT 16 251a963642SIcenowy Zheng #define SUN8I_SID_OP_LOCK (0xAC << 8) 261a963642SIcenowy Zheng #define SUN8I_SID_READ BIT(1) 271a963642SIcenowy Zheng 284a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 291a963642SIcenowy Zheng u32 value_offset; 304a72cda5SIcenowy Zheng u32 size; 311a963642SIcenowy Zheng bool need_register_readout; 324a72cda5SIcenowy Zheng }; 334a72cda5SIcenowy Zheng 343d0b16a6SMaxime Ripard struct sunxi_sid { 353d0b16a6SMaxime Ripard void __iomem *base; 361a963642SIcenowy Zheng u32 value_offset; 373d0b16a6SMaxime Ripard }; 383d0b16a6SMaxime Ripard 399c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 409c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 413d0b16a6SMaxime Ripard { 423d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 43c151d5edSSamuel Holland u32 word; 443d0b16a6SMaxime Ripard 45c151d5edSSamuel Holland /* .stride = 4 so offset is guaranteed to be aligned */ 46c151d5edSSamuel Holland __ioread32_copy(val, sid->base + sid->value_offset + offset, bytes / 4); 47c151d5edSSamuel Holland 48c151d5edSSamuel Holland val += round_down(bytes, 4); 49c151d5edSSamuel Holland offset += round_down(bytes, 4); 50c151d5edSSamuel Holland bytes = bytes % 4; 51c151d5edSSamuel Holland 52c151d5edSSamuel Holland if (!bytes) 53c151d5edSSamuel Holland return 0; 54c151d5edSSamuel Holland 55c151d5edSSamuel Holland /* Handle any trailing bytes */ 56c151d5edSSamuel Holland word = readl_relaxed(sid->base + sid->value_offset + offset); 57c151d5edSSamuel Holland memcpy(val, &word, bytes); 583d0b16a6SMaxime Ripard 593d0b16a6SMaxime Ripard return 0; 603d0b16a6SMaxime Ripard } 613d0b16a6SMaxime Ripard 621a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 630ab09d65SIcenowy Zheng const unsigned int offset, 640ab09d65SIcenowy Zheng u32 *out) 651a963642SIcenowy Zheng { 661a963642SIcenowy Zheng u32 reg_val; 671a963642SIcenowy Zheng int ret; 681a963642SIcenowy Zheng 691a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 700ab09d65SIcenowy Zheng reg_val = (offset & SUN8I_SID_OFFSET_MASK) 711a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 721a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 731a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 741a963642SIcenowy Zheng 751a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 761a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 771a963642SIcenowy Zheng if (ret) 781a963642SIcenowy Zheng return ret; 791a963642SIcenowy Zheng 800ab09d65SIcenowy Zheng if (out) 810ab09d65SIcenowy Zheng *out = readl(sid->base + SUN8I_SID_RDKEY); 820ab09d65SIcenowy Zheng 831a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 840ab09d65SIcenowy Zheng 850ab09d65SIcenowy Zheng return 0; 860ab09d65SIcenowy Zheng } 870ab09d65SIcenowy Zheng 880ab09d65SIcenowy Zheng /* 890ab09d65SIcenowy Zheng * On Allwinner H3, the value on the 0x200 offset of the SID controller seems 900ab09d65SIcenowy Zheng * to be not reliable at all. 910ab09d65SIcenowy Zheng * Read by the registers instead. 920ab09d65SIcenowy Zheng */ 930ab09d65SIcenowy Zheng static int sun8i_sid_read_by_reg(void *context, unsigned int offset, 940ab09d65SIcenowy Zheng void *val, size_t bytes) 950ab09d65SIcenowy Zheng { 960ab09d65SIcenowy Zheng struct sunxi_sid *sid = context; 97de2a3eaeSChen-Yu Tsai u32 word; 980ab09d65SIcenowy Zheng int ret; 990ab09d65SIcenowy Zheng 100de2a3eaeSChen-Yu Tsai /* .stride = 4 so offset is guaranteed to be aligned */ 101de2a3eaeSChen-Yu Tsai while (bytes >= 4) { 102de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, val); 1030ab09d65SIcenowy Zheng if (ret) 1040ab09d65SIcenowy Zheng return ret; 105de2a3eaeSChen-Yu Tsai 106de2a3eaeSChen-Yu Tsai val += 4; 107de2a3eaeSChen-Yu Tsai offset += 4; 108de2a3eaeSChen-Yu Tsai bytes -= 4; 1090ab09d65SIcenowy Zheng } 1100ab09d65SIcenowy Zheng 111de2a3eaeSChen-Yu Tsai if (!bytes) 112de2a3eaeSChen-Yu Tsai return 0; 113de2a3eaeSChen-Yu Tsai 114de2a3eaeSChen-Yu Tsai /* Handle any trailing bytes */ 115de2a3eaeSChen-Yu Tsai ret = sun8i_sid_register_readout(sid, offset, &word); 116de2a3eaeSChen-Yu Tsai if (ret) 117de2a3eaeSChen-Yu Tsai return ret; 118de2a3eaeSChen-Yu Tsai 119de2a3eaeSChen-Yu Tsai memcpy(val, &word, bytes); 120de2a3eaeSChen-Yu Tsai 1211a963642SIcenowy Zheng return 0; 1221a963642SIcenowy Zheng } 1231a963642SIcenowy Zheng 1243d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1253d0b16a6SMaxime Ripard { 1263d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1277fa5ad23SChen-Yu Tsai struct nvmem_config *nvmem_cfg; 1283d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1293d0b16a6SMaxime Ripard struct sunxi_sid *sid; 1309c4adfb5SChen-Yu Tsai int size; 1313d0b16a6SMaxime Ripard char *randomness; 1324a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1333d0b16a6SMaxime Ripard 1343d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1353d0b16a6SMaxime Ripard if (!sid) 1363d0b16a6SMaxime Ripard return -ENOMEM; 1373d0b16a6SMaxime Ripard 1384a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1394a72cda5SIcenowy Zheng if (!cfg) 1404a72cda5SIcenowy Zheng return -EINVAL; 1411a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1424a72cda5SIcenowy Zheng 1439ccfcbebSYangtao Li sid->base = devm_platform_ioremap_resource(pdev, 0); 1443d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1453d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1463d0b16a6SMaxime Ripard 1474a72cda5SIcenowy Zheng size = cfg->size; 1484a72cda5SIcenowy Zheng 1497fa5ad23SChen-Yu Tsai nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL); 1507fa5ad23SChen-Yu Tsai if (!nvmem_cfg) 1517fa5ad23SChen-Yu Tsai return -ENOMEM; 1527fa5ad23SChen-Yu Tsai 1537fa5ad23SChen-Yu Tsai nvmem_cfg->dev = dev; 1547fa5ad23SChen-Yu Tsai nvmem_cfg->name = "sunxi-sid"; 15578a005a2SSamuel Holland nvmem_cfg->type = NVMEM_TYPE_OTP; 156*26e2fe4cSRafał Miłecki nvmem_cfg->add_legacy_fixed_of_cells = true; 1577fa5ad23SChen-Yu Tsai nvmem_cfg->read_only = true; 1587fa5ad23SChen-Yu Tsai nvmem_cfg->size = cfg->size; 1597fa5ad23SChen-Yu Tsai nvmem_cfg->word_size = 1; 1607fa5ad23SChen-Yu Tsai nvmem_cfg->stride = 4; 1617fa5ad23SChen-Yu Tsai nvmem_cfg->priv = sid; 1620ab09d65SIcenowy Zheng if (cfg->need_register_readout) 1637fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sun8i_sid_read_by_reg; 1640ab09d65SIcenowy Zheng else 1657fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read = sunxi_sid_read; 1667fa5ad23SChen-Yu Tsai 1677fa5ad23SChen-Yu Tsai nvmem = devm_nvmem_register(dev, nvmem_cfg); 1683d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1693d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1703d0b16a6SMaxime Ripard 1716396bb22SKees Cook randomness = kzalloc(size, GFP_KERNEL); 1726eed8dd9SBartosz Golaszewski if (!randomness) 1736eed8dd9SBartosz Golaszewski return -ENOMEM; 174fb727077SMaxime Ripard 1757fa5ad23SChen-Yu Tsai nvmem_cfg->reg_read(sid, 0, randomness, size); 1763d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1773d0b16a6SMaxime Ripard kfree(randomness); 1783d0b16a6SMaxime Ripard 1793d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1803d0b16a6SMaxime Ripard 1813d0b16a6SMaxime Ripard return 0; 1823d0b16a6SMaxime Ripard } 1833d0b16a6SMaxime Ripard 1844a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 1854a72cda5SIcenowy Zheng .size = 0x10, 1864a72cda5SIcenowy Zheng }; 1874a72cda5SIcenowy Zheng 1884a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 1894a72cda5SIcenowy Zheng .size = 0x200, 1904a72cda5SIcenowy Zheng }; 1914a72cda5SIcenowy Zheng 1921a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 1931a963642SIcenowy Zheng .value_offset = 0x200, 1941a963642SIcenowy Zheng .size = 0x100, 1951a963642SIcenowy Zheng .need_register_readout = true, 1961a963642SIcenowy Zheng }; 1971a963642SIcenowy Zheng 198b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 199b7fe57b8SIcenowy Zheng .value_offset = 0x200, 200b7fe57b8SIcenowy Zheng .size = 0x100, 201b7fe57b8SIcenowy Zheng }; 202b7fe57b8SIcenowy Zheng 203fc1eb6ebSYangtao Li static const struct sunxi_sid_cfg sun50i_h6_cfg = { 204fc1eb6ebSYangtao Li .value_offset = 0x200, 205fc1eb6ebSYangtao Li .size = 0x200, 206fc1eb6ebSYangtao Li }; 207fc1eb6ebSYangtao Li 2083d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2094a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2104a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 211da75b890SChen-Yu Tsai { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg }, 2121a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 213c9dde85dSSamuel Holland { .compatible = "allwinner,sun20i-d1-sid", .data = &sun50i_a64_cfg }, 214b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 215da75b890SChen-Yu Tsai { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg }, 216fc1eb6ebSYangtao Li { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg }, 2173d0b16a6SMaxime Ripard {/* sentinel */}, 2183d0b16a6SMaxime Ripard }; 2193d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2203d0b16a6SMaxime Ripard 2213d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2223d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2233d0b16a6SMaxime Ripard .driver = { 2243d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2253d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2263d0b16a6SMaxime Ripard }, 2273d0b16a6SMaxime Ripard }; 2283d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2293d0b16a6SMaxime Ripard 2303d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2313d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2323d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 233