]>
Commit | Line | Data |
---|---|---|
02bfcc5c K |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * PRU-RTU remoteproc driver for various SoCs | |
4 | * | |
a94a4071 | 5 | * Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/ |
02bfcc5c K |
6 | * Keerthy <[email protected]> |
7 | */ | |
8 | ||
02bfcc5c K |
9 | #include <dm.h> |
10 | #include <elf.h> | |
11 | #include <dm/of_access.h> | |
12 | #include <remoteproc.h> | |
13 | #include <errno.h> | |
14 | #include <clk.h> | |
15 | #include <reset.h> | |
16 | #include <regmap.h> | |
17 | #include <syscon.h> | |
18 | #include <asm/io.h> | |
19 | #include <power-domain.h> | |
20 | #include <linux/pruss_driver.h> | |
21 | #include <dm/device_compat.h> | |
22 | ||
23 | /* PRU_ICSS_PRU_CTRL registers */ | |
24 | #define PRU_CTRL_CTRL 0x0000 | |
25 | #define PRU_CTRL_STS 0x0004 | |
26 | #define PRU_CTRL_WAKEUP_EN 0x0008 | |
27 | #define PRU_CTRL_CYCLE 0x000C | |
28 | #define PRU_CTRL_STALL 0x0010 | |
29 | #define PRU_CTRL_CTBIR0 0x0020 | |
30 | #define PRU_CTRL_CTBIR1 0x0024 | |
31 | #define PRU_CTRL_CTPPR0 0x0028 | |
32 | #define PRU_CTRL_CTPPR1 0x002C | |
33 | ||
34 | /* CTRL register bit-fields */ | |
35 | #define CTRL_CTRL_SOFT_RST_N BIT(0) | |
36 | #define CTRL_CTRL_EN BIT(1) | |
37 | #define CTRL_CTRL_SLEEPING BIT(2) | |
38 | #define CTRL_CTRL_CTR_EN BIT(3) | |
39 | #define CTRL_CTRL_SINGLE_STEP BIT(8) | |
40 | #define CTRL_CTRL_RUNSTATE BIT(15) | |
41 | ||
42 | #define RPROC_FLAGS_SHIFT 16 | |
43 | #define RPROC_FLAGS_NONE 0 | |
44 | #define RPROC_FLAGS_ELF_PHDR BIT(0 + RPROC_FLAGS_SHIFT) | |
45 | #define RPROC_FLAGS_ELF_SHDR BIT(1 + RPROC_FLAGS_SHIFT) | |
46 | ||
47 | /** | |
48 | * enum pru_mem - PRU core memory range identifiers | |
49 | */ | |
50 | enum pru_mem { | |
51 | PRU_MEM_IRAM = 0, | |
52 | PRU_MEM_CTRL, | |
53 | PRU_MEM_DEBUG, | |
54 | PRU_MEM_MAX, | |
55 | }; | |
56 | ||
57 | struct pru_privdata { | |
58 | phys_addr_t pru_iram; | |
59 | phys_addr_t pru_ctrl; | |
60 | phys_addr_t pru_debug; | |
61 | fdt_size_t pru_iramsz; | |
62 | fdt_size_t pru_ctrlsz; | |
63 | fdt_size_t pru_debugsz; | |
64 | const char *fw_name; | |
65 | u32 iram_da; | |
66 | u32 pdram_da; | |
67 | u32 sdram_da; | |
68 | u32 shrdram_da; | |
69 | u32 bootaddr; | |
70 | int id; | |
71 | struct pruss *prusspriv; | |
72 | }; | |
73 | ||
74 | static inline u32 pru_control_read_reg(struct pru_privdata *pru, unsigned int reg) | |
75 | { | |
76 | return readl(pru->pru_ctrl + reg); | |
77 | } | |
78 | ||
79 | static inline | |
80 | void pru_control_write_reg(struct pru_privdata *pru, unsigned int reg, u32 val) | |
81 | { | |
82 | writel(val, pru->pru_ctrl + reg); | |
83 | } | |
84 | ||
85 | static inline | |
86 | void pru_control_set_reg(struct pru_privdata *pru, unsigned int reg, | |
87 | u32 mask, u32 set) | |
88 | { | |
89 | u32 val; | |
90 | ||
91 | val = pru_control_read_reg(pru, reg); | |
92 | val &= ~mask; | |
93 | val |= (set & mask); | |
94 | pru_control_write_reg(pru, reg, val); | |
95 | } | |
96 | ||
97 | /** | |
98 | * pru_rproc_set_ctable() - set the constant table index for the PRU | |
99 | * @rproc: the rproc instance of the PRU | |
100 | * @c: constant table index to set | |
101 | * @addr: physical address to set it to | |
102 | */ | |
103 | static int pru_rproc_set_ctable(struct pru_privdata *pru, enum pru_ctable_idx c, u32 addr) | |
104 | { | |
105 | unsigned int reg; | |
106 | u32 mask, set; | |
107 | u16 idx; | |
108 | u16 idx_mask; | |
109 | ||
110 | /* pointer is 16 bit and index is 8-bit so mask out the rest */ | |
111 | idx_mask = (c >= PRU_C28) ? 0xFFFF : 0xFF; | |
112 | ||
113 | /* ctable uses bit 8 and upwards only */ | |
114 | idx = (addr >> 8) & idx_mask; | |
115 | ||
116 | /* configurable ctable (i.e. C24) starts at PRU_CTRL_CTBIR0 */ | |
117 | reg = PRU_CTRL_CTBIR0 + 4 * (c >> 1); | |
118 | mask = idx_mask << (16 * (c & 1)); | |
119 | set = idx << (16 * (c & 1)); | |
120 | ||
121 | pru_control_set_reg(pru, reg, mask, set); | |
122 | ||
123 | return 0; | |
124 | } | |
125 | ||
126 | /** | |
127 | * pru_start() - start the pru processor | |
128 | * @dev: corresponding k3 remote processor device | |
129 | * | |
130 | * Return: 0 if all goes good, else appropriate error message. | |
131 | */ | |
132 | static int pru_start(struct udevice *dev) | |
133 | { | |
134 | struct pru_privdata *priv; | |
135 | int val = 0; | |
136 | ||
137 | priv = dev_get_priv(dev); | |
138 | ||
139 | pru_rproc_set_ctable(priv, PRU_C28, 0x100 << 8); | |
140 | ||
141 | val = CTRL_CTRL_EN | ((priv->bootaddr >> 2) << 16); | |
142 | writel(val, priv->pru_ctrl + PRU_CTRL_CTRL); | |
143 | ||
144 | return 0; | |
145 | } | |
146 | ||
147 | /** | |
148 | * pru_stop() - Stop pru processor | |
149 | * @dev: corresponding k3 remote processor device | |
150 | * | |
151 | * Return: 0 if all goes good, else appropriate error message. | |
152 | */ | |
153 | static int pru_stop(struct udevice *dev) | |
154 | { | |
155 | struct pru_privdata *priv; | |
156 | int val = 0; | |
157 | ||
158 | priv = dev_get_priv(dev); | |
159 | ||
160 | val = readl(priv->pru_ctrl + PRU_CTRL_CTRL); | |
161 | val &= ~CTRL_CTRL_EN; | |
162 | writel(val, priv->pru_ctrl + PRU_CTRL_CTRL); | |
163 | ||
164 | return 0; | |
165 | } | |
166 | ||
167 | /** | |
168 | * pru_init() - Initialize the remote processor | |
169 | * @dev: rproc device pointer | |
170 | * | |
171 | * Return: 0 if all went ok, else return appropriate error | |
172 | */ | |
173 | static int pru_init(struct udevice *dev) | |
174 | { | |
175 | return 0; | |
176 | } | |
177 | ||
178 | /* | |
179 | * Convert PRU device address (data spaces only) to kernel virtual address | |
180 | * | |
181 | * Each PRU has access to all data memories within the PRUSS, accessible at | |
182 | * different ranges. So, look through both its primary and secondary Data | |
183 | * RAMs as well as any shared Data RAM to convert a PRU device address to | |
184 | * kernel virtual address. Data RAM0 is primary Data RAM for PRU0 and Data | |
185 | * RAM1 is primary Data RAM for PRU1. | |
186 | */ | |
187 | static void *pru_d_da_to_pa(struct pru_privdata *priv, u32 da, int len) | |
188 | { | |
189 | u32 offset; | |
190 | void *pa = NULL; | |
191 | phys_addr_t dram0, dram1, shrdram2; | |
192 | u32 dram0sz, dram1sz, shrdram2sz; | |
193 | ||
194 | if (len <= 0) | |
195 | return NULL; | |
196 | ||
197 | dram0 = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM0].pa; | |
198 | dram1 = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM1].pa; | |
199 | shrdram2 = priv->prusspriv->mem_regions[PRUSS_MEM_SHRD_RAM2].pa; | |
200 | dram0sz = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM0].size; | |
201 | dram1sz = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM1].size; | |
202 | shrdram2sz = priv->prusspriv->mem_regions[PRUSS_MEM_SHRD_RAM2].size; | |
203 | ||
204 | /* PRU1 has its local RAM addresses reversed */ | |
205 | if (priv->id == 1) { | |
206 | dram1 = dram0; | |
207 | dram1sz = dram0sz; | |
208 | ||
209 | dram0 = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM1].pa; | |
210 | dram0sz = priv->prusspriv->mem_regions[PRUSS_MEM_DRAM1].size; | |
211 | } | |
212 | ||
213 | if (da >= priv->pdram_da && da + len <= priv->pdram_da + dram0sz) { | |
214 | offset = da - priv->pdram_da; | |
215 | pa = (__force void *)(dram0 + offset); | |
216 | } else if (da >= priv->sdram_da && | |
217 | da + len <= priv->sdram_da + dram1sz) { | |
218 | offset = da - priv->sdram_da; | |
219 | pa = (__force void *)(dram1 + offset); | |
220 | } else if (da >= priv->shrdram_da && | |
221 | da + len <= priv->shrdram_da + shrdram2sz) { | |
222 | offset = da - priv->shrdram_da; | |
223 | pa = (__force void *)(shrdram2 + offset); | |
224 | } | |
225 | ||
226 | return pa; | |
227 | } | |
228 | ||
229 | /* | |
230 | * Convert PRU device address (instruction space) to kernel virtual address | |
231 | * | |
232 | * A PRU does not have an unified address space. Each PRU has its very own | |
233 | * private Instruction RAM, and its device address is identical to that of | |
234 | * its primary Data RAM device address. | |
235 | */ | |
236 | static void *pru_i_da_to_pa(struct pru_privdata *priv, u32 da, int len) | |
237 | { | |
238 | u32 offset; | |
239 | void *pa = NULL; | |
240 | ||
241 | if (len <= 0) | |
242 | return NULL; | |
243 | ||
244 | if (da >= priv->iram_da && | |
245 | da + len <= priv->iram_da + priv->pru_iramsz) { | |
246 | offset = da - priv->iram_da; | |
247 | pa = (__force void *)(priv->pru_iram + offset); | |
248 | } | |
249 | ||
250 | return pa; | |
251 | } | |
252 | ||
253 | /* PRU-specific address translator */ | |
254 | static void *pru_da_to_pa(struct pru_privdata *priv, u64 da, int len, u32 flags) | |
255 | { | |
256 | void *pa; | |
257 | u32 exec_flag; | |
258 | ||
259 | exec_flag = ((flags & RPROC_FLAGS_ELF_SHDR) ? flags & SHF_EXECINSTR : | |
260 | ((flags & RPROC_FLAGS_ELF_PHDR) ? flags & PF_X : 0)); | |
261 | ||
262 | if (exec_flag) | |
263 | pa = pru_i_da_to_pa(priv, da, len); | |
264 | else | |
265 | pa = pru_d_da_to_pa(priv, da, len); | |
266 | ||
267 | return pa; | |
268 | } | |
269 | ||
270 | /* | |
271 | * Custom memory copy implementation for ICSSG PRU/RTU Cores | |
272 | * | |
273 | * The ICSSG PRU/RTU cores have a memory copying issue with IRAM memories, that | |
274 | * is not seen on previous generation SoCs. The data is reflected properly in | |
275 | * the IRAM memories only for integer (4-byte) copies. Any unaligned copies | |
276 | * result in all the other pre-existing bytes zeroed out within that 4-byte | |
277 | * boundary, thereby resulting in wrong text/code in the IRAMs. Also, the | |
278 | * IRAM memory port interface does not allow any 8-byte copies (as commonly | |
279 | * used by ARM64 memcpy implementation) and throws an exception. The DRAM | |
280 | * memory ports do not show this behavior. Use this custom copying function | |
281 | * to properly load the PRU/RTU firmware images on all memories for simplicity. | |
282 | * | |
283 | * TODO: Improve the function to deal with additional corner cases like | |
284 | * unaligned copy sizes or sub-integer trailing bytes when the need arises. | |
285 | */ | |
286 | static int pru_rproc_memcpy(void *dest, void *src, size_t count) | |
287 | { | |
288 | const int *s = src; | |
289 | int *d = dest; | |
290 | int size = count / 4; | |
291 | int *tmp_src = NULL; | |
292 | ||
293 | /* limited to 4-byte aligned addresses and copy sizes */ | |
294 | if ((long)dest % 4 || count % 4) | |
295 | return -EINVAL; | |
296 | ||
297 | /* src offsets in ELF firmware image can be non-aligned */ | |
298 | if ((long)src % 4) { | |
299 | tmp_src = malloc(count); | |
300 | if (!tmp_src) | |
301 | return -ENOMEM; | |
302 | ||
303 | memcpy(tmp_src, src, count); | |
304 | s = tmp_src; | |
305 | } | |
306 | ||
307 | while (size--) | |
308 | *d++ = *s++; | |
309 | ||
310 | kfree(tmp_src); | |
311 | ||
312 | return 0; | |
313 | } | |
314 | ||
315 | /** | |
316 | * pru_load() - Load pru firmware | |
317 | * @dev: corresponding k3 remote processor device | |
318 | * @addr: Address on the RAM from which firmware is to be loaded | |
319 | * @size: Size of the pru firmware in bytes | |
320 | * | |
321 | * Return: 0 if all goes good, else appropriate error message. | |
322 | */ | |
323 | static int pru_load(struct udevice *dev, ulong addr, ulong size) | |
324 | { | |
325 | struct pru_privdata *priv; | |
326 | Elf32_Ehdr *ehdr; | |
327 | Elf32_Phdr *phdr; | |
328 | int i, ret = 0; | |
329 | ||
330 | priv = dev_get_priv(dev); | |
331 | ||
332 | ehdr = (Elf32_Ehdr *)addr; | |
333 | phdr = (Elf32_Phdr *)(addr + ehdr->e_phoff); | |
334 | ||
335 | /* go through the available ELF segments */ | |
336 | for (i = 0; i < ehdr->e_phnum; i++, phdr++) { | |
337 | u32 da = phdr->p_paddr; | |
338 | u32 memsz = phdr->p_memsz; | |
339 | u32 filesz = phdr->p_filesz; | |
340 | u32 offset = phdr->p_offset; | |
341 | void *ptr; | |
342 | ||
343 | if (phdr->p_type != PT_LOAD) | |
344 | continue; | |
345 | ||
346 | dev_dbg(dev, "phdr: type %d da 0x%x memsz 0x%x filesz 0x%x\n", | |
347 | phdr->p_type, da, memsz, filesz); | |
348 | ||
349 | if (filesz > memsz) { | |
350 | dev_dbg(dev, "bad phdr filesz 0x%x memsz 0x%x\n", | |
351 | filesz, memsz); | |
352 | ret = -EINVAL; | |
353 | break; | |
354 | } | |
355 | ||
356 | if (offset + filesz > size) { | |
357 | dev_dbg(dev, "truncated fw: need 0x%x avail 0x%zx\n", | |
358 | offset + filesz, size); | |
359 | ret = -EINVAL; | |
360 | break; | |
361 | } | |
362 | ||
363 | /* grab the kernel address for this device address */ | |
364 | ptr = pru_da_to_pa(priv, da, memsz, | |
365 | RPROC_FLAGS_ELF_PHDR | phdr->p_flags); | |
366 | if (!ptr) { | |
367 | dev_dbg(dev, "bad phdr da 0x%x mem 0x%x\n", da, memsz); | |
368 | ret = -EINVAL; | |
369 | break; | |
370 | } | |
371 | ||
372 | /* skip the memzero logic performed by remoteproc ELF loader */ | |
373 | if (!phdr->p_filesz) | |
374 | continue; | |
375 | ||
376 | ret = pru_rproc_memcpy(ptr, | |
377 | (void *)addr + phdr->p_offset, filesz); | |
378 | if (ret) { | |
379 | dev_dbg(dev, "PRU custom memory copy failed for da 0x%x memsz 0x%x\n", | |
380 | da, memsz); | |
381 | break; | |
382 | } | |
383 | } | |
384 | ||
385 | priv->bootaddr = ehdr->e_entry; | |
386 | ||
387 | return ret; | |
388 | } | |
389 | ||
390 | static const struct dm_rproc_ops pru_ops = { | |
391 | .init = pru_init, | |
392 | .start = pru_start, | |
393 | .stop = pru_stop, | |
394 | .load = pru_load, | |
395 | }; | |
396 | ||
397 | static void pru_set_id(struct pru_privdata *priv, struct udevice *dev) | |
398 | { | |
399 | u32 mask2 = 0x38000; | |
400 | ||
03de305e TR |
401 | if (device_is_compatible(dev, "ti,am654-rtu") || |
402 | device_is_compatible(dev, "ti,am642-rtu")) | |
02bfcc5c K |
403 | mask2 = 0x6000; |
404 | ||
03de305e TR |
405 | if (device_is_compatible(dev, "ti,am654-tx-pru") || |
406 | device_is_compatible(dev, "ti,am642-tx-pru")) | |
02bfcc5c K |
407 | mask2 = 0xc000; |
408 | ||
409 | if ((priv->pru_iram & mask2) == mask2) | |
410 | priv->id = 1; | |
411 | else | |
412 | priv->id = 0; | |
413 | } | |
414 | ||
415 | /** | |
416 | * pru_probe() - Basic probe | |
417 | * @dev: corresponding k3 remote processor device | |
418 | * | |
419 | * Return: 0 if all goes good, else appropriate error message. | |
420 | */ | |
421 | static int pru_probe(struct udevice *dev) | |
422 | { | |
423 | struct pru_privdata *priv; | |
424 | ofnode node; | |
425 | ||
426 | node = dev_ofnode(dev); | |
427 | ||
428 | priv = dev_get_priv(dev); | |
429 | priv->prusspriv = dev_get_priv(dev->parent); | |
430 | ||
431 | priv->pru_iram = devfdt_get_addr_size_index(dev, PRU_MEM_IRAM, | |
432 | &priv->pru_iramsz); | |
433 | priv->pru_ctrl = devfdt_get_addr_size_index(dev, PRU_MEM_CTRL, | |
434 | &priv->pru_ctrlsz); | |
435 | priv->pru_debug = devfdt_get_addr_size_index(dev, PRU_MEM_DEBUG, | |
436 | &priv->pru_debugsz); | |
437 | ||
438 | priv->iram_da = 0; | |
439 | priv->pdram_da = 0; | |
440 | priv->sdram_da = 0x2000; | |
441 | priv->shrdram_da = 0x10000; | |
442 | ||
443 | pru_set_id(priv, dev); | |
444 | ||
445 | return 0; | |
446 | } | |
447 | ||
448 | static const struct udevice_id pru_ids[] = { | |
449 | { .compatible = "ti,am654-pru"}, | |
450 | { .compatible = "ti,am654-rtu"}, | |
451 | { .compatible = "ti,am654-tx-pru" }, | |
03de305e TR |
452 | { .compatible = "ti,am642-pru"}, |
453 | { .compatible = "ti,am642-rtu"}, | |
454 | { .compatible = "ti,am642-tx-pru" }, | |
02bfcc5c K |
455 | {} |
456 | }; | |
457 | ||
458 | U_BOOT_DRIVER(pru) = { | |
459 | .name = "pru", | |
460 | .of_match = pru_ids, | |
461 | .id = UCLASS_REMOTEPROC, | |
462 | .ops = &pru_ops, | |
463 | .probe = pru_probe, | |
464 | .priv_auto = sizeof(struct pru_privdata), | |
465 | }; |