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 offset
,
94 /* Set word, lock access, and set read command */
95 reg_val
= (offset
& SUN8I_SID_OFFSET_MASK
)
96 << SUN8I_SID_OFFSET_SHIFT
;
97 reg_val
|= SUN8I_SID_OP_LOCK
| SUN8I_SID_READ
;
98 writel(reg_val
, sid
->base
+ SUN8I_SID_PRCTL
);
100 ret
= readl_poll_timeout(sid
->base
+ SUN8I_SID_PRCTL
, reg_val
,
101 !(reg_val
& SUN8I_SID_READ
), 100, 250000);
106 *out
= readl(sid
->base
+ SUN8I_SID_RDKEY
);
108 writel(0, sid
->base
+ SUN8I_SID_PRCTL
);
114 * On Allwinner H3, the value on the 0x200 offset of the SID controller seems
115 * to be not reliable at all.
116 * Read by the registers instead.
118 static int sun8i_sid_read_byte_by_reg(const struct sunxi_sid
*sid
,
119 const unsigned int offset
,
125 ret
= sun8i_sid_register_readout(sid
, offset
& ~0x03, &word
);
130 *out
= (word
>> ((offset
& 0x3) * 8)) & 0xff;
135 static int sun8i_sid_read_by_reg(void *context
, unsigned int offset
,
136 void *val
, size_t bytes
)
138 struct sunxi_sid
*sid
= context
;
143 ret
= sun8i_sid_read_byte_by_reg(sid
, offset
++, buf
++);
151 static int sunxi_sid_probe(struct platform_device
*pdev
)
153 struct device
*dev
= &pdev
->dev
;
154 struct resource
*res
;
155 struct nvmem_device
*nvmem
;
156 struct sunxi_sid
*sid
;
159 const struct sunxi_sid_cfg
*cfg
;
161 sid
= devm_kzalloc(dev
, sizeof(*sid
), GFP_KERNEL
);
165 cfg
= of_device_get_match_data(dev
);
168 sid
->value_offset
= cfg
->value_offset
;
170 res
= platform_get_resource(pdev
, IORESOURCE_MEM
, 0);
171 sid
->base
= devm_ioremap_resource(dev
, res
);
172 if (IS_ERR(sid
->base
))
173 return PTR_ERR(sid
->base
);
179 if (cfg
->need_register_readout
)
180 econfig
.reg_read
= sun8i_sid_read_by_reg
;
182 econfig
.reg_read
= sunxi_sid_read
;
184 nvmem
= nvmem_register(&econfig
);
186 return PTR_ERR(nvmem
);
188 randomness
= kzalloc(size
, GFP_KERNEL
);
191 goto err_unreg_nvmem
;
194 for (i
= 0; i
< size
; i
++)
195 econfig
.reg_read(sid
, i
, &randomness
[i
], 1);
197 add_device_randomness(randomness
, size
);
200 platform_set_drvdata(pdev
, nvmem
);
205 nvmem_unregister(nvmem
);
209 static int sunxi_sid_remove(struct platform_device
*pdev
)
211 struct nvmem_device
*nvmem
= platform_get_drvdata(pdev
);
213 return nvmem_unregister(nvmem
);
216 static const struct sunxi_sid_cfg sun4i_a10_cfg
= {
220 static const struct sunxi_sid_cfg sun7i_a20_cfg
= {
224 static const struct sunxi_sid_cfg sun8i_h3_cfg
= {
225 .value_offset
= 0x200,
227 .need_register_readout
= true,
230 static const struct sunxi_sid_cfg sun50i_a64_cfg
= {
231 .value_offset
= 0x200,
235 static const struct of_device_id sunxi_sid_of_match
[] = {
236 { .compatible
= "allwinner,sun4i-a10-sid", .data
= &sun4i_a10_cfg
},
237 { .compatible
= "allwinner,sun7i-a20-sid", .data
= &sun7i_a20_cfg
},
238 { .compatible
= "allwinner,sun8i-h3-sid", .data
= &sun8i_h3_cfg
},
239 { .compatible
= "allwinner,sun50i-a64-sid", .data
= &sun50i_a64_cfg
},
242 MODULE_DEVICE_TABLE(of
, sunxi_sid_of_match
);
244 static struct platform_driver sunxi_sid_driver
= {
245 .probe
= sunxi_sid_probe
,
246 .remove
= sunxi_sid_remove
,
248 .name
= "eeprom-sunxi-sid",
249 .of_match_table
= sunxi_sid_of_match
,
252 module_platform_driver(sunxi_sid_driver
);
254 MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>");
255 MODULE_DESCRIPTION("Allwinner sunxi security id driver");
256 MODULE_LICENSE("GPL");