2 * Allwinner sunXi SoCs Security ID support.
4 * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl>
5 * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
18 #include <linux/device.h>
20 #include <linux/iopoll.h>
21 #include <linux/module.h>
22 #include <linux/nvmem-provider.h>
24 #include <linux/of_device.h>
25 #include <linux/platform_device.h>
26 #include <linux/slab.h>
27 #include <linux/random.h>
29 /* Registers and special values for doing register-based SID readout on H3 */
30 #define SUN8I_SID_PRCTL 0x40
31 #define SUN8I_SID_RDKEY 0x60
33 #define SUN8I_SID_OFFSET_MASK 0x1FF
34 #define SUN8I_SID_OFFSET_SHIFT 16
35 #define SUN8I_SID_OP_LOCK (0xAC << 8)
36 #define SUN8I_SID_READ BIT(1)
38 static struct nvmem_config econfig
= {
45 struct sunxi_sid_cfg
{
48 bool need_register_readout
;
56 /* We read the entire key, due to a 32 bit read alignment requirement. Since we
57 * want to return the requested byte, this results in somewhat slower code and
58 * uses 4 times more reads as needed but keeps code simpler. Since the SID is
59 * only very rarely probed, this is not really an issue.
61 static u8
sunxi_sid_read_byte(const struct sunxi_sid
*sid
,
62 const unsigned int offset
)
66 sid_key
= ioread32be(sid
->base
+ round_down(offset
, 4));
67 sid_key
>>= (offset
% 4) * 8;
69 return sid_key
; /* Only return the last byte */
72 static int sunxi_sid_read(void *context
, unsigned int offset
,
73 void *val
, size_t bytes
)
75 struct sunxi_sid
*sid
= context
;
78 /* Offset the read operation to the real position of SID */
79 offset
+= sid
->value_offset
;
82 *buf
++ = sunxi_sid_read_byte(sid
, offset
++);
87 static int sun8i_sid_register_readout(const struct sunxi_sid
*sid
,
88 const unsigned int word
)
93 /* Set word, lock access, and set read command */
94 reg_val
= (word
& SUN8I_SID_OFFSET_MASK
)
95 << SUN8I_SID_OFFSET_SHIFT
;
96 reg_val
|= SUN8I_SID_OP_LOCK
| SUN8I_SID_READ
;
97 writel(reg_val
, sid
->base
+ SUN8I_SID_PRCTL
);
99 ret
= readl_poll_timeout(sid
->base
+ SUN8I_SID_PRCTL
, reg_val
,
100 !(reg_val
& SUN8I_SID_READ
), 100, 250000);
104 writel(0, sid
->base
+ SUN8I_SID_PRCTL
);
108 static int sunxi_sid_probe(struct platform_device
*pdev
)
110 struct device
*dev
= &pdev
->dev
;
111 struct resource
*res
;
112 struct nvmem_device
*nvmem
;
113 struct sunxi_sid
*sid
;
116 const struct sunxi_sid_cfg
*cfg
;
118 sid
= devm_kzalloc(dev
, sizeof(*sid
), GFP_KERNEL
);
122 cfg
= of_device_get_match_data(dev
);
125 sid
->value_offset
= cfg
->value_offset
;
127 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
128 sid
->base
= devm_ioremap_resource(dev
, res
);
129 if (IS_ERR(sid
->base
))
130 return PTR_ERR(sid
->base
);
134 if (cfg
->need_register_readout
) {
136 * H3's SID controller have a bug that the value at 0x200
137 * offset is not the correct value when the hardware is reseted.
138 * However, after doing a register-based read operation, the
139 * value become right.
140 * Do a full read operation here, but ignore its value
141 * (as it's more fast to read by direct MMIO value than
144 for (i
= 0; i
< (size
>> 2); i
++) {
145 ret
= sun8i_sid_register_readout(sid
, i
);
153 econfig
.reg_read
= sunxi_sid_read
;
155 nvmem
= nvmem_register(&econfig
);
157 return PTR_ERR(nvmem
);
159 randomness
= kzalloc(sizeof(u8
) * (size
), GFP_KERNEL
);
162 goto err_unreg_nvmem
;
165 for (i
= 0; i
< size
; i
++)
166 randomness
[i
] = sunxi_sid_read_byte(sid
, i
);
168 add_device_randomness(randomness
, size
);
171 platform_set_drvdata(pdev
, nvmem
);
176 nvmem_unregister(nvmem
);
180 static int sunxi_sid_remove(struct platform_device
*pdev
)
182 struct nvmem_device
*nvmem
= platform_get_drvdata(pdev
);
184 return nvmem_unregister(nvmem
);
187 static const struct sunxi_sid_cfg sun4i_a10_cfg
= {
191 static const struct sunxi_sid_cfg sun7i_a20_cfg
= {
195 static const struct sunxi_sid_cfg sun8i_h3_cfg
= {
196 .value_offset
= 0x200,
198 .need_register_readout
= true,
201 static const struct sunxi_sid_cfg sun50i_a64_cfg
= {
202 .value_offset
= 0x200,
206 static const struct of_device_id sunxi_sid_of_match
[] = {
207 { .compatible
= "allwinner,sun4i-a10-sid", .data
= &sun4i_a10_cfg
},
208 { .compatible
= "allwinner,sun7i-a20-sid", .data
= &sun7i_a20_cfg
},
209 { .compatible
= "allwinner,sun8i-h3-sid", .data
= &sun8i_h3_cfg
},
210 { .compatible
= "allwinner,sun50i-a64-sid", .data
= &sun50i_a64_cfg
},
213 MODULE_DEVICE_TABLE(of
, sunxi_sid_of_match
);
215 static struct platform_driver sunxi_sid_driver
= {
216 .probe
= sunxi_sid_probe
,
217 .remove
= sunxi_sid_remove
,
219 .name
= "eeprom-sunxi-sid",
220 .of_match_table
= sunxi_sid_of_match
,
223 module_platform_driver(sunxi_sid_driver
);
225 MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
226 MODULE_DESCRIPTION("Allwinner sunxi security id driver");
227 MODULE_LICENSE("GPL");