]>
Commit | Line | Data |
---|---|---|
16216333 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
4984c6f5 PZ |
2 | /* |
3 | * Generic on-chip SRAM allocation driver | |
4 | * | |
5 | * Copyright (C) 2012 Philipp Zabel, Pengutronix | |
4984c6f5 PZ |
6 | */ |
7 | ||
4984c6f5 | 8 | #include <linux/clk.h> |
2ae2e288 | 9 | #include <linux/delay.h> |
98ce2d27 | 10 | #include <linux/genalloc.h> |
4984c6f5 | 11 | #include <linux/io.h> |
2da19688 | 12 | #include <linux/list_sort.h> |
98ce2d27 | 13 | #include <linux/of_address.h> |
2ae2e288 | 14 | #include <linux/of_device.h> |
4984c6f5 | 15 | #include <linux/platform_device.h> |
2ae2e288 | 16 | #include <linux/regmap.h> |
4984c6f5 | 17 | #include <linux/slab.h> |
2ae2e288 AB |
18 | #include <linux/mfd/syscon.h> |
19 | #include <soc/at91/atmel-secumod.h> | |
4984c6f5 | 20 | |
cdd1737c | 21 | #include "sram.h" |
665d82fb | 22 | |
cdd1737c | 23 | #define SRAM_GRANULARITY 32 |
2da19688 | 24 | |
b4c3fcb3 VZ |
25 | static ssize_t sram_read(struct file *filp, struct kobject *kobj, |
26 | struct bin_attribute *attr, | |
27 | char *buf, loff_t pos, size_t count) | |
28 | { | |
29 | struct sram_partition *part; | |
30 | ||
31 | part = container_of(attr, struct sram_partition, battr); | |
32 | ||
33 | mutex_lock(&part->lock); | |
525d12f2 | 34 | memcpy_fromio(buf, part->base + pos, count); |
b4c3fcb3 VZ |
35 | mutex_unlock(&part->lock); |
36 | ||
37 | return count; | |
38 | } | |
39 | ||
40 | static ssize_t sram_write(struct file *filp, struct kobject *kobj, | |
41 | struct bin_attribute *attr, | |
42 | char *buf, loff_t pos, size_t count) | |
43 | { | |
44 | struct sram_partition *part; | |
45 | ||
46 | part = container_of(attr, struct sram_partition, battr); | |
47 | ||
48 | mutex_lock(&part->lock); | |
525d12f2 | 49 | memcpy_toio(part->base + pos, buf, count); |
b4c3fcb3 VZ |
50 | mutex_unlock(&part->lock); |
51 | ||
52 | return count; | |
53 | } | |
54 | ||
55 | static int sram_add_pool(struct sram_dev *sram, struct sram_reserve *block, | |
56 | phys_addr_t start, struct sram_partition *part) | |
57 | { | |
58 | int ret; | |
59 | ||
60 | part->pool = devm_gen_pool_create(sram->dev, ilog2(SRAM_GRANULARITY), | |
61 | NUMA_NO_NODE, block->label); | |
62 | if (IS_ERR(part->pool)) | |
63 | return PTR_ERR(part->pool); | |
64 | ||
65 | ret = gen_pool_add_virt(part->pool, (unsigned long)part->base, start, | |
66 | block->size, NUMA_NO_NODE); | |
67 | if (ret < 0) { | |
68 | dev_err(sram->dev, "failed to register subpool: %d\n", ret); | |
69 | return ret; | |
70 | } | |
71 | ||
72 | return 0; | |
73 | } | |
74 | ||
75 | static int sram_add_export(struct sram_dev *sram, struct sram_reserve *block, | |
76 | phys_addr_t start, struct sram_partition *part) | |
77 | { | |
78 | sysfs_bin_attr_init(&part->battr); | |
79 | part->battr.attr.name = devm_kasprintf(sram->dev, GFP_KERNEL, | |
80 | "%llx.sram", | |
81 | (unsigned long long)start); | |
82 | if (!part->battr.attr.name) | |
83 | return -ENOMEM; | |
84 | ||
85 | part->battr.attr.mode = S_IRUSR | S_IWUSR; | |
86 | part->battr.read = sram_read; | |
87 | part->battr.write = sram_write; | |
88 | part->battr.size = block->size; | |
89 | ||
90 | return device_create_bin_file(sram->dev, &part->battr); | |
91 | } | |
92 | ||
93 | static int sram_add_partition(struct sram_dev *sram, struct sram_reserve *block, | |
94 | phys_addr_t start) | |
95 | { | |
96 | int ret; | |
97 | struct sram_partition *part = &sram->partition[sram->partitions]; | |
98 | ||
99 | mutex_init(&part->lock); | |
100 | part->base = sram->virt_base + block->start; | |
101 | ||
102 | if (block->pool) { | |
103 | ret = sram_add_pool(sram, block, start, part); | |
104 | if (ret) | |
105 | return ret; | |
106 | } | |
107 | if (block->export) { | |
108 | ret = sram_add_export(sram, block, start, part); | |
109 | if (ret) | |
110 | return ret; | |
111 | } | |
37afff0d DG |
112 | if (block->protect_exec) { |
113 | ret = sram_check_protect_exec(sram, block, part); | |
114 | if (ret) | |
115 | return ret; | |
116 | ||
117 | ret = sram_add_pool(sram, block, start, part); | |
118 | if (ret) | |
119 | return ret; | |
120 | ||
121 | sram_add_protect_exec(part); | |
122 | } | |
123 | ||
b4c3fcb3 VZ |
124 | sram->partitions++; |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | static void sram_free_partitions(struct sram_dev *sram) | |
130 | { | |
131 | struct sram_partition *part; | |
132 | ||
133 | if (!sram->partitions) | |
134 | return; | |
135 | ||
136 | part = &sram->partition[sram->partitions - 1]; | |
137 | for (; sram->partitions; sram->partitions--, part--) { | |
138 | if (part->battr.size) | |
139 | device_remove_bin_file(sram->dev, &part->battr); | |
140 | ||
141 | if (part->pool && | |
142 | gen_pool_avail(part->pool) < gen_pool_size(part->pool)) | |
143 | dev_err(sram->dev, "removed pool while SRAM allocated\n"); | |
144 | } | |
145 | } | |
146 | ||
2da19688 HS |
147 | static int sram_reserve_cmp(void *priv, struct list_head *a, |
148 | struct list_head *b) | |
149 | { | |
150 | struct sram_reserve *ra = list_entry(a, struct sram_reserve, list); | |
151 | struct sram_reserve *rb = list_entry(b, struct sram_reserve, list); | |
152 | ||
153 | return ra->start - rb->start; | |
154 | } | |
155 | ||
a0a5be0b | 156 | static int sram_reserve_regions(struct sram_dev *sram, struct resource *res) |
4984c6f5 | 157 | { |
a0a5be0b | 158 | struct device_node *np = sram->dev->of_node, *child; |
2da19688 HS |
159 | unsigned long size, cur_start, cur_size; |
160 | struct sram_reserve *rblocks, *block; | |
161 | struct list_head reserve_list; | |
b4c3fcb3 VZ |
162 | unsigned int nblocks, exports = 0; |
163 | const char *label; | |
a0a5be0b | 164 | int ret = 0; |
4984c6f5 | 165 | |
2da19688 HS |
166 | INIT_LIST_HEAD(&reserve_list); |
167 | ||
f3cbfa5d | 168 | size = resource_size(res); |
4984c6f5 | 169 | |
2da19688 HS |
170 | /* |
171 | * We need an additional block to mark the end of the memory region | |
172 | * after the reserved blocks from the dt are processed. | |
173 | */ | |
174 | nblocks = (np) ? of_get_available_child_count(np) + 1 : 1; | |
6396bb22 | 175 | rblocks = kcalloc(nblocks, sizeof(*rblocks), GFP_KERNEL); |
ee895ccd VZ |
176 | if (!rblocks) |
177 | return -ENOMEM; | |
4984c6f5 | 178 | |
2da19688 HS |
179 | block = &rblocks[0]; |
180 | for_each_available_child_of_node(np, child) { | |
181 | struct resource child_res; | |
182 | ||
183 | ret = of_address_to_resource(child, 0, &child_res); | |
184 | if (ret < 0) { | |
665d82fb | 185 | dev_err(sram->dev, |
34d0eb50 RH |
186 | "could not get address for node %pOF\n", |
187 | child); | |
2da19688 HS |
188 | goto err_chunks; |
189 | } | |
190 | ||
191 | if (child_res.start < res->start || child_res.end > res->end) { | |
665d82fb | 192 | dev_err(sram->dev, |
34d0eb50 RH |
193 | "reserved block %pOF outside the sram area\n", |
194 | child); | |
2da19688 HS |
195 | ret = -EINVAL; |
196 | goto err_chunks; | |
197 | } | |
198 | ||
199 | block->start = child_res.start - res->start; | |
200 | block->size = resource_size(&child_res); | |
201 | list_add_tail(&block->list, &reserve_list); | |
202 | ||
b4c3fcb3 VZ |
203 | if (of_find_property(child, "export", NULL)) |
204 | block->export = true; | |
205 | ||
206 | if (of_find_property(child, "pool", NULL)) | |
207 | block->pool = true; | |
208 | ||
37afff0d DG |
209 | if (of_find_property(child, "protect-exec", NULL)) |
210 | block->protect_exec = true; | |
211 | ||
212 | if ((block->export || block->pool || block->protect_exec) && | |
213 | block->size) { | |
b4c3fcb3 VZ |
214 | exports++; |
215 | ||
216 | label = NULL; | |
217 | ret = of_property_read_string(child, "label", &label); | |
218 | if (ret && ret != -EINVAL) { | |
219 | dev_err(sram->dev, | |
34d0eb50 RH |
220 | "%pOF has invalid label name\n", |
221 | child); | |
b4c3fcb3 VZ |
222 | goto err_chunks; |
223 | } | |
224 | if (!label) | |
225 | label = child->name; | |
226 | ||
227 | block->label = devm_kstrdup(sram->dev, | |
228 | label, GFP_KERNEL); | |
ddc5c9a3 PB |
229 | if (!block->label) { |
230 | ret = -ENOMEM; | |
b4c3fcb3 | 231 | goto err_chunks; |
ddc5c9a3 | 232 | } |
b4c3fcb3 VZ |
233 | |
234 | dev_dbg(sram->dev, "found %sblock '%s' 0x%x-0x%x\n", | |
235 | block->export ? "exported " : "", block->label, | |
236 | block->start, block->start + block->size); | |
237 | } else { | |
238 | dev_dbg(sram->dev, "found reserved block 0x%x-0x%x\n", | |
239 | block->start, block->start + block->size); | |
240 | } | |
2da19688 HS |
241 | |
242 | block++; | |
243 | } | |
b4c3fcb3 | 244 | child = NULL; |
2da19688 HS |
245 | |
246 | /* the last chunk marks the end of the region */ | |
247 | rblocks[nblocks - 1].start = size; | |
248 | rblocks[nblocks - 1].size = 0; | |
249 | list_add_tail(&rblocks[nblocks - 1].list, &reserve_list); | |
250 | ||
251 | list_sort(NULL, &reserve_list, sram_reserve_cmp); | |
252 | ||
b4c3fcb3 | 253 | if (exports) { |
a86854d0 KC |
254 | sram->partition = devm_kcalloc(sram->dev, |
255 | exports, sizeof(*sram->partition), | |
b4c3fcb3 VZ |
256 | GFP_KERNEL); |
257 | if (!sram->partition) { | |
258 | ret = -ENOMEM; | |
259 | goto err_chunks; | |
260 | } | |
261 | } | |
2da19688 | 262 | |
b4c3fcb3 | 263 | cur_start = 0; |
2da19688 HS |
264 | list_for_each_entry(block, &reserve_list, list) { |
265 | /* can only happen if sections overlap */ | |
266 | if (block->start < cur_start) { | |
665d82fb | 267 | dev_err(sram->dev, |
2da19688 HS |
268 | "block at 0x%x starts after current offset 0x%lx\n", |
269 | block->start, cur_start); | |
270 | ret = -EINVAL; | |
b4c3fcb3 | 271 | sram_free_partitions(sram); |
2da19688 HS |
272 | goto err_chunks; |
273 | } | |
274 | ||
37afff0d DG |
275 | if ((block->export || block->pool || block->protect_exec) && |
276 | block->size) { | |
b4c3fcb3 VZ |
277 | ret = sram_add_partition(sram, block, |
278 | res->start + block->start); | |
279 | if (ret) { | |
280 | sram_free_partitions(sram); | |
281 | goto err_chunks; | |
282 | } | |
283 | } | |
284 | ||
2da19688 HS |
285 | /* current start is in a reserved block, so continue after it */ |
286 | if (block->start == cur_start) { | |
287 | cur_start = block->start + block->size; | |
288 | continue; | |
289 | } | |
290 | ||
291 | /* | |
292 | * allocate the space between the current starting | |
293 | * address and the following reserved block, or the | |
294 | * end of the region. | |
295 | */ | |
296 | cur_size = block->start - cur_start; | |
297 | ||
665d82fb | 298 | dev_dbg(sram->dev, "adding chunk 0x%lx-0x%lx\n", |
2da19688 | 299 | cur_start, cur_start + cur_size); |
665d82fb | 300 | |
2da19688 | 301 | ret = gen_pool_add_virt(sram->pool, |
665d82fb | 302 | (unsigned long)sram->virt_base + cur_start, |
2da19688 | 303 | res->start + cur_start, cur_size, -1); |
b4c3fcb3 VZ |
304 | if (ret < 0) { |
305 | sram_free_partitions(sram); | |
2da19688 | 306 | goto err_chunks; |
b4c3fcb3 | 307 | } |
2da19688 HS |
308 | |
309 | /* next allocation after this reserved block */ | |
310 | cur_start = block->start + block->size; | |
311 | } | |
312 | ||
3104389e | 313 | err_chunks: |
314 | of_node_put(child); | |
2da19688 HS |
315 | kfree(rblocks); |
316 | ||
a0a5be0b VZ |
317 | return ret; |
318 | } | |
319 | ||
2ae2e288 AB |
320 | static int atmel_securam_wait(void) |
321 | { | |
322 | struct regmap *regmap; | |
323 | u32 val; | |
324 | ||
325 | regmap = syscon_regmap_lookup_by_compatible("atmel,sama5d2-secumod"); | |
326 | if (IS_ERR(regmap)) | |
327 | return -ENODEV; | |
328 | ||
329 | return regmap_read_poll_timeout(regmap, AT91_SECUMOD_RAMRDY, val, | |
330 | val & AT91_SECUMOD_RAMRDY_READY, | |
331 | 10000, 500000); | |
332 | } | |
333 | ||
2ae2e288 AB |
334 | static const struct of_device_id sram_dt_ids[] = { |
335 | { .compatible = "mmio-sram" }, | |
336 | { .compatible = "atmel,sama5d2-securam", .data = atmel_securam_wait }, | |
337 | {} | |
338 | }; | |
2ae2e288 | 339 | |
a0a5be0b VZ |
340 | static int sram_probe(struct platform_device *pdev) |
341 | { | |
342 | struct sram_dev *sram; | |
a0a5be0b | 343 | int ret; |
2ae2e288 | 344 | int (*init_func)(void); |
a0a5be0b VZ |
345 | |
346 | sram = devm_kzalloc(&pdev->dev, sizeof(*sram), GFP_KERNEL); | |
347 | if (!sram) | |
348 | return -ENOMEM; | |
349 | ||
350 | sram->dev = &pdev->dev; | |
351 | ||
eb43e023 | 352 | if (of_property_read_bool(pdev->dev.of_node, "no-memory-wc")) |
444b0111 | 353 | sram->virt_base = devm_platform_ioremap_resource(pdev, 0); |
eb43e023 | 354 | else |
444b0111 BG |
355 | sram->virt_base = devm_platform_ioremap_resource_wc(pdev, 0); |
356 | if (IS_ERR(sram->virt_base)) { | |
357 | dev_err(&pdev->dev, "could not map SRAM registers\n"); | |
358 | return PTR_ERR(sram->virt_base); | |
359 | } | |
a0a5be0b | 360 | |
73858173 VZ |
361 | sram->pool = devm_gen_pool_create(sram->dev, ilog2(SRAM_GRANULARITY), |
362 | NUMA_NO_NODE, NULL); | |
363 | if (IS_ERR(sram->pool)) | |
364 | return PTR_ERR(sram->pool); | |
a0a5be0b | 365 | |
665d82fb | 366 | sram->clk = devm_clk_get(sram->dev, NULL); |
ee895ccd VZ |
367 | if (IS_ERR(sram->clk)) |
368 | sram->clk = NULL; | |
369 | else | |
370 | clk_prepare_enable(sram->clk); | |
371 | ||
444b0111 BG |
372 | ret = sram_reserve_regions(sram, |
373 | platform_get_resource(pdev, IORESOURCE_MEM, 0)); | |
d5b9653d JH |
374 | if (ret) |
375 | goto err_disable_clk; | |
376 | ||
4984c6f5 PZ |
377 | platform_set_drvdata(pdev, sram); |
378 | ||
2ae2e288 AB |
379 | init_func = of_device_get_match_data(&pdev->dev); |
380 | if (init_func) { | |
381 | ret = init_func(); | |
382 | if (ret) | |
d5b9653d | 383 | goto err_free_partitions; |
2ae2e288 AB |
384 | } |
385 | ||
665d82fb VZ |
386 | dev_dbg(sram->dev, "SRAM pool: %zu KiB @ 0x%p\n", |
387 | gen_pool_size(sram->pool) / 1024, sram->virt_base); | |
4984c6f5 PZ |
388 | |
389 | return 0; | |
f294d009 | 390 | |
d5b9653d JH |
391 | err_free_partitions: |
392 | sram_free_partitions(sram); | |
f294d009 JH |
393 | err_disable_clk: |
394 | if (sram->clk) | |
395 | clk_disable_unprepare(sram->clk); | |
f294d009 JH |
396 | |
397 | return ret; | |
4984c6f5 PZ |
398 | } |
399 | ||
400 | static int sram_remove(struct platform_device *pdev) | |
401 | { | |
402 | struct sram_dev *sram = platform_get_drvdata(pdev); | |
403 | ||
b4c3fcb3 VZ |
404 | sram_free_partitions(sram); |
405 | ||
4984c6f5 | 406 | if (gen_pool_avail(sram->pool) < gen_pool_size(sram->pool)) |
665d82fb | 407 | dev_err(sram->dev, "removed while SRAM allocated\n"); |
4984c6f5 | 408 | |
4984c6f5 PZ |
409 | if (sram->clk) |
410 | clk_disable_unprepare(sram->clk); | |
411 | ||
412 | return 0; | |
413 | } | |
414 | ||
4984c6f5 PZ |
415 | static struct platform_driver sram_driver = { |
416 | .driver = { | |
417 | .name = "sram", | |
2aa488a6 | 418 | .of_match_table = sram_dt_ids, |
4984c6f5 PZ |
419 | }, |
420 | .probe = sram_probe, | |
421 | .remove = sram_remove, | |
422 | }; | |
423 | ||
424 | static int __init sram_init(void) | |
425 | { | |
426 | return platform_driver_register(&sram_driver); | |
427 | } | |
428 | ||
429 | postcore_initcall(sram_init); |