Commit | Line | Data |
---|---|---|
3d0b16a6 MR |
1 | /* |
2 | * Allwinner sunXi SoCs Security ID support. | |
3 | * | |
4 | * Copyright (c) 2013 Oliver Schinagl <oliver@schinagl.nl> | |
5 | * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com> | |
6 | * | |
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. | |
11 | * | |
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. | |
3d0b16a6 MR |
16 | */ |
17 | ||
3d0b16a6 MR |
18 | #include <linux/device.h> |
19 | #include <linux/io.h> | |
1a963642 | 20 | #include <linux/iopoll.h> |
3d0b16a6 MR |
21 | #include <linux/module.h> |
22 | #include <linux/nvmem-provider.h> | |
23 | #include <linux/of.h> | |
4a72cda5 | 24 | #include <linux/of_device.h> |
3d0b16a6 | 25 | #include <linux/platform_device.h> |
3d0b16a6 MR |
26 | #include <linux/slab.h> |
27 | #include <linux/random.h> | |
28 | ||
1a963642 IZ |
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 | |
32 | ||
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) | |
37 | ||
3d0b16a6 MR |
38 | static struct nvmem_config econfig = { |
39 | .name = "sunxi-sid", | |
40 | .read_only = true, | |
9c7b16eb SK |
41 | .stride = 4, |
42 | .word_size = 1, | |
3d0b16a6 MR |
43 | }; |
44 | ||
4a72cda5 | 45 | struct sunxi_sid_cfg { |
1a963642 | 46 | u32 value_offset; |
4a72cda5 | 47 | u32 size; |
1a963642 | 48 | bool need_register_readout; |
4a72cda5 IZ |
49 | }; |
50 | ||
3d0b16a6 MR |
51 | struct sunxi_sid { |
52 | void __iomem *base; | |
1a963642 | 53 | u32 value_offset; |
3d0b16a6 MR |
54 | }; |
55 | ||
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. | |
60 | */ | |
61 | static u8 sunxi_sid_read_byte(const struct sunxi_sid *sid, | |
62 | const unsigned int offset) | |
63 | { | |
64 | u32 sid_key; | |
65 | ||
66 | sid_key = ioread32be(sid->base + round_down(offset, 4)); | |
67 | sid_key >>= (offset % 4) * 8; | |
68 | ||
69 | return sid_key; /* Only return the last byte */ | |
70 | } | |
71 | ||
9c7b16eb SK |
72 | static int sunxi_sid_read(void *context, unsigned int offset, |
73 | void *val, size_t bytes) | |
3d0b16a6 MR |
74 | { |
75 | struct sunxi_sid *sid = context; | |
3d0b16a6 MR |
76 | u8 *buf = val; |
77 | ||
1a963642 IZ |
78 | /* Offset the read operation to the real position of SID */ |
79 | offset += sid->value_offset; | |
80 | ||
9c7b16eb SK |
81 | while (bytes--) |
82 | *buf++ = sunxi_sid_read_byte(sid, offset++); | |
3d0b16a6 MR |
83 | |
84 | return 0; | |
85 | } | |
86 | ||
1a963642 | 87 | static int sun8i_sid_register_readout(const struct sunxi_sid *sid, |
0ab09d65 IZ |
88 | const unsigned int offset, |
89 | u32 *out) | |
1a963642 IZ |
90 | { |
91 | u32 reg_val; | |
92 | int ret; | |
93 | ||
94 | /* Set word, lock access, and set read command */ | |
0ab09d65 | 95 | reg_val = (offset & SUN8I_SID_OFFSET_MASK) |
1a963642 IZ |
96 | << SUN8I_SID_OFFSET_SHIFT; |
97 | reg_val |= SUN8I_SID_OP_LOCK | SUN8I_SID_READ; | |
98 | writel(reg_val, sid->base + SUN8I_SID_PRCTL); | |
99 | ||
100 | ret = readl_poll_timeout(sid->base + SUN8I_SID_PRCTL, reg_val, | |
101 | !(reg_val & SUN8I_SID_READ), 100, 250000); | |
102 | if (ret) | |
103 | return ret; | |
104 | ||
0ab09d65 IZ |
105 | if (out) |
106 | *out = readl(sid->base + SUN8I_SID_RDKEY); | |
107 | ||
1a963642 | 108 | writel(0, sid->base + SUN8I_SID_PRCTL); |
0ab09d65 IZ |
109 | |
110 | return 0; | |
111 | } | |
112 | ||
113 | /* | |
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. | |
117 | */ | |
118 | static int sun8i_sid_read_byte_by_reg(const struct sunxi_sid *sid, | |
119 | const unsigned int offset, | |
120 | u8 *out) | |
121 | { | |
122 | u32 word; | |
123 | int ret; | |
124 | ||
125 | ret = sun8i_sid_register_readout(sid, offset & ~0x03, &word); | |
126 | ||
127 | if (ret) | |
128 | return ret; | |
129 | ||
130 | *out = (word >> ((offset & 0x3) * 8)) & 0xff; | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | static int sun8i_sid_read_by_reg(void *context, unsigned int offset, | |
136 | void *val, size_t bytes) | |
137 | { | |
138 | struct sunxi_sid *sid = context; | |
139 | u8 *buf = val; | |
140 | int ret; | |
141 | ||
142 | while (bytes--) { | |
143 | ret = sun8i_sid_read_byte_by_reg(sid, offset++, buf++); | |
144 | if (ret) | |
145 | return ret; | |
146 | } | |
147 | ||
1a963642 IZ |
148 | return 0; |
149 | } | |
150 | ||
3d0b16a6 MR |
151 | static int sunxi_sid_probe(struct platform_device *pdev) |
152 | { | |
153 | struct device *dev = &pdev->dev; | |
154 | struct resource *res; | |
155 | struct nvmem_device *nvmem; | |
3d0b16a6 | 156 | struct sunxi_sid *sid; |
4876bfe6 | 157 | int i, size; |
3d0b16a6 | 158 | char *randomness; |
4a72cda5 | 159 | const struct sunxi_sid_cfg *cfg; |
3d0b16a6 MR |
160 | |
161 | sid = devm_kzalloc(dev, sizeof(*sid), GFP_KERNEL); | |
162 | if (!sid) | |
163 | return -ENOMEM; | |
164 | ||
4a72cda5 IZ |
165 | cfg = of_device_get_match_data(dev); |
166 | if (!cfg) | |
167 | return -EINVAL; | |
1a963642 | 168 | sid->value_offset = cfg->value_offset; |
4a72cda5 | 169 | |
3d0b16a6 MR |
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); | |
174 | ||
4a72cda5 IZ |
175 | size = cfg->size; |
176 | ||
177 | econfig.size = size; | |
3d0b16a6 | 178 | econfig.dev = dev; |
0ab09d65 IZ |
179 | if (cfg->need_register_readout) |
180 | econfig.reg_read = sun8i_sid_read_by_reg; | |
181 | else | |
182 | econfig.reg_read = sunxi_sid_read; | |
9c7b16eb | 183 | econfig.priv = sid; |
6eed8dd9 | 184 | nvmem = devm_nvmem_register(dev, &econfig); |
3d0b16a6 MR |
185 | if (IS_ERR(nvmem)) |
186 | return PTR_ERR(nvmem); | |
187 | ||
6396bb22 | 188 | randomness = kzalloc(size, GFP_KERNEL); |
6eed8dd9 BG |
189 | if (!randomness) |
190 | return -ENOMEM; | |
fb727077 | 191 | |
3d0b16a6 | 192 | for (i = 0; i < size; i++) |
0ab09d65 | 193 | econfig.reg_read(sid, i, &randomness[i], 1); |
3d0b16a6 MR |
194 | |
195 | add_device_randomness(randomness, size); | |
196 | kfree(randomness); | |
197 | ||
198 | platform_set_drvdata(pdev, nvmem); | |
199 | ||
200 | return 0; | |
3d0b16a6 MR |
201 | } |
202 | ||
4a72cda5 IZ |
203 | static const struct sunxi_sid_cfg sun4i_a10_cfg = { |
204 | .size = 0x10, | |
205 | }; | |
206 | ||
207 | static const struct sunxi_sid_cfg sun7i_a20_cfg = { | |
208 | .size = 0x200, | |
209 | }; | |
210 | ||
1a963642 IZ |
211 | static const struct sunxi_sid_cfg sun8i_h3_cfg = { |
212 | .value_offset = 0x200, | |
213 | .size = 0x100, | |
214 | .need_register_readout = true, | |
215 | }; | |
216 | ||
b7fe57b8 IZ |
217 | static const struct sunxi_sid_cfg sun50i_a64_cfg = { |
218 | .value_offset = 0x200, | |
219 | .size = 0x100, | |
220 | }; | |
221 | ||
3d0b16a6 | 222 | static const struct of_device_id sunxi_sid_of_match[] = { |
4a72cda5 IZ |
223 | { .compatible = "allwinner,sun4i-a10-sid", .data = &sun4i_a10_cfg }, |
224 | { .compatible = "allwinner,sun7i-a20-sid", .data = &sun7i_a20_cfg }, | |
1a963642 | 225 | { .compatible = "allwinner,sun8i-h3-sid", .data = &sun8i_h3_cfg }, |
b7fe57b8 | 226 | { .compatible = "allwinner,sun50i-a64-sid", .data = &sun50i_a64_cfg }, |
3d0b16a6 MR |
227 | {/* sentinel */}, |
228 | }; | |
229 | MODULE_DEVICE_TABLE(of, sunxi_sid_of_match); | |
230 | ||
231 | static struct platform_driver sunxi_sid_driver = { | |
232 | .probe = sunxi_sid_probe, | |
3d0b16a6 MR |
233 | .driver = { |
234 | .name = "eeprom-sunxi-sid", | |
235 | .of_match_table = sunxi_sid_of_match, | |
236 | }, | |
237 | }; | |
238 | module_platform_driver(sunxi_sid_driver); | |
239 | ||
240 | MODULE_AUTHOR("Oliver Schinagl <oliver@schinagl.nl>"); | |
241 | MODULE_DESCRIPTION("Allwinner sunxi security id driver"); | |
242 | MODULE_LICENSE("GPL"); |