0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <linux/device.h>
0010 #include <linux/io.h>
0011 #include <linux/iopoll.h>
0012 #include <linux/module.h>
0013 #include <linux/nvmem-provider.h>
0014 #include <linux/of.h>
0015 #include <linux/of_device.h>
0016 #include <linux/platform_device.h>
0017 #include <linux/slab.h>
0018 #include <linux/random.h>
0019
0020
0021 #define SUN8I_SID_PRCTL 0x40
0022 #define SUN8I_SID_RDKEY 0x60
0023
0024 #define SUN8I_SID_OFFSET_MASK 0x1FF
0025 #define SUN8I_SID_OFFSET_SHIFT 16
0026 #define SUN8I_SID_OP_LOCK (0xAC << 8)
0027 #define SUN8I_SID_READ BIT(1)
0028
0029 struct sunxi_sid_cfg {
0030 u32 value_offset;
0031 u32 size;
0032 bool need_register_readout;
0033 };
0034
0035 struct sunxi_sid {
0036 void __iomem *base;
0037 u32 value_offset;
0038 };
0039
0040 static int sunxi_sid_read(void *context, unsigned int offset,
0041 void *val, size_t bytes)
0042 {
0043 struct sunxi_sid *sid = context;
0044
0045 memcpy_fromio(val, sid->base + sid->value_offset + offset, bytes);
0046
0047 return 0;
0048 }
0049
0050 static int sun8i_sid_register_readout(const struct sunxi_sid *sid,
0051 const unsigned int offset,
0052 u32 *out)
0053 {
0054 u32 reg_val;
0055 int ret;
0056
0057
0058 reg_val = (offset & SUN8I_SID_OFFSET_MASK)
0059 << SUN8I_SID_OFFSET_SHIFT;
0060 reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ;
0061 writel(reg_val, sid->base + SUN8I_SID_PRCTL);
0062
0063 ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val,
0064 !(reg_val & SUN8I_SID_READ), 100, 250000);
0065 if (ret)
0066 return ret;
0067
0068 if (out)
0069 *out = readl(sid->base + SUN8I_SID_RDKEY);
0070
0071 writel(0, sid->base + SUN8I_SID_PRCTL);
0072
0073 return 0;
0074 }
0075
0076
0077
0078
0079
0080
0081 static int sun8i_sid_read_by_reg(void *context, unsigned int offset,
0082 void *val, size_t bytes)
0083 {
0084 struct sunxi_sid *sid = context;
0085 u32 word;
0086 int ret;
0087
0088
0089 while (bytes >= 4) {
0090 ret = sun8i_sid_register_readout(sid, offset, val);
0091 if (ret)
0092 return ret;
0093
0094 val += 4;
0095 offset += 4;
0096 bytes -= 4;
0097 }
0098
0099 if (!bytes)
0100 return 0;
0101
0102
0103 ret = sun8i_sid_register_readout(sid, offset, &word);
0104 if (ret)
0105 return ret;
0106
0107 memcpy(val, &word, bytes);
0108
0109 return 0;
0110 }
0111
0112 static int sunxi_sid_probe(struct platform_device *pdev)
0113 {
0114 struct device *dev = &pdev->dev;
0115 struct resource *res;
0116 struct nvmem_config *nvmem_cfg;
0117 struct nvmem_device *nvmem;
0118 struct sunxi_sid *sid;
0119 int size;
0120 char *randomness;
0121 const struct sunxi_sid_cfg *cfg;
0122
0123 sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL);
0124 if (!sid)
0125 return -ENOMEM;
0126
0127 cfg = of_device_get_match_data(dev);
0128 if (!cfg)
0129 return -EINVAL;
0130 sid->value_offset = cfg->value_offset;
0131
0132 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
0133 sid->base = devm_ioremap_resource(dev, res);
0134 if (IS_ERR(sid->base))
0135 return PTR_ERR(sid->base);
0136
0137 size = cfg->size;
0138
0139 nvmem_cfg = devm_kzalloc(dev, sizeof(*nvmem_cfg), GFP_KERNEL);
0140 if (!nvmem_cfg)
0141 return -ENOMEM;
0142
0143 nvmem_cfg->dev = dev;
0144 nvmem_cfg->name = "sunxi-sid";
0145 nvmem_cfg->type = NVMEM_TYPE_OTP;
0146 nvmem_cfg->read_only = true;
0147 nvmem_cfg->size = cfg->size;
0148 nvmem_cfg->word_size = 1;
0149 nvmem_cfg->stride = 4;
0150 nvmem_cfg->priv = sid;
0151 if (cfg->need_register_readout)
0152 nvmem_cfg->reg_read = sun8i_sid_read_by_reg;
0153 else
0154 nvmem_cfg->reg_read = sunxi_sid_read;
0155
0156 nvmem = devm_nvmem_register(dev, nvmem_cfg);
0157 if (IS_ERR(nvmem))
0158 return PTR_ERR(nvmem);
0159
0160 randomness = kzalloc(size, GFP_KERNEL);
0161 if (!randomness)
0162 return -ENOMEM;
0163
0164 nvmem_cfg->reg_read(sid, 0, randomness, size);
0165 add_device_randomness(randomness, size);
0166 kfree(randomness);
0167
0168 platform_set_drvdata(pdev, nvmem);
0169
0170 return 0;
0171 }
0172
0173 static const struct sunxi_sid_cfg sun4i_a10_cfg = {
0174 .size = 0x10,
0175 };
0176
0177 static const struct sunxi_sid_cfg sun7i_a20_cfg = {
0178 .size = 0x200,
0179 };
0180
0181 static const struct sunxi_sid_cfg sun8i_h3_cfg = {
0182 .value_offset = 0x200,
0183 .size = 0x100,
0184 .need_register_readout = true,
0185 };
0186
0187 static const struct sunxi_sid_cfg sun20i_d1_cfg = {
0188 .value_offset = 0x200,
0189 .size = 0x100,
0190 };
0191
0192 static const struct sunxi_sid_cfg sun50i_a64_cfg = {
0193 .value_offset = 0x200,
0194 .size = 0x100,
0195 .need_register_readout = true,
0196 };
0197
0198 static const struct sunxi_sid_cfg sun50i_h6_cfg = {
0199 .value_offset = 0x200,
0200 .size = 0x200,
0201 };
0202
0203 static const struct of_device_id sunxi_sid_of_match[] = {
0204 { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg },
0205 { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg },
0206 { .compatible = "allwinner,sun8i-a83t-sid", .data = &sun50i_a64_cfg },
0207 { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg },
0208 { .compatible = "allwinner,sun20i-d1-sid", .data = &sun20i_d1_cfg },
0209 { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg },
0210 { .compatible = "allwinner,sun50i-h5-sid", .data = &sun50i_a64_cfg },
0211 { .compatible = "allwinner,sun50i-h6-sid", .data = &sun50i_h6_cfg },
0212 {},
0213 };
0214 MODULE_DEVICE_TABLE(of, sunxi_sid_of_match);
0215
0216 static struct platform_driver sunxi_sid_driver = {
0217 .probe = sunxi_sid_probe,
0218 .driver = {
0219 .name = "eeprom-sunxi-sid",
0220 .of_match_table = sunxi_sid_of_match,
0221 },
0222 };
0223 module_platform_driver(sunxi_sid_driver);
0224
0225 MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
0226 MODULE_DESCRIPTION("Allwinner sunxi security id driver");
0227 MODULE_LICENSE("GPL");