162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Allwinner sunXi SoCs Security ID support. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> 662306a36Sopenharmony_ci * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/device.h> 1062306a36Sopenharmony_ci#include <linux/io.h> 1162306a36Sopenharmony_ci#include <linux/iopoll.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/nvmem-provider.h> 1462306a36Sopenharmony_ci#include <linux/of.h> 1562306a36Sopenharmony_ci#include <linux/platform_device.h> 1662306a36Sopenharmony_ci#include <linux/slab.h> 1762306a36Sopenharmony_ci#include <linux/random.h> 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* Registers and special values for doing register-based SID readout on H3 */ 2062306a36Sopenharmony_ci#define SUN8I_SID_PRCTL 0x40 2162306a36Sopenharmony_ci#define SUN8I_SID_RDKEY 0x60 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#define SUN8I_SID_OFFSET_MASK 0x1FF 2462306a36Sopenharmony_ci#define SUN8I_SID_OFFSET_SHIFT 16 2562306a36Sopenharmony_ci#define SUN8I_SID_OP_LOCK (0xAC << 8) 2662306a36Sopenharmony_ci#define SUN8I_SID_READ BIT(1) 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct sunxi_sid_cfg { 2962306a36Sopenharmony_ci u32 value_offset; 3062306a36Sopenharmony_ci u32 size; 3162306a36Sopenharmony_ci bool need_register_readout; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct sunxi_sid { 3562306a36Sopenharmony_ci void __iomem *base; 3662306a36Sopenharmony_ci u32 value_offset; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistatic int sunxi_sid_read(void *context, unsigned int offset, 4062306a36Sopenharmony_ci void *val, size_t bytes) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci struct sunxi_sid *sid = context; 4362306a36Sopenharmony_ci u32 word; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* .stride = 4 so offset is guaranteed to be aligned */ 4662306a36Sopenharmony_ci __ioread32_copy(val, sid->base + sid->value_offset + offset, bytes / 4); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci val += round_down(bytes, 4); 4962306a36Sopenharmony_ci offset += round_down(bytes, 4); 5062306a36Sopenharmony_ci bytes = bytes % 4; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci if (!bytes) 5362306a36Sopenharmony_ci return 0; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci /* Handle any trailing bytes */ 5662306a36Sopenharmony_ci word = readl_relaxed(sid->base + sid->value_offset + offset); 5762306a36Sopenharmony_ci memcpy(val, &word, bytes); 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci return 0; 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic int sun8i_sid_register_readout(const struct sunxi_sid *sid, 6362306a36Sopenharmony_ci const unsigned int offset, 6462306a36Sopenharmony_ci u32 *out) 6562306a36Sopenharmony_ci{ 6662306a36Sopenharmony_ci u32 reg_val; 6762306a36Sopenharmony_ci int ret; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ci /* Set word, lock access, and set read command */ 7062306a36Sopenharmony_ci reg_val = (offset & SUN8I_SID_OFFSET_MASK) 7162306a36Sopenharmony_ci << SUN8I_SID_OFFSET_SHIFT; 7262306a36Sopenharmony_ci reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; 7362306a36Sopenharmony_ci writel(reg_val, sid->base + SUN8I_SID_PRCTL); 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, 7662306a36Sopenharmony_ci !(reg_val & SUN8I_SID_READ), 100, 250000); 7762306a36Sopenharmony_ci if (ret) 7862306a36Sopenharmony_ci return ret; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci if (out) 8162306a36Sopenharmony_ci *out = readl(sid->base + SUN8I_SID_RDKEY); 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_ci writel(0, sid->base + SUN8I_SID_PRCTL); 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci return 0; 8662306a36Sopenharmony_ci} 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci/* 8962306a36Sopenharmony_ci * On Allwinner H3, the value on the 0x200 offset of the SID controller seems 9062306a36Sopenharmony_ci * to be not reliable at all. 9162306a36Sopenharmony_ci * Read by the registers instead. 9262306a36Sopenharmony_ci */ 9362306a36Sopenharmony_cistatic int sun8i_sid_read_by_reg(void *context, unsigned int offset, 9462306a36Sopenharmony_ci void *val, size_t bytes) 9562306a36Sopenharmony_ci{ 9662306a36Sopenharmony_ci struct sunxi_sid *sid = context; 9762306a36Sopenharmony_ci u32 word; 9862306a36Sopenharmony_ci int ret; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci /* .stride = 4 so offset is guaranteed to be aligned */ 10162306a36Sopenharmony_ci while (bytes >= 4) { 10262306a36Sopenharmony_ci ret = sun8i_sid_register_readout(sid, offset, val); 10362306a36Sopenharmony_ci if (ret) 10462306a36Sopenharmony_ci return ret; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci val += 4; 10762306a36Sopenharmony_ci offset += 4; 10862306a36Sopenharmony_ci bytes -= 4; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci if (!bytes) 11262306a36Sopenharmony_ci return 0; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci /* Handle any trailing bytes */ 11562306a36Sopenharmony_ci ret = sun8i_sid_register_readout(sid, offset, &word); 11662306a36Sopenharmony_ci if (ret) 11762306a36Sopenharmony_ci return ret; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci memcpy(val, &word, bytes); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci return 0; 12262306a36Sopenharmony_ci} 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic int sunxi_sid_probe(struct platform_device *pdev) 12562306a36Sopenharmony_ci{ 12662306a36Sopenharmony_ci struct device *dev = &pdev->dev; 12762306a36Sopenharmony_ci struct nvmem_config *nvmem_cfg; 12862306a36Sopenharmony_ci struct nvmem_device *nvmem; 12962306a36Sopenharmony_ci struct sunxi_sid *sid; 13062306a36Sopenharmony_ci int size; 13162306a36Sopenharmony_ci char *randomness; 13262306a36Sopenharmony_ci const struct sunxi_sid_cfg *cfg; 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); 13562306a36Sopenharmony_ci if (!sid) 13662306a36Sopenharmony_ci return -ENOMEM; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci cfg = of_device_get_match_data(dev); 13962306a36Sopenharmony_ci if (!cfg) 14062306a36Sopenharmony_ci return -EINVAL; 14162306a36Sopenharmony_ci sid->value_offset = cfg->value_offset; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci sid->base = devm_platform_ioremap_resource(pdev, 0); 14462306a36Sopenharmony_ci if (IS_ERR(sid->base)) 14562306a36Sopenharmony_ci return PTR_ERR(sid->base); 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci size = cfg->size; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL); 15062306a36Sopenharmony_ci if (!nvmem_cfg) 15162306a36Sopenharmony_ci return -ENOMEM; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci nvmem_cfg->dev = dev; 15462306a36Sopenharmony_ci nvmem_cfg->name = "sunxi-sid"; 15562306a36Sopenharmony_ci nvmem_cfg->type = NVMEM_TYPE_OTP; 15662306a36Sopenharmony_ci nvmem_cfg->read_only = true; 15762306a36Sopenharmony_ci nvmem_cfg->size = cfg->size; 15862306a36Sopenharmony_ci nvmem_cfg->word_size = 1; 15962306a36Sopenharmony_ci nvmem_cfg->stride = 4; 16062306a36Sopenharmony_ci nvmem_cfg->priv = sid; 16162306a36Sopenharmony_ci if (cfg->need_register_readout) 16262306a36Sopenharmony_ci nvmem_cfg->reg_read = sun8i_sid_read_by_reg; 16362306a36Sopenharmony_ci else 16462306a36Sopenharmony_ci nvmem_cfg->reg_read = sunxi_sid_read; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci nvmem = devm_nvmem_register(dev, nvmem_cfg); 16762306a36Sopenharmony_ci if (IS_ERR(nvmem)) 16862306a36Sopenharmony_ci return PTR_ERR(nvmem); 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci randomness = kzalloc(size, GFP_KERNEL); 17162306a36Sopenharmony_ci if (!randomness) 17262306a36Sopenharmony_ci return -ENOMEM; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci nvmem_cfg->reg_read(sid, 0, randomness, size); 17562306a36Sopenharmony_ci add_device_randomness(randomness, size); 17662306a36Sopenharmony_ci kfree(randomness); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci platform_set_drvdata(pdev, nvmem); 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci return 0; 18162306a36Sopenharmony_ci} 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_cistatic const struct sunxi_sid_cfg sun4i_a10_cfg = { 18462306a36Sopenharmony_ci .size = 0x10, 18562306a36Sopenharmony_ci}; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_cistatic const struct sunxi_sid_cfg sun7i_a20_cfg = { 18862306a36Sopenharmony_ci .size = 0x200, 18962306a36Sopenharmony_ci}; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_cistatic const struct sunxi_sid_cfg sun8i_h3_cfg = { 19262306a36Sopenharmony_ci .value_offset = 0x200, 19362306a36Sopenharmony_ci .size = 0x100, 19462306a36Sopenharmony_ci .need_register_readout = true, 19562306a36Sopenharmony_ci}; 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_cistatic const struct sunxi_sid_cfg sun50i_a64_cfg = { 19862306a36Sopenharmony_ci .value_offset = 0x200, 19962306a36Sopenharmony_ci .size = 0x100, 20062306a36Sopenharmony_ci}; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_cistatic const struct sunxi_sid_cfg sun50i_h6_cfg = { 20362306a36Sopenharmony_ci .value_offset = 0x200, 20462306a36Sopenharmony_ci .size = 0x200, 20562306a36Sopenharmony_ci}; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic const struct of_device_id sunxi_sid_of_match[] = { 20862306a36Sopenharmony_ci { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, 20962306a36Sopenharmony_ci { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, 21062306a36Sopenharmony_ci { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg }, 21162306a36Sopenharmony_ci { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, 21262306a36Sopenharmony_ci { .compatible = "allwinner,sun20i-d1-sid", .data = &sun50i_a64_cfg }, 21362306a36Sopenharmony_ci { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, 21462306a36Sopenharmony_ci { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg }, 21562306a36Sopenharmony_ci { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg }, 21662306a36Sopenharmony_ci {/* sentinel */}, 21762306a36Sopenharmony_ci}; 21862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sunxi_sid_of_match); 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_cistatic struct platform_driver sunxi_sid_driver = { 22162306a36Sopenharmony_ci .probe = sunxi_sid_probe, 22262306a36Sopenharmony_ci .driver = { 22362306a36Sopenharmony_ci .name = "eeprom-sunxi-sid", 22462306a36Sopenharmony_ci .of_match_table = sunxi_sid_of_match, 22562306a36Sopenharmony_ci }, 22662306a36Sopenharmony_ci}; 22762306a36Sopenharmony_cimodule_platform_driver(sunxi_sid_driver); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ciMODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); 23062306a36Sopenharmony_ciMODULE_DESCRIPTION("Allwinner sunxi security id driver"); 23162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 232