1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for MPC52xx processor BestComm peripheral controller |
4 | * |
5 | * Copyright (C) 2006-2007 Sylvain Munaut <tnt@246tNt.com> |
6 | * Copyright (C) 2005 Varma Electronics Oy, |
7 | * ( by Andrey Volkov <avolkov@varma-el.com> ) |
8 | * Copyright (C) 2003-2004 MontaVista, Software, Inc. |
9 | * ( by Dale Farnsworth <dfarnsworth@mvista.com> ) |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/slab.h> |
15 | #include <linux/of.h> |
16 | #include <linux/of_address.h> |
17 | #include <linux/of_irq.h> |
18 | #include <linux/platform_device.h> |
19 | #include <asm/io.h> |
20 | #include <asm/irq.h> |
21 | #include <asm/mpc52xx.h> |
22 | |
23 | #include <linux/fsl/bestcomm/sram.h> |
24 | #include <linux/fsl/bestcomm/bestcomm_priv.h> |
25 | #include "linux/fsl/bestcomm/bestcomm.h" |
26 | |
27 | #define DRIVER_NAME "bestcomm-core" |
28 | |
29 | /* MPC5200 device tree match tables */ |
30 | static const struct of_device_id mpc52xx_sram_ids[] = { |
31 | { .compatible = "fsl,mpc5200-sram" , }, |
32 | { .compatible = "mpc5200-sram" , }, |
33 | {} |
34 | }; |
35 | |
36 | |
37 | struct bcom_engine *bcom_eng = NULL; |
38 | EXPORT_SYMBOL_GPL(bcom_eng); /* needed for inline functions */ |
39 | |
40 | /* ======================================================================== */ |
41 | /* Public and private API */ |
42 | /* ======================================================================== */ |
43 | |
44 | /* Private API */ |
45 | |
46 | struct bcom_task * |
47 | bcom_task_alloc(int bd_count, int bd_size, int priv_size) |
48 | { |
49 | int i, tasknum = -1; |
50 | struct bcom_task *tsk; |
51 | |
52 | /* Don't try to do anything if bestcomm init failed */ |
53 | if (!bcom_eng) |
54 | return NULL; |
55 | |
56 | /* Get and reserve a task num */ |
57 | spin_lock(lock: &bcom_eng->lock); |
58 | |
59 | for (i=0; i<BCOM_MAX_TASKS; i++) |
60 | if (!bcom_eng->tdt[i].stop) { /* we use stop as a marker */ |
61 | bcom_eng->tdt[i].stop = 0xfffffffful; /* dummy addr */ |
62 | tasknum = i; |
63 | break; |
64 | } |
65 | |
66 | spin_unlock(lock: &bcom_eng->lock); |
67 | |
68 | if (tasknum < 0) |
69 | return NULL; |
70 | |
71 | /* Allocate our structure */ |
72 | tsk = kzalloc(size: sizeof(struct bcom_task) + priv_size, GFP_KERNEL); |
73 | if (!tsk) |
74 | goto error; |
75 | |
76 | tsk->tasknum = tasknum; |
77 | if (priv_size) |
78 | tsk->priv = (void*)tsk + sizeof(struct bcom_task); |
79 | |
80 | /* Get IRQ of that task */ |
81 | tsk->irq = irq_of_parse_and_map(node: bcom_eng->ofnode, index: tsk->tasknum); |
82 | if (!tsk->irq) |
83 | goto error; |
84 | |
85 | /* Init the BDs, if needed */ |
86 | if (bd_count) { |
87 | tsk->cookie = kmalloc_array(n: bd_count, size: sizeof(void *), |
88 | GFP_KERNEL); |
89 | if (!tsk->cookie) |
90 | goto error; |
91 | |
92 | tsk->bd = bcom_sram_alloc(size: bd_count * bd_size, align: 4, phys: &tsk->bd_pa); |
93 | if (!tsk->bd) |
94 | goto error; |
95 | memset_io(tsk->bd, 0x00, bd_count * bd_size); |
96 | |
97 | tsk->num_bd = bd_count; |
98 | tsk->bd_size = bd_size; |
99 | } |
100 | |
101 | return tsk; |
102 | |
103 | error: |
104 | if (tsk) { |
105 | if (tsk->irq) |
106 | irq_dispose_mapping(virq: tsk->irq); |
107 | bcom_sram_free(ptr: tsk->bd); |
108 | kfree(objp: tsk->cookie); |
109 | kfree(objp: tsk); |
110 | } |
111 | |
112 | bcom_eng->tdt[tasknum].stop = 0; |
113 | |
114 | return NULL; |
115 | } |
116 | EXPORT_SYMBOL_GPL(bcom_task_alloc); |
117 | |
118 | void |
119 | bcom_task_free(struct bcom_task *tsk) |
120 | { |
121 | /* Stop the task */ |
122 | bcom_disable_task(task: tsk->tasknum); |
123 | |
124 | /* Clear TDT */ |
125 | bcom_eng->tdt[tsk->tasknum].start = 0; |
126 | bcom_eng->tdt[tsk->tasknum].stop = 0; |
127 | |
128 | /* Free everything */ |
129 | irq_dispose_mapping(virq: tsk->irq); |
130 | bcom_sram_free(ptr: tsk->bd); |
131 | kfree(objp: tsk->cookie); |
132 | kfree(objp: tsk); |
133 | } |
134 | EXPORT_SYMBOL_GPL(bcom_task_free); |
135 | |
136 | int |
137 | bcom_load_image(int task, u32 *task_image) |
138 | { |
139 | struct bcom_task_header *hdr = (struct bcom_task_header *)task_image; |
140 | struct bcom_tdt *tdt; |
141 | u32 *desc, *var, *inc; |
142 | u32 *desc_src, *var_src, *inc_src; |
143 | |
144 | /* Safety checks */ |
145 | if (hdr->magic != BCOM_TASK_MAGIC) { |
146 | printk(KERN_ERR DRIVER_NAME |
147 | ": Trying to load invalid microcode\n" ); |
148 | return -EINVAL; |
149 | } |
150 | |
151 | if ((task < 0) || (task >= BCOM_MAX_TASKS)) { |
152 | printk(KERN_ERR DRIVER_NAME |
153 | ": Trying to load invalid task %d\n" , task); |
154 | return -EINVAL; |
155 | } |
156 | |
157 | /* Initial load or reload */ |
158 | tdt = &bcom_eng->tdt[task]; |
159 | |
160 | if (tdt->start) { |
161 | desc = bcom_task_desc(task); |
162 | if (hdr->desc_size != bcom_task_num_descs(task)) { |
163 | printk(KERN_ERR DRIVER_NAME |
164 | ": Trying to reload wrong task image " |
165 | "(%d size %d/%d)!\n" , |
166 | task, |
167 | hdr->desc_size, |
168 | bcom_task_num_descs(task)); |
169 | return -EINVAL; |
170 | } |
171 | } else { |
172 | phys_addr_t start_pa; |
173 | |
174 | desc = bcom_sram_alloc(size: hdr->desc_size * sizeof(u32), align: 4, phys: &start_pa); |
175 | if (!desc) |
176 | return -ENOMEM; |
177 | |
178 | tdt->start = start_pa; |
179 | tdt->stop = start_pa + ((hdr->desc_size-1) * sizeof(u32)); |
180 | } |
181 | |
182 | var = bcom_task_var(task); |
183 | inc = bcom_task_inc(task); |
184 | |
185 | /* Clear & copy */ |
186 | memset_io(var, 0x00, BCOM_VAR_SIZE); |
187 | memset_io(inc, 0x00, BCOM_INC_SIZE); |
188 | |
189 | desc_src = (u32 *)(hdr + 1); |
190 | var_src = desc_src + hdr->desc_size; |
191 | inc_src = var_src + hdr->var_size; |
192 | |
193 | memcpy_toio(desc, desc_src, hdr->desc_size * sizeof(u32)); |
194 | memcpy_toio(var + hdr->first_var, var_src, hdr->var_size * sizeof(u32)); |
195 | memcpy_toio(inc, inc_src, hdr->inc_size * sizeof(u32)); |
196 | |
197 | return 0; |
198 | } |
199 | EXPORT_SYMBOL_GPL(bcom_load_image); |
200 | |
201 | void |
202 | bcom_set_initiator(int task, int initiator) |
203 | { |
204 | int i; |
205 | int num_descs; |
206 | u32 *desc; |
207 | int next_drd_has_initiator; |
208 | |
209 | bcom_set_tcr_initiator(task, initiator); |
210 | |
211 | /* Just setting tcr is apparently not enough due to some problem */ |
212 | /* with it. So we just go thru all the microcode and replace in */ |
213 | /* the DRD directly */ |
214 | |
215 | desc = bcom_task_desc(task); |
216 | next_drd_has_initiator = 1; |
217 | num_descs = bcom_task_num_descs(task); |
218 | |
219 | for (i=0; i<num_descs; i++, desc++) { |
220 | if (!bcom_desc_is_drd(desc: *desc)) |
221 | continue; |
222 | if (next_drd_has_initiator) |
223 | if (bcom_desc_initiator(desc: *desc) != BCOM_INITIATOR_ALWAYS) |
224 | bcom_set_desc_initiator(desc, initiator); |
225 | next_drd_has_initiator = !bcom_drd_is_extended(desc: *desc); |
226 | } |
227 | } |
228 | EXPORT_SYMBOL_GPL(bcom_set_initiator); |
229 | |
230 | |
231 | /* Public API */ |
232 | |
233 | void |
234 | bcom_enable(struct bcom_task *tsk) |
235 | { |
236 | bcom_enable_task(task: tsk->tasknum); |
237 | } |
238 | EXPORT_SYMBOL_GPL(bcom_enable); |
239 | |
240 | void |
241 | bcom_disable(struct bcom_task *tsk) |
242 | { |
243 | bcom_disable_task(task: tsk->tasknum); |
244 | } |
245 | EXPORT_SYMBOL_GPL(bcom_disable); |
246 | |
247 | |
248 | /* ======================================================================== */ |
249 | /* Engine init/cleanup */ |
250 | /* ======================================================================== */ |
251 | |
252 | /* Function Descriptor table */ |
253 | /* this will need to be updated if Freescale changes their task code FDT */ |
254 | static u32 fdt_ops[] = { |
255 | 0xa0045670, /* FDT[48] - load_acc() */ |
256 | 0x80045670, /* FDT[49] - unload_acc() */ |
257 | 0x21800000, /* FDT[50] - and() */ |
258 | 0x21e00000, /* FDT[51] - or() */ |
259 | 0x21500000, /* FDT[52] - xor() */ |
260 | 0x21400000, /* FDT[53] - andn() */ |
261 | 0x21500000, /* FDT[54] - not() */ |
262 | 0x20400000, /* FDT[55] - add() */ |
263 | 0x20500000, /* FDT[56] - sub() */ |
264 | 0x20800000, /* FDT[57] - lsh() */ |
265 | 0x20a00000, /* FDT[58] - rsh() */ |
266 | 0xc0170000, /* FDT[59] - crc8() */ |
267 | 0xc0145670, /* FDT[60] - crc16() */ |
268 | 0xc0345670, /* FDT[61] - crc32() */ |
269 | 0xa0076540, /* FDT[62] - endian32() */ |
270 | 0xa0000760, /* FDT[63] - endian16() */ |
271 | }; |
272 | |
273 | |
274 | static int bcom_engine_init(void) |
275 | { |
276 | int task; |
277 | phys_addr_t tdt_pa, ctx_pa, var_pa, fdt_pa; |
278 | unsigned int tdt_size, ctx_size, var_size, fdt_size; |
279 | |
280 | /* Allocate & clear SRAM zones for FDT, TDTs, contexts and vars/incs */ |
281 | tdt_size = BCOM_MAX_TASKS * sizeof(struct bcom_tdt); |
282 | ctx_size = BCOM_MAX_TASKS * BCOM_CTX_SIZE; |
283 | var_size = BCOM_MAX_TASKS * (BCOM_VAR_SIZE + BCOM_INC_SIZE); |
284 | fdt_size = BCOM_FDT_SIZE; |
285 | |
286 | bcom_eng->tdt = bcom_sram_alloc(size: tdt_size, align: sizeof(u32), phys: &tdt_pa); |
287 | bcom_eng->ctx = bcom_sram_alloc(size: ctx_size, BCOM_CTX_ALIGN, phys: &ctx_pa); |
288 | bcom_eng->var = bcom_sram_alloc(size: var_size, BCOM_VAR_ALIGN, phys: &var_pa); |
289 | bcom_eng->fdt = bcom_sram_alloc(size: fdt_size, BCOM_FDT_ALIGN, phys: &fdt_pa); |
290 | |
291 | if (!bcom_eng->tdt || !bcom_eng->ctx || !bcom_eng->var || !bcom_eng->fdt) { |
292 | printk(KERN_ERR "DMA: SRAM alloc failed in engine init !\n" ); |
293 | |
294 | bcom_sram_free(ptr: bcom_eng->tdt); |
295 | bcom_sram_free(ptr: bcom_eng->ctx); |
296 | bcom_sram_free(ptr: bcom_eng->var); |
297 | bcom_sram_free(ptr: bcom_eng->fdt); |
298 | |
299 | return -ENOMEM; |
300 | } |
301 | |
302 | memset_io(bcom_eng->tdt, 0x00, tdt_size); |
303 | memset_io(bcom_eng->ctx, 0x00, ctx_size); |
304 | memset_io(bcom_eng->var, 0x00, var_size); |
305 | memset_io(bcom_eng->fdt, 0x00, fdt_size); |
306 | |
307 | /* Copy the FDT for the EU#3 */ |
308 | memcpy_toio(&bcom_eng->fdt[48], fdt_ops, sizeof(fdt_ops)); |
309 | |
310 | /* Initialize Task base structure */ |
311 | for (task=0; task<BCOM_MAX_TASKS; task++) |
312 | { |
313 | out_be16(&bcom_eng->regs->tcr[task], 0); |
314 | out_8(&bcom_eng->regs->ipr[task], 0); |
315 | |
316 | bcom_eng->tdt[task].context = ctx_pa; |
317 | bcom_eng->tdt[task].var = var_pa; |
318 | bcom_eng->tdt[task].fdt = fdt_pa; |
319 | |
320 | var_pa += BCOM_VAR_SIZE + BCOM_INC_SIZE; |
321 | ctx_pa += BCOM_CTX_SIZE; |
322 | } |
323 | |
324 | out_be32(&bcom_eng->regs->taskBar, tdt_pa); |
325 | |
326 | /* Init 'always' initiator */ |
327 | out_8(&bcom_eng->regs->ipr[BCOM_INITIATOR_ALWAYS], BCOM_IPR_ALWAYS); |
328 | |
329 | /* Disable COMM Bus Prefetch on the original 5200; it's broken */ |
330 | if ((mfspr(SPRN_SVR) & MPC5200_SVR_MASK) == MPC5200_SVR) |
331 | bcom_disable_prefetch(); |
332 | |
333 | /* Init lock */ |
334 | spin_lock_init(&bcom_eng->lock); |
335 | |
336 | return 0; |
337 | } |
338 | |
339 | static void |
340 | bcom_engine_cleanup(void) |
341 | { |
342 | int task; |
343 | |
344 | /* Stop all tasks */ |
345 | for (task=0; task<BCOM_MAX_TASKS; task++) |
346 | { |
347 | out_be16(&bcom_eng->regs->tcr[task], 0); |
348 | out_8(&bcom_eng->regs->ipr[task], 0); |
349 | } |
350 | |
351 | out_be32(&bcom_eng->regs->taskBar, 0ul); |
352 | |
353 | /* Release the SRAM zones */ |
354 | bcom_sram_free(ptr: bcom_eng->tdt); |
355 | bcom_sram_free(ptr: bcom_eng->ctx); |
356 | bcom_sram_free(ptr: bcom_eng->var); |
357 | bcom_sram_free(ptr: bcom_eng->fdt); |
358 | } |
359 | |
360 | |
361 | /* ======================================================================== */ |
362 | /* OF platform driver */ |
363 | /* ======================================================================== */ |
364 | |
365 | static int mpc52xx_bcom_probe(struct platform_device *op) |
366 | { |
367 | struct device_node *ofn_sram; |
368 | struct resource res_bcom; |
369 | |
370 | int rv; |
371 | |
372 | /* Inform user we're ok so far */ |
373 | printk(KERN_INFO "DMA: MPC52xx BestComm driver\n" ); |
374 | |
375 | /* Get the bestcomm node */ |
376 | of_node_get(node: op->dev.of_node); |
377 | |
378 | /* Prepare SRAM */ |
379 | ofn_sram = of_find_matching_node(NULL, matches: mpc52xx_sram_ids); |
380 | if (!ofn_sram) { |
381 | printk(KERN_ERR DRIVER_NAME ": " |
382 | "No SRAM found in device tree\n" ); |
383 | rv = -ENODEV; |
384 | goto error_ofput; |
385 | } |
386 | rv = bcom_sram_init(sram_node: ofn_sram, DRIVER_NAME); |
387 | of_node_put(node: ofn_sram); |
388 | |
389 | if (rv) { |
390 | printk(KERN_ERR DRIVER_NAME ": " |
391 | "Error in SRAM init\n" ); |
392 | goto error_ofput; |
393 | } |
394 | |
395 | /* Get a clean struct */ |
396 | bcom_eng = kzalloc(size: sizeof(struct bcom_engine), GFP_KERNEL); |
397 | if (!bcom_eng) { |
398 | rv = -ENOMEM; |
399 | goto error_sramclean; |
400 | } |
401 | |
402 | /* Save the node */ |
403 | bcom_eng->ofnode = op->dev.of_node; |
404 | |
405 | /* Get, reserve & map io */ |
406 | if (of_address_to_resource(dev: op->dev.of_node, index: 0, r: &res_bcom)) { |
407 | printk(KERN_ERR DRIVER_NAME ": " |
408 | "Can't get resource\n" ); |
409 | rv = -EINVAL; |
410 | goto error_sramclean; |
411 | } |
412 | |
413 | if (!request_mem_region(res_bcom.start, resource_size(&res_bcom), |
414 | DRIVER_NAME)) { |
415 | printk(KERN_ERR DRIVER_NAME ": " |
416 | "Can't request registers region\n" ); |
417 | rv = -EBUSY; |
418 | goto error_sramclean; |
419 | } |
420 | |
421 | bcom_eng->regs_base = res_bcom.start; |
422 | bcom_eng->regs = ioremap(res_bcom.start, sizeof(struct mpc52xx_sdma)); |
423 | if (!bcom_eng->regs) { |
424 | printk(KERN_ERR DRIVER_NAME ": " |
425 | "Can't map registers\n" ); |
426 | rv = -ENOMEM; |
427 | goto error_release; |
428 | } |
429 | |
430 | /* Now, do the real init */ |
431 | rv = bcom_engine_init(); |
432 | if (rv) |
433 | goto error_unmap; |
434 | |
435 | /* Done ! */ |
436 | printk(KERN_INFO "DMA: MPC52xx BestComm engine @%08lx ok !\n" , |
437 | (long)bcom_eng->regs_base); |
438 | |
439 | return 0; |
440 | |
441 | /* Error path */ |
442 | error_unmap: |
443 | iounmap(addr: bcom_eng->regs); |
444 | error_release: |
445 | release_mem_region(res_bcom.start, sizeof(struct mpc52xx_sdma)); |
446 | error_sramclean: |
447 | kfree(objp: bcom_eng); |
448 | bcom_sram_cleanup(); |
449 | error_ofput: |
450 | of_node_put(node: op->dev.of_node); |
451 | |
452 | printk(KERN_ERR "DMA: MPC52xx BestComm init failed !\n" ); |
453 | |
454 | return rv; |
455 | } |
456 | |
457 | |
458 | static void mpc52xx_bcom_remove(struct platform_device *op) |
459 | { |
460 | /* Clean up the engine */ |
461 | bcom_engine_cleanup(); |
462 | |
463 | /* Cleanup SRAM */ |
464 | bcom_sram_cleanup(); |
465 | |
466 | /* Release regs */ |
467 | iounmap(addr: bcom_eng->regs); |
468 | release_mem_region(bcom_eng->regs_base, sizeof(struct mpc52xx_sdma)); |
469 | |
470 | /* Release the node */ |
471 | of_node_put(node: bcom_eng->ofnode); |
472 | |
473 | /* Release memory */ |
474 | kfree(objp: bcom_eng); |
475 | bcom_eng = NULL; |
476 | } |
477 | |
478 | static const struct of_device_id mpc52xx_bcom_of_match[] = { |
479 | { .compatible = "fsl,mpc5200-bestcomm" , }, |
480 | { .compatible = "mpc5200-bestcomm" , }, |
481 | {}, |
482 | }; |
483 | |
484 | MODULE_DEVICE_TABLE(of, mpc52xx_bcom_of_match); |
485 | |
486 | |
487 | static struct platform_driver mpc52xx_bcom_of_platform_driver = { |
488 | .probe = mpc52xx_bcom_probe, |
489 | .remove_new = mpc52xx_bcom_remove, |
490 | .driver = { |
491 | .name = DRIVER_NAME, |
492 | .of_match_table = mpc52xx_bcom_of_match, |
493 | }, |
494 | }; |
495 | |
496 | |
497 | /* ======================================================================== */ |
498 | /* Module */ |
499 | /* ======================================================================== */ |
500 | |
501 | static int __init |
502 | mpc52xx_bcom_init(void) |
503 | { |
504 | return platform_driver_register(&mpc52xx_bcom_of_platform_driver); |
505 | } |
506 | |
507 | static void __exit |
508 | mpc52xx_bcom_exit(void) |
509 | { |
510 | platform_driver_unregister(&mpc52xx_bcom_of_platform_driver); |
511 | } |
512 | |
513 | /* If we're not a module, we must make sure everything is setup before */ |
514 | /* anyone tries to use us ... that's why we use subsys_initcall instead */ |
515 | /* of module_init. */ |
516 | subsys_initcall(mpc52xx_bcom_init); |
517 | module_exit(mpc52xx_bcom_exit); |
518 | |
519 | MODULE_DESCRIPTION("Freescale MPC52xx BestComm DMA" ); |
520 | MODULE_AUTHOR("Sylvain Munaut <tnt@246tNt.com>" ); |
521 | MODULE_AUTHOR("Andrey Volkov <avolkov@varma-el.com>" ); |
522 | MODULE_AUTHOR("Dale Farnsworth <dfarnsworth@mvista.com>" ); |
523 | MODULE_LICENSE("GPL v2" ); |
524 | |