13d0b16a6SMaxime Ripard /* 23d0b16a6SMaxime Ripard * Allwinner sunXi SoCs Security ID support. 33d0b16a6SMaxime Ripard * 43d0b16a6SMaxime Ripard * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> 53d0b16a6SMaxime Ripard * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> 63d0b16a6SMaxime Ripard * 73d0b16a6SMaxime Ripard * This program is free software; you can redistribute it and/or modify 83d0b16a6SMaxime Ripard * it under the terms of the GNU General Public License as published by 93d0b16a6SMaxime Ripard * the Free Software Foundation; either version 2 of the License, or 103d0b16a6SMaxime Ripard * (at your option) any later version. 113d0b16a6SMaxime Ripard * 123d0b16a6SMaxime Ripard * This program is distributed in the hope that it will be useful, 133d0b16a6SMaxime Ripard * but WITHOUT ANY WARRANTY; without even the implied warranty of 143d0b16a6SMaxime Ripard * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 153d0b16a6SMaxime Ripard * GNU General Public License for more details. 163d0b16a6SMaxime Ripard */ 173d0b16a6SMaxime Ripard 183d0b16a6SMaxime Ripard #include <linux/device.h> 193d0b16a6SMaxime Ripard #include <linux/io.h> 20*1a963642SIcenowy Zheng #include <linux/iopoll.h> 213d0b16a6SMaxime Ripard #include <linux/module.h> 223d0b16a6SMaxime Ripard #include <linux/nvmem-provider.h> 233d0b16a6SMaxime Ripard #include <linux/of.h> 244a72cda5SIcenowy Zheng #include <linux/of_device.h> 253d0b16a6SMaxime Ripard #include <linux/platform_device.h> 263d0b16a6SMaxime Ripard #include <linux/slab.h> 273d0b16a6SMaxime Ripard #include <linux/random.h> 283d0b16a6SMaxime Ripard 29*1a963642SIcenowy Zheng /* Registers and special values for doing register-based SID readout on H3 */ 30*1a963642SIcenowy Zheng #define SUN8I_SID_PRCTL 0x40 31*1a963642SIcenowy Zheng #define SUN8I_SID_RDKEY 0x60 32*1a963642SIcenowy Zheng 33*1a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_MASK 0x1FF 34*1a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_SHIFT 16 35*1a963642SIcenowy Zheng #define SUN8I_SID_OP_LOCK (0xAC << 8) 36*1a963642SIcenowy Zheng #define SUN8I_SID_READ BIT(1) 37*1a963642SIcenowy Zheng 383d0b16a6SMaxime Ripard static struct nvmem_config econfig = { 393d0b16a6SMaxime Ripard .name = "sunxi-sid", 403d0b16a6SMaxime Ripard .read_only = true, 419c7b16ebSSrinivas Kandagatla .stride = 4, 429c7b16ebSSrinivas Kandagatla .word_size = 1, 433d0b16a6SMaxime Ripard .owner = THIS_MODULE, 443d0b16a6SMaxime Ripard }; 453d0b16a6SMaxime Ripard 464a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 47*1a963642SIcenowy Zheng u32 value_offset; 484a72cda5SIcenowy Zheng u32 size; 49*1a963642SIcenowy Zheng bool need_register_readout; 504a72cda5SIcenowy Zheng }; 514a72cda5SIcenowy Zheng 523d0b16a6SMaxime Ripard struct sunxi_sid { 533d0b16a6SMaxime Ripard void __iomem *base; 54*1a963642SIcenowy Zheng u32 value_offset; 553d0b16a6SMaxime Ripard }; 563d0b16a6SMaxime Ripard 573d0b16a6SMaxime Ripard /* We read the entire key, due to a 32 bit read alignment requirement. Since we 583d0b16a6SMaxime Ripard * want to return the requested byte, this results in somewhat slower code and 593d0b16a6SMaxime Ripard * uses 4 times more reads as needed but keeps code simpler. Since the SID is 603d0b16a6SMaxime Ripard * only very rarely probed, this is not really an issue. 613d0b16a6SMaxime Ripard */ 623d0b16a6SMaxime Ripard static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, 633d0b16a6SMaxime Ripard const unsigned int offset) 643d0b16a6SMaxime Ripard { 653d0b16a6SMaxime Ripard u32 sid_key; 663d0b16a6SMaxime Ripard 673d0b16a6SMaxime Ripard sid_key = ioread32be(sid->base + round_down(offset, 4)); 683d0b16a6SMaxime Ripard sid_key >>= (offset % 4) * 8; 693d0b16a6SMaxime Ripard 703d0b16a6SMaxime Ripard return sid_key; /* Only return the last byte */ 713d0b16a6SMaxime Ripard } 723d0b16a6SMaxime Ripard 739c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 749c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 753d0b16a6SMaxime Ripard { 763d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 773d0b16a6SMaxime Ripard u8 *buf = val; 783d0b16a6SMaxime Ripard 79*1a963642SIcenowy Zheng /* Offset the read operation to the real position of SID */ 80*1a963642SIcenowy Zheng offset += sid->value_offset; 81*1a963642SIcenowy Zheng 829c7b16ebSSrinivas Kandagatla while (bytes--) 839c7b16ebSSrinivas Kandagatla *buf++ = sunxi_sid_read_byte(sid, offset++); 843d0b16a6SMaxime Ripard 853d0b16a6SMaxime Ripard return 0; 863d0b16a6SMaxime Ripard } 873d0b16a6SMaxime Ripard 88*1a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 89*1a963642SIcenowy Zheng const unsigned int word) 90*1a963642SIcenowy Zheng { 91*1a963642SIcenowy Zheng u32 reg_val; 92*1a963642SIcenowy Zheng int ret; 93*1a963642SIcenowy Zheng 94*1a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 95*1a963642SIcenowy Zheng reg_val = (word & SUN8I_SID_OFFSET_MASK) 96*1a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 97*1a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 98*1a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 99*1a963642SIcenowy Zheng 100*1a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 101*1a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 102*1a963642SIcenowy Zheng if (ret) 103*1a963642SIcenowy Zheng return ret; 104*1a963642SIcenowy Zheng 105*1a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 106*1a963642SIcenowy Zheng return 0; 107*1a963642SIcenowy Zheng } 108*1a963642SIcenowy Zheng 1093d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1103d0b16a6SMaxime Ripard { 1113d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1123d0b16a6SMaxime Ripard struct resource *res; 1133d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1143d0b16a6SMaxime Ripard struct sunxi_sid *sid; 115fb727077SMaxime Ripard int ret, i, size; 1163d0b16a6SMaxime Ripard char *randomness; 1174a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1183d0b16a6SMaxime Ripard 1193d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1203d0b16a6SMaxime Ripard if (!sid) 1213d0b16a6SMaxime Ripard return -ENOMEM; 1223d0b16a6SMaxime Ripard 1234a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1244a72cda5SIcenowy Zheng if (!cfg) 1254a72cda5SIcenowy Zheng return -EINVAL; 126*1a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1274a72cda5SIcenowy Zheng 1283d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1293d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1303d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1313d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1323d0b16a6SMaxime Ripard 1334a72cda5SIcenowy Zheng size = cfg->size; 1344a72cda5SIcenowy Zheng 135*1a963642SIcenowy Zheng if (cfg->need_register_readout) { 136*1a963642SIcenowy Zheng /* 137*1a963642SIcenowy Zheng * H3's SID controller have a bug that the value at 0x200 138*1a963642SIcenowy Zheng * offset is not the correct value when the hardware is reseted. 139*1a963642SIcenowy Zheng * However, after doing a register-based read operation, the 140*1a963642SIcenowy Zheng * value become right. 141*1a963642SIcenowy Zheng * Do a full read operation here, but ignore its value 142*1a963642SIcenowy Zheng * (as it's more fast to read by direct MMIO value than 143*1a963642SIcenowy Zheng * with registers) 144*1a963642SIcenowy Zheng */ 145*1a963642SIcenowy Zheng for (i = 0; i < (size >> 2); i++) { 146*1a963642SIcenowy Zheng ret = sun8i_sid_register_readout(sid, i); 147*1a963642SIcenowy Zheng if (ret) 148*1a963642SIcenowy Zheng return ret; 149*1a963642SIcenowy Zheng } 150*1a963642SIcenowy Zheng } 151*1a963642SIcenowy Zheng 1524a72cda5SIcenowy Zheng econfig.size = size; 1533d0b16a6SMaxime Ripard econfig.dev = dev; 1549c7b16ebSSrinivas Kandagatla econfig.reg_read = sunxi_sid_read; 1559c7b16ebSSrinivas Kandagatla econfig.priv = sid; 1563d0b16a6SMaxime Ripard nvmem = nvmem_register(&econfig); 1573d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1583d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1593d0b16a6SMaxime Ripard 160d16abd30SCaesar Wang randomness = kzalloc(sizeof(u8) * (size), GFP_KERNEL); 161fb727077SMaxime Ripard if (!randomness) { 162fb727077SMaxime Ripard ret = -EINVAL; 163fb727077SMaxime Ripard goto err_unreg_nvmem; 164fb727077SMaxime Ripard } 165fb727077SMaxime Ripard 1663d0b16a6SMaxime Ripard for (i = 0; i < size; i++) 1673d0b16a6SMaxime Ripard randomness[i] = sunxi_sid_read_byte(sid, i); 1683d0b16a6SMaxime Ripard 1693d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1703d0b16a6SMaxime Ripard kfree(randomness); 1713d0b16a6SMaxime Ripard 1723d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1733d0b16a6SMaxime Ripard 1743d0b16a6SMaxime Ripard return 0; 175fb727077SMaxime Ripard 176fb727077SMaxime Ripard err_unreg_nvmem: 177fb727077SMaxime Ripard nvmem_unregister(nvmem); 178fb727077SMaxime Ripard return ret; 1793d0b16a6SMaxime Ripard } 1803d0b16a6SMaxime Ripard 1813d0b16a6SMaxime Ripard static int sunxi_sid_remove(struct platform_device *pdev) 1823d0b16a6SMaxime Ripard { 1833d0b16a6SMaxime Ripard struct nvmem_device *nvmem = platform_get_drvdata(pdev); 1843d0b16a6SMaxime Ripard 1853d0b16a6SMaxime Ripard return nvmem_unregister(nvmem); 1863d0b16a6SMaxime Ripard } 1873d0b16a6SMaxime Ripard 1884a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 1894a72cda5SIcenowy Zheng .size = 0x10, 1904a72cda5SIcenowy Zheng }; 1914a72cda5SIcenowy Zheng 1924a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 1934a72cda5SIcenowy Zheng .size = 0x200, 1944a72cda5SIcenowy Zheng }; 1954a72cda5SIcenowy Zheng 196*1a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 197*1a963642SIcenowy Zheng .value_offset = 0x200, 198*1a963642SIcenowy Zheng .size = 0x100, 199*1a963642SIcenowy Zheng .need_register_readout = true, 200*1a963642SIcenowy Zheng }; 201*1a963642SIcenowy Zheng 2023d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2034a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2044a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 205*1a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 2063d0b16a6SMaxime Ripard {/* sentinel */}, 2073d0b16a6SMaxime Ripard }; 2083d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2093d0b16a6SMaxime Ripard 2103d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2113d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2123d0b16a6SMaxime Ripard .remove = sunxi_sid_remove, 2133d0b16a6SMaxime Ripard .driver = { 2143d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2153d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2163d0b16a6SMaxime Ripard }, 2173d0b16a6SMaxime Ripard }; 2183d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2193d0b16a6SMaxime Ripard 2203d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2213d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2223d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 223