Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
d6be34fb JL |
2 | /* |
3 | * drivers/dma/fsl-edma.c | |
4 | * | |
5 | * Copyright 2013-2014 Freescale Semiconductor, Inc. | |
6 | * | |
7 | * Driver for the Freescale eDMA engine with flexible channel multiplexing | |
8 | * capability for DMA request sources. The eDMA block can be found on some | |
9 | * Vybrid and Layerscape SoCs. | |
d6be34fb JL |
10 | */ |
11 | ||
d6be34fb JL |
12 | #include <linux/module.h> |
13 | #include <linux/interrupt.h> | |
14 | #include <linux/clk.h> | |
d6be34fb JL |
15 | #include <linux/of.h> |
16 | #include <linux/of_device.h> | |
17 | #include <linux/of_address.h> | |
18 | #include <linux/of_irq.h> | |
19 | #include <linux/of_dma.h> | |
20 | ||
9d831528 | 21 | #include "fsl-edma-common.h" |
d6be34fb JL |
22 | |
23 | static irqreturn_t fsl_edma_tx_handler(int irq, void *dev_id) | |
24 | { | |
25 | struct fsl_edma_engine *fsl_edma = dev_id; | |
26 | unsigned int intr, ch; | |
377eaf3b | 27 | struct edma_regs *regs = &fsl_edma->regs; |
d6be34fb JL |
28 | struct fsl_edma_chan *fsl_chan; |
29 | ||
377eaf3b | 30 | intr = edma_readl(fsl_edma, regs->intl); |
d6be34fb JL |
31 | if (!intr) |
32 | return IRQ_NONE; | |
33 | ||
34 | for (ch = 0; ch < fsl_edma->n_chans; ch++) { | |
35 | if (intr & (0x1 << ch)) { | |
377eaf3b | 36 | edma_writeb(fsl_edma, EDMA_CINT_CINT(ch), regs->cint); |
d6be34fb JL |
37 | |
38 | fsl_chan = &fsl_edma->chans[ch]; | |
39 | ||
40 | spin_lock(&fsl_chan->vchan.lock); | |
41 | if (!fsl_chan->edesc->iscyclic) { | |
42 | list_del(&fsl_chan->edesc->vdesc.node); | |
43 | vchan_cookie_complete(&fsl_chan->edesc->vdesc); | |
44 | fsl_chan->edesc = NULL; | |
45 | fsl_chan->status = DMA_COMPLETE; | |
82d149b8 | 46 | fsl_chan->idle = true; |
d6be34fb JL |
47 | } else { |
48 | vchan_cyclic_callback(&fsl_chan->edesc->vdesc); | |
49 | } | |
50 | ||
51 | if (!fsl_chan->edesc) | |
52 | fsl_edma_xfer_desc(fsl_chan); | |
53 | ||
54 | spin_unlock(&fsl_chan->vchan.lock); | |
55 | } | |
56 | } | |
57 | return IRQ_HANDLED; | |
58 | } | |
59 | ||
60 | static irqreturn_t fsl_edma_err_handler(int irq, void *dev_id) | |
61 | { | |
62 | struct fsl_edma_engine *fsl_edma = dev_id; | |
63 | unsigned int err, ch; | |
377eaf3b | 64 | struct edma_regs *regs = &fsl_edma->regs; |
d6be34fb | 65 | |
377eaf3b | 66 | err = edma_readl(fsl_edma, regs->errl); |
d6be34fb JL |
67 | if (!err) |
68 | return IRQ_NONE; | |
69 | ||
70 | for (ch = 0; ch < fsl_edma->n_chans; ch++) { | |
71 | if (err & (0x1 << ch)) { | |
72 | fsl_edma_disable_request(&fsl_edma->chans[ch]); | |
377eaf3b | 73 | edma_writeb(fsl_edma, EDMA_CERR_CERR(ch), regs->cerr); |
d6be34fb | 74 | fsl_edma->chans[ch].status = DMA_ERROR; |
82d149b8 | 75 | fsl_edma->chans[ch].idle = true; |
d6be34fb JL |
76 | } |
77 | } | |
78 | return IRQ_HANDLED; | |
79 | } | |
80 | ||
81 | static irqreturn_t fsl_edma_irq_handler(int irq, void *dev_id) | |
82 | { | |
83 | if (fsl_edma_tx_handler(irq, dev_id) == IRQ_HANDLED) | |
84 | return IRQ_HANDLED; | |
85 | ||
86 | return fsl_edma_err_handler(irq, dev_id); | |
87 | } | |
88 | ||
d6be34fb JL |
89 | static struct dma_chan *fsl_edma_xlate(struct of_phandle_args *dma_spec, |
90 | struct of_dma *ofdma) | |
91 | { | |
92 | struct fsl_edma_engine *fsl_edma = ofdma->of_dma_data; | |
178c81e5 | 93 | struct dma_chan *chan, *_chan; |
82d149b8 | 94 | struct fsl_edma_chan *fsl_chan; |
af802728 RG |
95 | u32 dmamux_nr = fsl_edma->drvdata->dmamuxs; |
96 | unsigned long chans_per_mux = fsl_edma->n_chans / dmamux_nr; | |
d6be34fb JL |
97 | |
98 | if (dma_spec->args_count != 2) | |
99 | return NULL; | |
100 | ||
101 | mutex_lock(&fsl_edma->fsl_edma_mutex); | |
178c81e5 | 102 | list_for_each_entry_safe(chan, _chan, &fsl_edma->dma_dev.channels, device_node) { |
d6be34fb JL |
103 | if (chan->client_count) |
104 | continue; | |
211bfef7 | 105 | if ((chan->chan_id / chans_per_mux) == dma_spec->args[0]) { |
d6be34fb JL |
106 | chan = dma_get_slave_channel(chan); |
107 | if (chan) { | |
108 | chan->device->privatecnt++; | |
82d149b8 YY |
109 | fsl_chan = to_fsl_edma_chan(chan); |
110 | fsl_chan->slave_id = dma_spec->args[1]; | |
111 | fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, | |
112 | true); | |
d6be34fb JL |
113 | mutex_unlock(&fsl_edma->fsl_edma_mutex); |
114 | return chan; | |
115 | } | |
116 | } | |
117 | } | |
118 | mutex_unlock(&fsl_edma->fsl_edma_mutex); | |
119 | return NULL; | |
120 | } | |
121 | ||
d6be34fb JL |
122 | static int |
123 | fsl_edma_irq_init(struct platform_device *pdev, struct fsl_edma_engine *fsl_edma) | |
124 | { | |
125 | int ret; | |
126 | ||
127 | fsl_edma->txirq = platform_get_irq_byname(pdev, "edma-tx"); | |
128 | if (fsl_edma->txirq < 0) { | |
129 | dev_err(&pdev->dev, "Can't get edma-tx irq.\n"); | |
130 | return fsl_edma->txirq; | |
131 | } | |
132 | ||
133 | fsl_edma->errirq = platform_get_irq_byname(pdev, "edma-err"); | |
134 | if (fsl_edma->errirq < 0) { | |
135 | dev_err(&pdev->dev, "Can't get edma-err irq.\n"); | |
136 | return fsl_edma->errirq; | |
137 | } | |
138 | ||
139 | if (fsl_edma->txirq == fsl_edma->errirq) { | |
140 | ret = devm_request_irq(&pdev->dev, fsl_edma->txirq, | |
141 | fsl_edma_irq_handler, 0, "eDMA", fsl_edma); | |
142 | if (ret) { | |
143 | dev_err(&pdev->dev, "Can't register eDMA IRQ.\n"); | |
e095189a | 144 | return ret; |
d6be34fb JL |
145 | } |
146 | } else { | |
147 | ret = devm_request_irq(&pdev->dev, fsl_edma->txirq, | |
148 | fsl_edma_tx_handler, 0, "eDMA tx", fsl_edma); | |
149 | if (ret) { | |
150 | dev_err(&pdev->dev, "Can't register eDMA tx IRQ.\n"); | |
e095189a | 151 | return ret; |
d6be34fb JL |
152 | } |
153 | ||
154 | ret = devm_request_irq(&pdev->dev, fsl_edma->errirq, | |
155 | fsl_edma_err_handler, 0, "eDMA err", fsl_edma); | |
156 | if (ret) { | |
157 | dev_err(&pdev->dev, "Can't register eDMA err IRQ.\n"); | |
e095189a | 158 | return ret; |
d6be34fb JL |
159 | } |
160 | } | |
161 | ||
162 | return 0; | |
163 | } | |
164 | ||
476c7c80 VK |
165 | static void fsl_edma_irq_exit( |
166 | struct platform_device *pdev, struct fsl_edma_engine *fsl_edma) | |
167 | { | |
168 | if (fsl_edma->txirq == fsl_edma->errirq) { | |
169 | devm_free_irq(&pdev->dev, fsl_edma->txirq, fsl_edma); | |
170 | } else { | |
171 | devm_free_irq(&pdev->dev, fsl_edma->txirq, fsl_edma); | |
172 | devm_free_irq(&pdev->dev, fsl_edma->errirq, fsl_edma); | |
173 | } | |
174 | } | |
175 | ||
2610acf4 | 176 | static void fsl_disable_clocks(struct fsl_edma_engine *fsl_edma, int nr_clocks) |
5e2fe1e7 PG |
177 | { |
178 | int i; | |
179 | ||
2610acf4 | 180 | for (i = 0; i < nr_clocks; i++) |
5e2fe1e7 PG |
181 | clk_disable_unprepare(fsl_edma->muxclk[i]); |
182 | } | |
183 | ||
af802728 RG |
184 | static struct fsl_edma_drvdata vf610_data = { |
185 | .version = v1, | |
186 | .dmamuxs = DMAMUX_NR, | |
187 | .setup_irq = fsl_edma_irq_init, | |
188 | }; | |
189 | ||
190 | static const struct of_device_id fsl_edma_dt_ids[] = { | |
191 | { .compatible = "fsl,vf610-edma", .data = &vf610_data}, | |
192 | { /* sentinel */ } | |
193 | }; | |
194 | MODULE_DEVICE_TABLE(of, fsl_edma_dt_ids); | |
195 | ||
d6be34fb JL |
196 | static int fsl_edma_probe(struct platform_device *pdev) |
197 | { | |
af802728 RG |
198 | const struct of_device_id *of_id = |
199 | of_match_device(fsl_edma_dt_ids, &pdev->dev); | |
d6be34fb JL |
200 | struct device_node *np = pdev->dev.of_node; |
201 | struct fsl_edma_engine *fsl_edma; | |
af802728 | 202 | const struct fsl_edma_drvdata *drvdata = NULL; |
d6be34fb | 203 | struct fsl_edma_chan *fsl_chan; |
377eaf3b | 204 | struct edma_regs *regs; |
d6be34fb JL |
205 | struct resource *res; |
206 | int len, chans; | |
207 | int ret, i; | |
208 | ||
af802728 RG |
209 | if (of_id) |
210 | drvdata = of_id->data; | |
211 | if (!drvdata) { | |
212 | dev_err(&pdev->dev, "unable to find driver data\n"); | |
213 | return -EINVAL; | |
214 | } | |
215 | ||
d6be34fb JL |
216 | ret = of_property_read_u32(np, "dma-channels", &chans); |
217 | if (ret) { | |
218 | dev_err(&pdev->dev, "Can't get dma-channels.\n"); | |
219 | return ret; | |
220 | } | |
221 | ||
222 | len = sizeof(*fsl_edma) + sizeof(*fsl_chan) * chans; | |
223 | fsl_edma = devm_kzalloc(&pdev->dev, len, GFP_KERNEL); | |
224 | if (!fsl_edma) | |
225 | return -ENOMEM; | |
226 | ||
af802728 | 227 | fsl_edma->drvdata = drvdata; |
d6be34fb JL |
228 | fsl_edma->n_chans = chans; |
229 | mutex_init(&fsl_edma->fsl_edma_mutex); | |
230 | ||
231 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
232 | fsl_edma->membase = devm_ioremap_resource(&pdev->dev, res); | |
233 | if (IS_ERR(fsl_edma->membase)) | |
234 | return PTR_ERR(fsl_edma->membase); | |
235 | ||
377eaf3b AD |
236 | fsl_edma_setup_regs(fsl_edma); |
237 | regs = &fsl_edma->regs; | |
238 | ||
af802728 | 239 | for (i = 0; i < fsl_edma->drvdata->dmamuxs; i++) { |
d6be34fb JL |
240 | char clkname[32]; |
241 | ||
242 | res = platform_get_resource(pdev, IORESOURCE_MEM, 1 + i); | |
243 | fsl_edma->muxbase[i] = devm_ioremap_resource(&pdev->dev, res); | |
2610acf4 AP |
244 | if (IS_ERR(fsl_edma->muxbase[i])) { |
245 | /* on error: disable all previously enabled clks */ | |
246 | fsl_disable_clocks(fsl_edma, i); | |
d6be34fb | 247 | return PTR_ERR(fsl_edma->muxbase[i]); |
2610acf4 | 248 | } |
d6be34fb JL |
249 | |
250 | sprintf(clkname, "dmamux%d", i); | |
251 | fsl_edma->muxclk[i] = devm_clk_get(&pdev->dev, clkname); | |
252 | if (IS_ERR(fsl_edma->muxclk[i])) { | |
253 | dev_err(&pdev->dev, "Missing DMAMUX block clock.\n"); | |
2610acf4 AP |
254 | /* on error: disable all previously enabled clks */ |
255 | fsl_disable_clocks(fsl_edma, i); | |
d6be34fb JL |
256 | return PTR_ERR(fsl_edma->muxclk[i]); |
257 | } | |
258 | ||
259 | ret = clk_prepare_enable(fsl_edma->muxclk[i]); | |
2610acf4 AP |
260 | if (ret) |
261 | /* on error: disable all previously enabled clks */ | |
262 | fsl_disable_clocks(fsl_edma, i); | |
d6be34fb JL |
263 | |
264 | } | |
265 | ||
d6be34fb JL |
266 | fsl_edma->big_endian = of_property_read_bool(np, "big-endian"); |
267 | ||
268 | INIT_LIST_HEAD(&fsl_edma->dma_dev.channels); | |
269 | for (i = 0; i < fsl_edma->n_chans; i++) { | |
270 | struct fsl_edma_chan *fsl_chan = &fsl_edma->chans[i]; | |
271 | ||
272 | fsl_chan->edma = fsl_edma; | |
82d149b8 YY |
273 | fsl_chan->pm_state = RUNNING; |
274 | fsl_chan->slave_id = 0; | |
275 | fsl_chan->idle = true; | |
0fa89f97 | 276 | fsl_chan->dma_dir = DMA_NONE; |
d6be34fb JL |
277 | fsl_chan->vchan.desc_free = fsl_edma_free_desc; |
278 | vchan_init(&fsl_chan->vchan, &fsl_edma->dma_dev); | |
279 | ||
377eaf3b | 280 | edma_writew(fsl_edma, 0x0, ®s->tcd[i].csr); |
d6be34fb JL |
281 | fsl_edma_chan_mux(fsl_chan, 0, false); |
282 | } | |
283 | ||
377eaf3b | 284 | edma_writel(fsl_edma, ~0, regs->intl); |
af802728 | 285 | ret = fsl_edma->drvdata->setup_irq(pdev, fsl_edma); |
0fe25d61 SA |
286 | if (ret) |
287 | return ret; | |
288 | ||
d6be34fb JL |
289 | dma_cap_set(DMA_PRIVATE, fsl_edma->dma_dev.cap_mask); |
290 | dma_cap_set(DMA_SLAVE, fsl_edma->dma_dev.cap_mask); | |
291 | dma_cap_set(DMA_CYCLIC, fsl_edma->dma_dev.cap_mask); | |
292 | ||
293 | fsl_edma->dma_dev.dev = &pdev->dev; | |
294 | fsl_edma->dma_dev.device_alloc_chan_resources | |
295 | = fsl_edma_alloc_chan_resources; | |
296 | fsl_edma->dma_dev.device_free_chan_resources | |
297 | = fsl_edma_free_chan_resources; | |
298 | fsl_edma->dma_dev.device_tx_status = fsl_edma_tx_status; | |
299 | fsl_edma->dma_dev.device_prep_slave_sg = fsl_edma_prep_slave_sg; | |
300 | fsl_edma->dma_dev.device_prep_dma_cyclic = fsl_edma_prep_dma_cyclic; | |
d80f381f MR |
301 | fsl_edma->dma_dev.device_config = fsl_edma_slave_config; |
302 | fsl_edma->dma_dev.device_pause = fsl_edma_pause; | |
303 | fsl_edma->dma_dev.device_resume = fsl_edma_resume; | |
304 | fsl_edma->dma_dev.device_terminate_all = fsl_edma_terminate_all; | |
d6be34fb | 305 | fsl_edma->dma_dev.device_issue_pending = fsl_edma_issue_pending; |
f45c4311 MR |
306 | |
307 | fsl_edma->dma_dev.src_addr_widths = FSL_EDMA_BUSWIDTHS; | |
308 | fsl_edma->dma_dev.dst_addr_widths = FSL_EDMA_BUSWIDTHS; | |
309 | fsl_edma->dma_dev.directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV); | |
d6be34fb JL |
310 | |
311 | platform_set_drvdata(pdev, fsl_edma); | |
312 | ||
313 | ret = dma_async_device_register(&fsl_edma->dma_dev); | |
314 | if (ret) { | |
a86144da PG |
315 | dev_err(&pdev->dev, |
316 | "Can't register Freescale eDMA engine. (%d)\n", ret); | |
af802728 | 317 | fsl_disable_clocks(fsl_edma, fsl_edma->drvdata->dmamuxs); |
d6be34fb JL |
318 | return ret; |
319 | } | |
320 | ||
321 | ret = of_dma_controller_register(np, fsl_edma_xlate, fsl_edma); | |
322 | if (ret) { | |
a86144da PG |
323 | dev_err(&pdev->dev, |
324 | "Can't register Freescale eDMA of_dma. (%d)\n", ret); | |
d6be34fb | 325 | dma_async_device_unregister(&fsl_edma->dma_dev); |
af802728 | 326 | fsl_disable_clocks(fsl_edma, fsl_edma->drvdata->dmamuxs); |
d6be34fb JL |
327 | return ret; |
328 | } | |
329 | ||
330 | /* enable round robin arbitration */ | |
377eaf3b | 331 | edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA, regs->cr); |
d6be34fb JL |
332 | |
333 | return 0; | |
334 | } | |
335 | ||
336 | static int fsl_edma_remove(struct platform_device *pdev) | |
337 | { | |
338 | struct device_node *np = pdev->dev.of_node; | |
339 | struct fsl_edma_engine *fsl_edma = platform_get_drvdata(pdev); | |
d6be34fb | 340 | |
476c7c80 | 341 | fsl_edma_irq_exit(pdev, fsl_edma); |
6f93b93b | 342 | fsl_edma_cleanup_vchan(&fsl_edma->dma_dev); |
d6be34fb JL |
343 | of_dma_controller_free(np); |
344 | dma_async_device_unregister(&fsl_edma->dma_dev); | |
af802728 | 345 | fsl_disable_clocks(fsl_edma, fsl_edma->drvdata->dmamuxs); |
d6be34fb JL |
346 | |
347 | return 0; | |
348 | } | |
349 | ||
82d149b8 YY |
350 | static int fsl_edma_suspend_late(struct device *dev) |
351 | { | |
352 | struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); | |
353 | struct fsl_edma_chan *fsl_chan; | |
354 | unsigned long flags; | |
355 | int i; | |
356 | ||
357 | for (i = 0; i < fsl_edma->n_chans; i++) { | |
358 | fsl_chan = &fsl_edma->chans[i]; | |
359 | spin_lock_irqsave(&fsl_chan->vchan.lock, flags); | |
360 | /* Make sure chan is idle or will force disable. */ | |
361 | if (unlikely(!fsl_chan->idle)) { | |
362 | dev_warn(dev, "WARN: There is non-idle channel."); | |
363 | fsl_edma_disable_request(fsl_chan); | |
364 | fsl_edma_chan_mux(fsl_chan, 0, false); | |
365 | } | |
366 | ||
367 | fsl_chan->pm_state = SUSPENDED; | |
368 | spin_unlock_irqrestore(&fsl_chan->vchan.lock, flags); | |
369 | } | |
370 | ||
371 | return 0; | |
372 | } | |
373 | ||
374 | static int fsl_edma_resume_early(struct device *dev) | |
375 | { | |
376 | struct fsl_edma_engine *fsl_edma = dev_get_drvdata(dev); | |
377 | struct fsl_edma_chan *fsl_chan; | |
377eaf3b | 378 | struct edma_regs *regs = &fsl_edma->regs; |
82d149b8 YY |
379 | int i; |
380 | ||
381 | for (i = 0; i < fsl_edma->n_chans; i++) { | |
382 | fsl_chan = &fsl_edma->chans[i]; | |
383 | fsl_chan->pm_state = RUNNING; | |
377eaf3b | 384 | edma_writew(fsl_edma, 0x0, ®s->tcd[i].csr); |
82d149b8 YY |
385 | if (fsl_chan->slave_id != 0) |
386 | fsl_edma_chan_mux(fsl_chan, fsl_chan->slave_id, true); | |
387 | } | |
388 | ||
377eaf3b | 389 | edma_writel(fsl_edma, EDMA_CR_ERGA | EDMA_CR_ERCA, regs->cr); |
82d149b8 YY |
390 | |
391 | return 0; | |
392 | } | |
393 | ||
394 | /* | |
395 | * eDMA provides the service to others, so it should be suspend late | |
396 | * and resume early. When eDMA suspend, all of the clients should stop | |
397 | * the DMA data transmission and let the channel idle. | |
398 | */ | |
399 | static const struct dev_pm_ops fsl_edma_pm_ops = { | |
400 | .suspend_late = fsl_edma_suspend_late, | |
401 | .resume_early = fsl_edma_resume_early, | |
402 | }; | |
403 | ||
d6be34fb JL |
404 | static struct platform_driver fsl_edma_driver = { |
405 | .driver = { | |
406 | .name = "fsl-edma", | |
d6be34fb | 407 | .of_match_table = fsl_edma_dt_ids, |
82d149b8 | 408 | .pm = &fsl_edma_pm_ops, |
d6be34fb JL |
409 | }, |
410 | .probe = fsl_edma_probe, | |
411 | .remove = fsl_edma_remove, | |
412 | }; | |
413 | ||
8edc51c1 YY |
414 | static int __init fsl_edma_init(void) |
415 | { | |
416 | return platform_driver_register(&fsl_edma_driver); | |
417 | } | |
418 | subsys_initcall(fsl_edma_init); | |
419 | ||
420 | static void __exit fsl_edma_exit(void) | |
421 | { | |
422 | platform_driver_unregister(&fsl_edma_driver); | |
423 | } | |
424 | module_exit(fsl_edma_exit); | |
d6be34fb JL |
425 | |
426 | MODULE_ALIAS("platform:fsl-edma"); | |
427 | MODULE_DESCRIPTION("Freescale eDMA engine driver"); | |
428 | MODULE_LICENSE("GPL v2"); |