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> 201a963642SIcenowy 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 291a963642SIcenowy Zheng /* Registers and special values for doing register-based SID readout on H3 */ 301a963642SIcenowy Zheng #define SUN8I_SID_PRCTL 0x40 311a963642SIcenowy Zheng #define SUN8I_SID_RDKEY 0x60 321a963642SIcenowy Zheng 331a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_MASK 0x1FF 341a963642SIcenowy Zheng #define SUN8I_SID_OFFSET_SHIFT 16 351a963642SIcenowy Zheng #define SUN8I_SID_OP_LOCK (0xAC << 8) 361a963642SIcenowy Zheng #define SUN8I_SID_READ BIT(1) 371a963642SIcenowy 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 }; 443d0b16a6SMaxime Ripard 454a72cda5SIcenowy Zheng struct sunxi_sid_cfg { 461a963642SIcenowy Zheng u32 value_offset; 474a72cda5SIcenowy Zheng u32 size; 481a963642SIcenowy Zheng bool need_register_readout; 494a72cda5SIcenowy Zheng }; 504a72cda5SIcenowy Zheng 513d0b16a6SMaxime Ripard struct sunxi_sid { 523d0b16a6SMaxime Ripard void __iomem *base; 531a963642SIcenowy Zheng u32 value_offset; 543d0b16a6SMaxime Ripard }; 553d0b16a6SMaxime Ripard 563d0b16a6SMaxime Ripard /* We read the entire key, due to a 32 bit read alignment requirement. Since we 573d0b16a6SMaxime Ripard * want to return the requested byte, this results in somewhat slower code and 583d0b16a6SMaxime Ripard * uses 4 times more reads as needed but keeps code simpler. Since the SID is 593d0b16a6SMaxime Ripard * only very rarely probed, this is not really an issue. 603d0b16a6SMaxime Ripard */ 613d0b16a6SMaxime Ripard static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, 623d0b16a6SMaxime Ripard const unsigned int offset) 633d0b16a6SMaxime Ripard { 643d0b16a6SMaxime Ripard u32 sid_key; 653d0b16a6SMaxime Ripard 663d0b16a6SMaxime Ripard sid_key = ioread32be(sid->base + round_down(offset, 4)); 673d0b16a6SMaxime Ripard sid_key >>= (offset % 4) * 8; 683d0b16a6SMaxime Ripard 693d0b16a6SMaxime Ripard return sid_key; /* Only return the last byte */ 703d0b16a6SMaxime Ripard } 713d0b16a6SMaxime Ripard 729c7b16ebSSrinivas Kandagatla static int sunxi_sid_read(void *context, unsigned int offset, 739c7b16ebSSrinivas Kandagatla void *val, size_t bytes) 743d0b16a6SMaxime Ripard { 753d0b16a6SMaxime Ripard struct sunxi_sid *sid = context; 763d0b16a6SMaxime Ripard u8 *buf = val; 773d0b16a6SMaxime Ripard 781a963642SIcenowy Zheng /* Offset the read operation to the real position of SID */ 791a963642SIcenowy Zheng offset += sid->value_offset; 801a963642SIcenowy Zheng 819c7b16ebSSrinivas Kandagatla while (bytes--) 829c7b16ebSSrinivas Kandagatla *buf++ = sunxi_sid_read_byte(sid, offset++); 833d0b16a6SMaxime Ripard 843d0b16a6SMaxime Ripard return 0; 853d0b16a6SMaxime Ripard } 863d0b16a6SMaxime Ripard 871a963642SIcenowy Zheng static int sun8i_sid_register_readout(const struct sunxi_sid *sid, 881a963642SIcenowy Zheng const unsigned int word) 891a963642SIcenowy Zheng { 901a963642SIcenowy Zheng u32 reg_val; 911a963642SIcenowy Zheng int ret; 921a963642SIcenowy Zheng 931a963642SIcenowy Zheng /* Set word, lock access, and set read command */ 941a963642SIcenowy Zheng reg_val = (word & SUN8I_SID_OFFSET_MASK) 951a963642SIcenowy Zheng << SUN8I_SID_OFFSET_SHIFT; 961a963642SIcenowy Zheng reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 971a963642SIcenowy Zheng writel(reg_val, sid->base + SUN8I_SID_PRCTL); 981a963642SIcenowy Zheng 991a963642SIcenowy Zheng ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 1001a963642SIcenowy Zheng !(reg_val & SUN8I_SID_READ), 100, 250000); 1011a963642SIcenowy Zheng if (ret) 1021a963642SIcenowy Zheng return ret; 1031a963642SIcenowy Zheng 1041a963642SIcenowy Zheng writel(0, sid->base + SUN8I_SID_PRCTL); 1051a963642SIcenowy Zheng return 0; 1061a963642SIcenowy Zheng } 1071a963642SIcenowy Zheng 1083d0b16a6SMaxime Ripard static int sunxi_sid_probe(struct platform_device *pdev) 1093d0b16a6SMaxime Ripard { 1103d0b16a6SMaxime Ripard struct device *dev = &pdev->dev; 1113d0b16a6SMaxime Ripard struct resource *res; 1123d0b16a6SMaxime Ripard struct nvmem_device *nvmem; 1133d0b16a6SMaxime Ripard struct sunxi_sid *sid; 114fb727077SMaxime Ripard int ret, i, size; 1153d0b16a6SMaxime Ripard char *randomness; 1164a72cda5SIcenowy Zheng const struct sunxi_sid_cfg *cfg; 1173d0b16a6SMaxime Ripard 1183d0b16a6SMaxime Ripard sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 1193d0b16a6SMaxime Ripard if (!sid) 1203d0b16a6SMaxime Ripard return -ENOMEM; 1213d0b16a6SMaxime Ripard 1224a72cda5SIcenowy Zheng cfg = of_device_get_match_data(dev); 1234a72cda5SIcenowy Zheng if (!cfg) 1244a72cda5SIcenowy Zheng return -EINVAL; 1251a963642SIcenowy Zheng sid->value_offset = cfg->value_offset; 1264a72cda5SIcenowy Zheng 1273d0b16a6SMaxime Ripard res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1283d0b16a6SMaxime Ripard sid->base = devm_ioremap_resource(dev, res); 1293d0b16a6SMaxime Ripard if (IS_ERR(sid->base)) 1303d0b16a6SMaxime Ripard return PTR_ERR(sid->base); 1313d0b16a6SMaxime Ripard 1324a72cda5SIcenowy Zheng size = cfg->size; 1334a72cda5SIcenowy Zheng 1341a963642SIcenowy Zheng if (cfg->need_register_readout) { 1351a963642SIcenowy Zheng /* 1361a963642SIcenowy Zheng * H3's SID controller have a bug that the value at 0x200 1371a963642SIcenowy Zheng * offset is not the correct value when the hardware is reseted. 1381a963642SIcenowy Zheng * However, after doing a register-based read operation, the 1391a963642SIcenowy Zheng * value become right. 1401a963642SIcenowy Zheng * Do a full read operation here, but ignore its value 1411a963642SIcenowy Zheng * (as it's more fast to read by direct MMIO value than 1421a963642SIcenowy Zheng * with registers) 1431a963642SIcenowy Zheng */ 1441a963642SIcenowy Zheng for (i = 0; i < (size >> 2); i++) { 1451a963642SIcenowy Zheng ret = sun8i_sid_register_readout(sid, i); 1461a963642SIcenowy Zheng if (ret) 1471a963642SIcenowy Zheng return ret; 1481a963642SIcenowy Zheng } 1491a963642SIcenowy Zheng } 1501a963642SIcenowy Zheng 1514a72cda5SIcenowy Zheng econfig.size = size; 1523d0b16a6SMaxime Ripard econfig.dev = dev; 1539c7b16ebSSrinivas Kandagatla econfig.reg_read = sunxi_sid_read; 1549c7b16ebSSrinivas Kandagatla econfig.priv = sid; 1553d0b16a6SMaxime Ripard nvmem = nvmem_register(&econfig); 1563d0b16a6SMaxime Ripard if (IS_ERR(nvmem)) 1573d0b16a6SMaxime Ripard return PTR_ERR(nvmem); 1583d0b16a6SMaxime Ripard 159d16abd30SCaesar Wang randomness = kzalloc(sizeof(u8) * (size), GFP_KERNEL); 160fb727077SMaxime Ripard if (!randomness) { 161fb727077SMaxime Ripard ret = -EINVAL; 162fb727077SMaxime Ripard goto err_unreg_nvmem; 163fb727077SMaxime Ripard } 164fb727077SMaxime Ripard 1653d0b16a6SMaxime Ripard for (i = 0; i < size; i++) 1663d0b16a6SMaxime Ripard randomness[i] = sunxi_sid_read_byte(sid, i); 1673d0b16a6SMaxime Ripard 1683d0b16a6SMaxime Ripard add_device_randomness(randomness, size); 1693d0b16a6SMaxime Ripard kfree(randomness); 1703d0b16a6SMaxime Ripard 1713d0b16a6SMaxime Ripard platform_set_drvdata(pdev, nvmem); 1723d0b16a6SMaxime Ripard 1733d0b16a6SMaxime Ripard return 0; 174fb727077SMaxime Ripard 175fb727077SMaxime Ripard err_unreg_nvmem: 176fb727077SMaxime Ripard nvmem_unregister(nvmem); 177fb727077SMaxime Ripard return ret; 1783d0b16a6SMaxime Ripard } 1793d0b16a6SMaxime Ripard 1803d0b16a6SMaxime Ripard static int sunxi_sid_remove(struct platform_device *pdev) 1813d0b16a6SMaxime Ripard { 1823d0b16a6SMaxime Ripard struct nvmem_device *nvmem = platform_get_drvdata(pdev); 1833d0b16a6SMaxime Ripard 1843d0b16a6SMaxime Ripard return nvmem_unregister(nvmem); 1853d0b16a6SMaxime Ripard } 1863d0b16a6SMaxime Ripard 1874a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun4i_a10_cfg = { 1884a72cda5SIcenowy Zheng .size = 0x10, 1894a72cda5SIcenowy Zheng }; 1904a72cda5SIcenowy Zheng 1914a72cda5SIcenowy Zheng static const struct sunxi_sid_cfg sun7i_a20_cfg = { 1924a72cda5SIcenowy Zheng .size = 0x200, 1934a72cda5SIcenowy Zheng }; 1944a72cda5SIcenowy Zheng 1951a963642SIcenowy Zheng static const struct sunxi_sid_cfg sun8i_h3_cfg = { 1961a963642SIcenowy Zheng .value_offset = 0x200, 1971a963642SIcenowy Zheng .size = 0x100, 1981a963642SIcenowy Zheng .need_register_readout = true, 1991a963642SIcenowy Zheng }; 2001a963642SIcenowy Zheng 201*b7fe57b8SIcenowy Zheng static const struct sunxi_sid_cfg sun50i_a64_cfg = { 202*b7fe57b8SIcenowy Zheng .value_offset = 0x200, 203*b7fe57b8SIcenowy Zheng .size = 0x100, 204*b7fe57b8SIcenowy Zheng }; 205*b7fe57b8SIcenowy Zheng 2063d0b16a6SMaxime Ripard static const struct of_device_id sunxi_sid_of_match[] = { 2074a72cda5SIcenowy Zheng { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 2084a72cda5SIcenowy Zheng { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 2091a963642SIcenowy Zheng { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 210*b7fe57b8SIcenowy Zheng { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 2113d0b16a6SMaxime Ripard {/* sentinel */}, 2123d0b16a6SMaxime Ripard }; 2133d0b16a6SMaxime Ripard MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 2143d0b16a6SMaxime Ripard 2153d0b16a6SMaxime Ripard static struct platform_driver sunxi_sid_driver = { 2163d0b16a6SMaxime Ripard .probe = sunxi_sid_probe, 2173d0b16a6SMaxime Ripard .remove = sunxi_sid_remove, 2183d0b16a6SMaxime Ripard .driver = { 2193d0b16a6SMaxime Ripard .name = "eeprom-sunxi-sid", 2203d0b16a6SMaxime Ripard .of_match_table = sunxi_sid_of_match, 2213d0b16a6SMaxime Ripard }, 2223d0b16a6SMaxime Ripard }; 2233d0b16a6SMaxime Ripard module_platform_driver(sunxi_sid_driver); 2243d0b16a6SMaxime Ripard 2253d0b16a6SMaxime Ripard MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 2263d0b16a6SMaxime Ripard MODULE_DESCRIPTION("Allwinner sunxi security id driver"); 2273d0b16a6SMaxime Ripard MODULE_LICENSE("GPL"); 228