diff options
author | Shawn Guo <shawn.guo@freescale.com> | 2011-02-26 11:47:42 -0500 |
---|---|---|
committer | Vinod Koul <vinod.koul@intel.com> | 2011-03-01 20:36:27 -0500 |
commit | a580b8c5429a624d120cd603e1498bf676e2b4da (patch) | |
tree | 14e4bd82f203bf9f43fa19341d85d993e5a4c569 | |
parent | 26d890f0d09fd58f7194aad651e86283cb9e6574 (diff) |
dmaengine: mxs-dma: add dma support for i.MX23/28
This patch adds dma support for Freescale MXS-based SoC i.MX23/28,
including apbh-dma and apbx-dma.
* apbh-dma and apbx-dma are supported in the driver as two mxs-dma
instances.
* apbh-dma is different between mx23 and mx28, hardware version
register is used to differentiate.
* mxs-dma supports pio function besides data transfer. The driver
uses dma_data_direction DMA_NONE to identify the pio mode, and
steals sgl and sg_len to get pio words and numbers from clients.
* mxs dmaengine has some very specific features, like sense function
and the special NAND support (nand_lock, nand_wait4ready). These
are too specific to implemented in generic dmaengine driver.
* The driver refers to imx-sdma and only a single descriptor is
statically assigned to each channel.
Signed-off-by: Shawn Guo <shawn.guo@freescale.com>
Signed-off-by: Vinod Koul <vinod.koul@intel.com>
-rw-r--r-- | arch/arm/mach-mxs/include/mach/dma.h | 26 | ||||
-rw-r--r-- | drivers/dma/Kconfig | 8 | ||||
-rw-r--r-- | drivers/dma/Makefile | 1 | ||||
-rw-r--r-- | drivers/dma/mxs-dma.c | 724 |
4 files changed, 759 insertions, 0 deletions
diff --git a/arch/arm/mach-mxs/include/mach/dma.h b/arch/arm/mach-mxs/include/mach/dma.h new file mode 100644 index 000000000000..7f4aeeaba8df --- /dev/null +++ b/arch/arm/mach-mxs/include/mach/dma.h | |||
@@ -0,0 +1,26 @@ | |||
1 | /* | ||
2 | * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License version 2 as | ||
6 | * published by the Free Software Foundation. | ||
7 | */ | ||
8 | |||
9 | #ifndef __MACH_MXS_DMA_H__ | ||
10 | #define __MACH_MXS_DMA_H__ | ||
11 | |||
12 | struct mxs_dma_data { | ||
13 | int chan_irq; | ||
14 | }; | ||
15 | |||
16 | static inline int mxs_dma_is_apbh(struct dma_chan *chan) | ||
17 | { | ||
18 | return !strcmp(dev_name(chan->device->dev), "mxs-dma-apbh"); | ||
19 | } | ||
20 | |||
21 | static inline int mxs_dma_is_apbx(struct dma_chan *chan) | ||
22 | { | ||
23 | return !strcmp(dev_name(chan->device->dev), "mxs-dma-apbx"); | ||
24 | } | ||
25 | |||
26 | #endif /* __MACH_MXS_DMA_H__ */ | ||
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index 1c28816152fa..76f6472ba315 100644 --- a/drivers/dma/Kconfig +++ b/drivers/dma/Kconfig | |||
@@ -227,6 +227,14 @@ config IMX_DMA | |||
227 | Support the i.MX DMA engine. This engine is integrated into | 227 | Support the i.MX DMA engine. This engine is integrated into |
228 | Freescale i.MX1/21/27 chips. | 228 | Freescale i.MX1/21/27 chips. |
229 | 229 | ||
230 | config MXS_DMA | ||
231 | bool "MXS DMA support" | ||
232 | depends on SOC_IMX23 || SOC_IMX28 | ||
233 | select DMA_ENGINE | ||
234 | help | ||
235 | Support the MXS DMA engine. This engine including APBH-DMA | ||
236 | and APBX-DMA is integrated into Freescale i.MX23/28 chips. | ||
237 | |||
230 | config DMA_ENGINE | 238 | config DMA_ENGINE |
231 | bool | 239 | bool |
232 | 240 | ||
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile index 64b21f5cd740..802b55795f8b 100644 --- a/drivers/dma/Makefile +++ b/drivers/dma/Makefile | |||
@@ -23,6 +23,7 @@ obj-$(CONFIG_COH901318) += coh901318.o coh901318_lli.o | |||
23 | obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ | 23 | obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/ |
24 | obj-$(CONFIG_IMX_SDMA) += imx-sdma.o | 24 | obj-$(CONFIG_IMX_SDMA) += imx-sdma.o |
25 | obj-$(CONFIG_IMX_DMA) += imx-dma.o | 25 | obj-$(CONFIG_IMX_DMA) += imx-dma.o |
26 | obj-$(CONFIG_MXS_DMA) += mxs-dma.o | ||
26 | obj-$(CONFIG_TIMB_DMA) += timb_dma.o | 27 | obj-$(CONFIG_TIMB_DMA) += timb_dma.o |
27 | obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o | 28 | obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o |
28 | obj-$(CONFIG_PL330_DMA) += pl330.o | 29 | obj-$(CONFIG_PL330_DMA) += pl330.o |
diff --git a/drivers/dma/mxs-dma.c b/drivers/dma/mxs-dma.c new file mode 100644 index 000000000000..88aad4f54002 --- /dev/null +++ b/drivers/dma/mxs-dma.c | |||
@@ -0,0 +1,724 @@ | |||
1 | /* | ||
2 | * Copyright 2011 Freescale Semiconductor, Inc. All Rights Reserved. | ||
3 | * | ||
4 | * Refer to drivers/dma/imx-sdma.c | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License version 2 as | ||
8 | * published by the Free Software Foundation. | ||
9 | */ | ||
10 | |||
11 | #include <linux/init.h> | ||
12 | #include <linux/types.h> | ||
13 | #include <linux/mm.h> | ||
14 | #include <linux/interrupt.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/wait.h> | ||
17 | #include <linux/sched.h> | ||
18 | #include <linux/semaphore.h> | ||
19 | #include <linux/device.h> | ||
20 | #include <linux/dma-mapping.h> | ||
21 | #include <linux/slab.h> | ||
22 | #include <linux/platform_device.h> | ||
23 | #include <linux/dmaengine.h> | ||
24 | #include <linux/delay.h> | ||
25 | |||
26 | #include <asm/irq.h> | ||
27 | #include <mach/mxs.h> | ||
28 | #include <mach/dma.h> | ||
29 | #include <mach/common.h> | ||
30 | |||
31 | /* | ||
32 | * NOTE: The term "PIO" throughout the mxs-dma implementation means | ||
33 | * PIO mode of mxs apbh-dma and apbx-dma. With this working mode, | ||
34 | * dma can program the controller registers of peripheral devices. | ||
35 | */ | ||
36 | |||
37 | #define MXS_DMA_APBH 0 | ||
38 | #define MXS_DMA_APBX 1 | ||
39 | #define dma_is_apbh() (mxs_dma->dev_id == MXS_DMA_APBH) | ||
40 | |||
41 | #define APBH_VERSION_LATEST 3 | ||
42 | #define apbh_is_old() (mxs_dma->version < APBH_VERSION_LATEST) | ||
43 | |||
44 | #define HW_APBHX_CTRL0 0x000 | ||
45 | #define BM_APBH_CTRL0_APB_BURST8_EN (1 << 29) | ||
46 | #define BM_APBH_CTRL0_APB_BURST_EN (1 << 28) | ||
47 | #define BP_APBH_CTRL0_CLKGATE_CHANNEL 8 | ||
48 | #define BP_APBH_CTRL0_RESET_CHANNEL 16 | ||
49 | #define HW_APBHX_CTRL1 0x010 | ||
50 | #define HW_APBHX_CTRL2 0x020 | ||
51 | #define HW_APBHX_CHANNEL_CTRL 0x030 | ||
52 | #define BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL 16 | ||
53 | #define HW_APBH_VERSION (cpu_is_mx23() ? 0x3f0 : 0x800) | ||
54 | #define HW_APBX_VERSION 0x800 | ||
55 | #define BP_APBHX_VERSION_MAJOR 24 | ||
56 | #define HW_APBHX_CHn_NXTCMDAR(n) \ | ||
57 | (((dma_is_apbh() && apbh_is_old()) ? 0x050 : 0x110) + (n) * 0x70) | ||
58 | #define HW_APBHX_CHn_SEMA(n) \ | ||
59 | (((dma_is_apbh() && apbh_is_old()) ? 0x080 : 0x140) + (n) * 0x70) | ||
60 | |||
61 | /* | ||
62 | * ccw bits definitions | ||
63 | * | ||
64 | * COMMAND: 0..1 (2) | ||
65 | * CHAIN: 2 (1) | ||
66 | * IRQ: 3 (1) | ||
67 | * NAND_LOCK: 4 (1) - not implemented | ||
68 | * NAND_WAIT4READY: 5 (1) - not implemented | ||
69 | * DEC_SEM: 6 (1) | ||
70 | * WAIT4END: 7 (1) | ||
71 | * HALT_ON_TERMINATE: 8 (1) | ||
72 | * TERMINATE_FLUSH: 9 (1) | ||
73 | * RESERVED: 10..11 (2) | ||
74 | * PIO_NUM: 12..15 (4) | ||
75 | */ | ||
76 | #define BP_CCW_COMMAND 0 | ||
77 | #define BM_CCW_COMMAND (3 << 0) | ||
78 | #define CCW_CHAIN (1 << 2) | ||
79 | #define CCW_IRQ (1 << 3) | ||
80 | #define CCW_DEC_SEM (1 << 6) | ||
81 | #define CCW_WAIT4END (1 << 7) | ||
82 | #define CCW_HALT_ON_TERM (1 << 8) | ||
83 | #define CCW_TERM_FLUSH (1 << 9) | ||
84 | #define BP_CCW_PIO_NUM 12 | ||
85 | #define BM_CCW_PIO_NUM (0xf << 12) | ||
86 | |||
87 | #define BF_CCW(value, field) (((value) << BP_CCW_##field) & BM_CCW_##field) | ||
88 | |||
89 | #define MXS_DMA_CMD_NO_XFER 0 | ||
90 | #define MXS_DMA_CMD_WRITE 1 | ||
91 | #define MXS_DMA_CMD_READ 2 | ||
92 | #define MXS_DMA_CMD_DMA_SENSE 3 /* not implemented */ | ||
93 | |||
94 | struct mxs_dma_ccw { | ||
95 | u32 next; | ||
96 | u16 bits; | ||
97 | u16 xfer_bytes; | ||
98 | #define MAX_XFER_BYTES 0xff00 | ||
99 | u32 bufaddr; | ||
100 | #define MXS_PIO_WORDS 16 | ||
101 | u32 pio_words[MXS_PIO_WORDS]; | ||
102 | }; | ||
103 | |||
104 | #define NUM_CCW (int)(PAGE_SIZE / sizeof(struct mxs_dma_ccw)) | ||
105 | |||
106 | struct mxs_dma_chan { | ||
107 | struct mxs_dma_engine *mxs_dma; | ||
108 | struct dma_chan chan; | ||
109 | struct dma_async_tx_descriptor desc; | ||
110 | struct tasklet_struct tasklet; | ||
111 | int chan_irq; | ||
112 | struct mxs_dma_ccw *ccw; | ||
113 | dma_addr_t ccw_phys; | ||
114 | dma_cookie_t last_completed; | ||
115 | enum dma_status status; | ||
116 | unsigned int flags; | ||
117 | #define MXS_DMA_SG_LOOP (1 << 0) | ||
118 | }; | ||
119 | |||
120 | #define MXS_DMA_CHANNELS 16 | ||
121 | #define MXS_DMA_CHANNELS_MASK 0xffff | ||
122 | |||
123 | struct mxs_dma_engine { | ||
124 | int dev_id; | ||
125 | unsigned int version; | ||
126 | void __iomem *base; | ||
127 | struct clk *clk; | ||
128 | struct dma_device dma_device; | ||
129 | struct device_dma_parameters dma_parms; | ||
130 | struct mxs_dma_chan mxs_chans[MXS_DMA_CHANNELS]; | ||
131 | }; | ||
132 | |||
133 | static void mxs_dma_reset_chan(struct mxs_dma_chan *mxs_chan) | ||
134 | { | ||
135 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
136 | int chan_id = mxs_chan->chan.chan_id; | ||
137 | |||
138 | if (dma_is_apbh() && apbh_is_old()) | ||
139 | writel(1 << (chan_id + BP_APBH_CTRL0_RESET_CHANNEL), | ||
140 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
141 | else | ||
142 | writel(1 << (chan_id + BP_APBHX_CHANNEL_CTRL_RESET_CHANNEL), | ||
143 | mxs_dma->base + HW_APBHX_CHANNEL_CTRL + MXS_SET_ADDR); | ||
144 | } | ||
145 | |||
146 | static void mxs_dma_enable_chan(struct mxs_dma_chan *mxs_chan) | ||
147 | { | ||
148 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
149 | int chan_id = mxs_chan->chan.chan_id; | ||
150 | |||
151 | /* set cmd_addr up */ | ||
152 | writel(mxs_chan->ccw_phys, | ||
153 | mxs_dma->base + HW_APBHX_CHn_NXTCMDAR(chan_id)); | ||
154 | |||
155 | /* enable apbh channel clock */ | ||
156 | if (dma_is_apbh()) { | ||
157 | if (apbh_is_old()) | ||
158 | writel(1 << (chan_id + BP_APBH_CTRL0_CLKGATE_CHANNEL), | ||
159 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_CLR_ADDR); | ||
160 | else | ||
161 | writel(1 << chan_id, | ||
162 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_CLR_ADDR); | ||
163 | } | ||
164 | |||
165 | /* write 1 to SEMA to kick off the channel */ | ||
166 | writel(1, mxs_dma->base + HW_APBHX_CHn_SEMA(chan_id)); | ||
167 | } | ||
168 | |||
169 | static void mxs_dma_disable_chan(struct mxs_dma_chan *mxs_chan) | ||
170 | { | ||
171 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
172 | int chan_id = mxs_chan->chan.chan_id; | ||
173 | |||
174 | /* disable apbh channel clock */ | ||
175 | if (dma_is_apbh()) { | ||
176 | if (apbh_is_old()) | ||
177 | writel(1 << (chan_id + BP_APBH_CTRL0_CLKGATE_CHANNEL), | ||
178 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
179 | else | ||
180 | writel(1 << chan_id, | ||
181 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
182 | } | ||
183 | |||
184 | mxs_chan->status = DMA_SUCCESS; | ||
185 | } | ||
186 | |||
187 | static void mxs_dma_pause_chan(struct mxs_dma_chan *mxs_chan) | ||
188 | { | ||
189 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
190 | int chan_id = mxs_chan->chan.chan_id; | ||
191 | |||
192 | /* freeze the channel */ | ||
193 | if (dma_is_apbh() && apbh_is_old()) | ||
194 | writel(1 << chan_id, | ||
195 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
196 | else | ||
197 | writel(1 << chan_id, | ||
198 | mxs_dma->base + HW_APBHX_CHANNEL_CTRL + MXS_SET_ADDR); | ||
199 | |||
200 | mxs_chan->status = DMA_PAUSED; | ||
201 | } | ||
202 | |||
203 | static void mxs_dma_resume_chan(struct mxs_dma_chan *mxs_chan) | ||
204 | { | ||
205 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
206 | int chan_id = mxs_chan->chan.chan_id; | ||
207 | |||
208 | /* unfreeze the channel */ | ||
209 | if (dma_is_apbh() && apbh_is_old()) | ||
210 | writel(1 << chan_id, | ||
211 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_CLR_ADDR); | ||
212 | else | ||
213 | writel(1 << chan_id, | ||
214 | mxs_dma->base + HW_APBHX_CHANNEL_CTRL + MXS_CLR_ADDR); | ||
215 | |||
216 | mxs_chan->status = DMA_IN_PROGRESS; | ||
217 | } | ||
218 | |||
219 | static dma_cookie_t mxs_dma_assign_cookie(struct mxs_dma_chan *mxs_chan) | ||
220 | { | ||
221 | dma_cookie_t cookie = mxs_chan->chan.cookie; | ||
222 | |||
223 | if (++cookie < 0) | ||
224 | cookie = 1; | ||
225 | |||
226 | mxs_chan->chan.cookie = cookie; | ||
227 | mxs_chan->desc.cookie = cookie; | ||
228 | |||
229 | return cookie; | ||
230 | } | ||
231 | |||
232 | static struct mxs_dma_chan *to_mxs_dma_chan(struct dma_chan *chan) | ||
233 | { | ||
234 | return container_of(chan, struct mxs_dma_chan, chan); | ||
235 | } | ||
236 | |||
237 | static dma_cookie_t mxs_dma_tx_submit(struct dma_async_tx_descriptor *tx) | ||
238 | { | ||
239 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(tx->chan); | ||
240 | |||
241 | mxs_dma_enable_chan(mxs_chan); | ||
242 | |||
243 | return mxs_dma_assign_cookie(mxs_chan); | ||
244 | } | ||
245 | |||
246 | static void mxs_dma_tasklet(unsigned long data) | ||
247 | { | ||
248 | struct mxs_dma_chan *mxs_chan = (struct mxs_dma_chan *) data; | ||
249 | |||
250 | if (mxs_chan->desc.callback) | ||
251 | mxs_chan->desc.callback(mxs_chan->desc.callback_param); | ||
252 | } | ||
253 | |||
254 | static irqreturn_t mxs_dma_int_handler(int irq, void *dev_id) | ||
255 | { | ||
256 | struct mxs_dma_engine *mxs_dma = dev_id; | ||
257 | u32 stat1, stat2; | ||
258 | |||
259 | /* completion status */ | ||
260 | stat1 = readl(mxs_dma->base + HW_APBHX_CTRL1); | ||
261 | stat1 &= MXS_DMA_CHANNELS_MASK; | ||
262 | writel(stat1, mxs_dma->base + HW_APBHX_CTRL1 + MXS_CLR_ADDR); | ||
263 | |||
264 | /* error status */ | ||
265 | stat2 = readl(mxs_dma->base + HW_APBHX_CTRL2); | ||
266 | writel(stat2, mxs_dma->base + HW_APBHX_CTRL2 + MXS_CLR_ADDR); | ||
267 | |||
268 | /* | ||
269 | * When both completion and error of termination bits set at the | ||
270 | * same time, we do not take it as an error. IOW, it only becomes | ||
271 | * an error we need to handler here in case of ether it's (1) an bus | ||
272 | * error or (2) a termination error with no completion. | ||
273 | */ | ||
274 | stat2 = ((stat2 >> MXS_DMA_CHANNELS) & stat2) | /* (1) */ | ||
275 | (~(stat2 >> MXS_DMA_CHANNELS) & stat2 & ~stat1); /* (2) */ | ||
276 | |||
277 | /* combine error and completion status for checking */ | ||
278 | stat1 = (stat2 << MXS_DMA_CHANNELS) | stat1; | ||
279 | while (stat1) { | ||
280 | int channel = fls(stat1) - 1; | ||
281 | struct mxs_dma_chan *mxs_chan = | ||
282 | &mxs_dma->mxs_chans[channel % MXS_DMA_CHANNELS]; | ||
283 | |||
284 | if (channel >= MXS_DMA_CHANNELS) { | ||
285 | dev_dbg(mxs_dma->dma_device.dev, | ||
286 | "%s: error in channel %d\n", __func__, | ||
287 | channel - MXS_DMA_CHANNELS); | ||
288 | mxs_chan->status = DMA_ERROR; | ||
289 | mxs_dma_reset_chan(mxs_chan); | ||
290 | } else { | ||
291 | if (mxs_chan->flags & MXS_DMA_SG_LOOP) | ||
292 | mxs_chan->status = DMA_IN_PROGRESS; | ||
293 | else | ||
294 | mxs_chan->status = DMA_SUCCESS; | ||
295 | } | ||
296 | |||
297 | stat1 &= ~(1 << channel); | ||
298 | |||
299 | if (mxs_chan->status == DMA_SUCCESS) | ||
300 | mxs_chan->last_completed = mxs_chan->desc.cookie; | ||
301 | |||
302 | /* schedule tasklet on this channel */ | ||
303 | tasklet_schedule(&mxs_chan->tasklet); | ||
304 | } | ||
305 | |||
306 | return IRQ_HANDLED; | ||
307 | } | ||
308 | |||
309 | static int mxs_dma_alloc_chan_resources(struct dma_chan *chan) | ||
310 | { | ||
311 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
312 | struct mxs_dma_data *data = chan->private; | ||
313 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
314 | int ret; | ||
315 | |||
316 | if (!data) | ||
317 | return -EINVAL; | ||
318 | |||
319 | mxs_chan->chan_irq = data->chan_irq; | ||
320 | |||
321 | mxs_chan->ccw = dma_alloc_coherent(mxs_dma->dma_device.dev, PAGE_SIZE, | ||
322 | &mxs_chan->ccw_phys, GFP_KERNEL); | ||
323 | if (!mxs_chan->ccw) { | ||
324 | ret = -ENOMEM; | ||
325 | goto err_alloc; | ||
326 | } | ||
327 | |||
328 | memset(mxs_chan->ccw, 0, PAGE_SIZE); | ||
329 | |||
330 | ret = request_irq(mxs_chan->chan_irq, mxs_dma_int_handler, | ||
331 | 0, "mxs-dma", mxs_dma); | ||
332 | if (ret) | ||
333 | goto err_irq; | ||
334 | |||
335 | ret = clk_enable(mxs_dma->clk); | ||
336 | if (ret) | ||
337 | goto err_clk; | ||
338 | |||
339 | mxs_dma_reset_chan(mxs_chan); | ||
340 | |||
341 | dma_async_tx_descriptor_init(&mxs_chan->desc, chan); | ||
342 | mxs_chan->desc.tx_submit = mxs_dma_tx_submit; | ||
343 | |||
344 | /* the descriptor is ready */ | ||
345 | async_tx_ack(&mxs_chan->desc); | ||
346 | |||
347 | return 0; | ||
348 | |||
349 | err_clk: | ||
350 | free_irq(mxs_chan->chan_irq, mxs_dma); | ||
351 | err_irq: | ||
352 | dma_free_coherent(mxs_dma->dma_device.dev, PAGE_SIZE, | ||
353 | mxs_chan->ccw, mxs_chan->ccw_phys); | ||
354 | err_alloc: | ||
355 | return ret; | ||
356 | } | ||
357 | |||
358 | static void mxs_dma_free_chan_resources(struct dma_chan *chan) | ||
359 | { | ||
360 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
361 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
362 | |||
363 | mxs_dma_disable_chan(mxs_chan); | ||
364 | |||
365 | free_irq(mxs_chan->chan_irq, mxs_dma); | ||
366 | |||
367 | dma_free_coherent(mxs_dma->dma_device.dev, PAGE_SIZE, | ||
368 | mxs_chan->ccw, mxs_chan->ccw_phys); | ||
369 | |||
370 | clk_disable(mxs_dma->clk); | ||
371 | } | ||
372 | |||
373 | static struct dma_async_tx_descriptor *mxs_dma_prep_slave_sg( | ||
374 | struct dma_chan *chan, struct scatterlist *sgl, | ||
375 | unsigned int sg_len, enum dma_data_direction direction, | ||
376 | unsigned long append) | ||
377 | { | ||
378 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
379 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
380 | struct mxs_dma_ccw *ccw; | ||
381 | struct scatterlist *sg; | ||
382 | int i, j; | ||
383 | u32 *pio; | ||
384 | static int idx; | ||
385 | |||
386 | if (mxs_chan->status == DMA_IN_PROGRESS && !append) | ||
387 | return NULL; | ||
388 | |||
389 | if (sg_len + (append ? idx : 0) > NUM_CCW) { | ||
390 | dev_err(mxs_dma->dma_device.dev, | ||
391 | "maximum number of sg exceeded: %d > %d\n", | ||
392 | sg_len, NUM_CCW); | ||
393 | goto err_out; | ||
394 | } | ||
395 | |||
396 | mxs_chan->status = DMA_IN_PROGRESS; | ||
397 | mxs_chan->flags = 0; | ||
398 | |||
399 | /* | ||
400 | * If the sg is prepared with append flag set, the sg | ||
401 | * will be appended to the last prepared sg. | ||
402 | */ | ||
403 | if (append) { | ||
404 | BUG_ON(idx < 1); | ||
405 | ccw = &mxs_chan->ccw[idx - 1]; | ||
406 | ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * idx; | ||
407 | ccw->bits |= CCW_CHAIN; | ||
408 | ccw->bits &= ~CCW_IRQ; | ||
409 | ccw->bits &= ~CCW_DEC_SEM; | ||
410 | ccw->bits &= ~CCW_WAIT4END; | ||
411 | } else { | ||
412 | idx = 0; | ||
413 | } | ||
414 | |||
415 | if (direction == DMA_NONE) { | ||
416 | ccw = &mxs_chan->ccw[idx++]; | ||
417 | pio = (u32 *) sgl; | ||
418 | |||
419 | for (j = 0; j < sg_len;) | ||
420 | ccw->pio_words[j++] = *pio++; | ||
421 | |||
422 | ccw->bits = 0; | ||
423 | ccw->bits |= CCW_IRQ; | ||
424 | ccw->bits |= CCW_DEC_SEM; | ||
425 | ccw->bits |= CCW_WAIT4END; | ||
426 | ccw->bits |= CCW_HALT_ON_TERM; | ||
427 | ccw->bits |= CCW_TERM_FLUSH; | ||
428 | ccw->bits |= BF_CCW(sg_len, PIO_NUM); | ||
429 | ccw->bits |= BF_CCW(MXS_DMA_CMD_NO_XFER, COMMAND); | ||
430 | } else { | ||
431 | for_each_sg(sgl, sg, sg_len, i) { | ||
432 | if (sg->length > MAX_XFER_BYTES) { | ||
433 | dev_err(mxs_dma->dma_device.dev, "maximum bytes for sg entry exceeded: %d > %d\n", | ||
434 | sg->length, MAX_XFER_BYTES); | ||
435 | goto err_out; | ||
436 | } | ||
437 | |||
438 | ccw = &mxs_chan->ccw[idx++]; | ||
439 | |||
440 | ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * idx; | ||
441 | ccw->bufaddr = sg->dma_address; | ||
442 | ccw->xfer_bytes = sg->length; | ||
443 | |||
444 | ccw->bits = 0; | ||
445 | ccw->bits |= CCW_CHAIN; | ||
446 | ccw->bits |= CCW_HALT_ON_TERM; | ||
447 | ccw->bits |= CCW_TERM_FLUSH; | ||
448 | ccw->bits |= BF_CCW(direction == DMA_FROM_DEVICE ? | ||
449 | MXS_DMA_CMD_WRITE : MXS_DMA_CMD_READ, | ||
450 | COMMAND); | ||
451 | |||
452 | if (i + 1 == sg_len) { | ||
453 | ccw->bits &= ~CCW_CHAIN; | ||
454 | ccw->bits |= CCW_IRQ; | ||
455 | ccw->bits |= CCW_DEC_SEM; | ||
456 | ccw->bits |= CCW_WAIT4END; | ||
457 | } | ||
458 | } | ||
459 | } | ||
460 | |||
461 | return &mxs_chan->desc; | ||
462 | |||
463 | err_out: | ||
464 | mxs_chan->status = DMA_ERROR; | ||
465 | return NULL; | ||
466 | } | ||
467 | |||
468 | static struct dma_async_tx_descriptor *mxs_dma_prep_dma_cyclic( | ||
469 | struct dma_chan *chan, dma_addr_t dma_addr, size_t buf_len, | ||
470 | size_t period_len, enum dma_data_direction direction) | ||
471 | { | ||
472 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
473 | struct mxs_dma_engine *mxs_dma = mxs_chan->mxs_dma; | ||
474 | int num_periods = buf_len / period_len; | ||
475 | int i = 0, buf = 0; | ||
476 | |||
477 | if (mxs_chan->status == DMA_IN_PROGRESS) | ||
478 | return NULL; | ||
479 | |||
480 | mxs_chan->status = DMA_IN_PROGRESS; | ||
481 | mxs_chan->flags |= MXS_DMA_SG_LOOP; | ||
482 | |||
483 | if (num_periods > NUM_CCW) { | ||
484 | dev_err(mxs_dma->dma_device.dev, | ||
485 | "maximum number of sg exceeded: %d > %d\n", | ||
486 | num_periods, NUM_CCW); | ||
487 | goto err_out; | ||
488 | } | ||
489 | |||
490 | if (period_len > MAX_XFER_BYTES) { | ||
491 | dev_err(mxs_dma->dma_device.dev, | ||
492 | "maximum period size exceeded: %d > %d\n", | ||
493 | period_len, MAX_XFER_BYTES); | ||
494 | goto err_out; | ||
495 | } | ||
496 | |||
497 | while (buf < buf_len) { | ||
498 | struct mxs_dma_ccw *ccw = &mxs_chan->ccw[i]; | ||
499 | |||
500 | if (i + 1 == num_periods) | ||
501 | ccw->next = mxs_chan->ccw_phys; | ||
502 | else | ||
503 | ccw->next = mxs_chan->ccw_phys + sizeof(*ccw) * (i + 1); | ||
504 | |||
505 | ccw->bufaddr = dma_addr; | ||
506 | ccw->xfer_bytes = period_len; | ||
507 | |||
508 | ccw->bits = 0; | ||
509 | ccw->bits |= CCW_CHAIN; | ||
510 | ccw->bits |= CCW_IRQ; | ||
511 | ccw->bits |= CCW_HALT_ON_TERM; | ||
512 | ccw->bits |= CCW_TERM_FLUSH; | ||
513 | ccw->bits |= BF_CCW(direction == DMA_FROM_DEVICE ? | ||
514 | MXS_DMA_CMD_WRITE : MXS_DMA_CMD_READ, COMMAND); | ||
515 | |||
516 | dma_addr += period_len; | ||
517 | buf += period_len; | ||
518 | |||
519 | i++; | ||
520 | } | ||
521 | |||
522 | return &mxs_chan->desc; | ||
523 | |||
524 | err_out: | ||
525 | mxs_chan->status = DMA_ERROR; | ||
526 | return NULL; | ||
527 | } | ||
528 | |||
529 | static int mxs_dma_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, | ||
530 | unsigned long arg) | ||
531 | { | ||
532 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
533 | int ret = 0; | ||
534 | |||
535 | switch (cmd) { | ||
536 | case DMA_TERMINATE_ALL: | ||
537 | mxs_dma_disable_chan(mxs_chan); | ||
538 | break; | ||
539 | case DMA_PAUSE: | ||
540 | mxs_dma_pause_chan(mxs_chan); | ||
541 | break; | ||
542 | case DMA_RESUME: | ||
543 | mxs_dma_resume_chan(mxs_chan); | ||
544 | break; | ||
545 | default: | ||
546 | ret = -ENOSYS; | ||
547 | } | ||
548 | |||
549 | return ret; | ||
550 | } | ||
551 | |||
552 | static enum dma_status mxs_dma_tx_status(struct dma_chan *chan, | ||
553 | dma_cookie_t cookie, struct dma_tx_state *txstate) | ||
554 | { | ||
555 | struct mxs_dma_chan *mxs_chan = to_mxs_dma_chan(chan); | ||
556 | dma_cookie_t last_used; | ||
557 | |||
558 | last_used = chan->cookie; | ||
559 | dma_set_tx_state(txstate, mxs_chan->last_completed, last_used, 0); | ||
560 | |||
561 | return mxs_chan->status; | ||
562 | } | ||
563 | |||
564 | static void mxs_dma_issue_pending(struct dma_chan *chan) | ||
565 | { | ||
566 | /* | ||
567 | * Nothing to do. We only have a single descriptor. | ||
568 | */ | ||
569 | } | ||
570 | |||
571 | static int __init mxs_dma_init(struct mxs_dma_engine *mxs_dma) | ||
572 | { | ||
573 | int ret; | ||
574 | |||
575 | ret = clk_enable(mxs_dma->clk); | ||
576 | if (ret) | ||
577 | goto err_out; | ||
578 | |||
579 | ret = mxs_reset_block(mxs_dma->base); | ||
580 | if (ret) | ||
581 | goto err_out; | ||
582 | |||
583 | /* only major version matters */ | ||
584 | mxs_dma->version = readl(mxs_dma->base + | ||
585 | ((mxs_dma->dev_id == MXS_DMA_APBX) ? | ||
586 | HW_APBX_VERSION : HW_APBH_VERSION)) >> | ||
587 | BP_APBHX_VERSION_MAJOR; | ||
588 | |||
589 | /* enable apbh burst */ | ||
590 | if (dma_is_apbh()) { | ||
591 | writel(BM_APBH_CTRL0_APB_BURST_EN, | ||
592 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
593 | writel(BM_APBH_CTRL0_APB_BURST8_EN, | ||
594 | mxs_dma->base + HW_APBHX_CTRL0 + MXS_SET_ADDR); | ||
595 | } | ||
596 | |||
597 | /* enable irq for all the channels */ | ||
598 | writel(MXS_DMA_CHANNELS_MASK << MXS_DMA_CHANNELS, | ||
599 | mxs_dma->base + HW_APBHX_CTRL1 + MXS_SET_ADDR); | ||
600 | |||
601 | clk_disable(mxs_dma->clk); | ||
602 | |||
603 | return 0; | ||
604 | |||
605 | err_out: | ||
606 | return ret; | ||
607 | } | ||
608 | |||
609 | static int __init mxs_dma_probe(struct platform_device *pdev) | ||
610 | { | ||
611 | const struct platform_device_id *id_entry = | ||
612 | platform_get_device_id(pdev); | ||
613 | struct mxs_dma_engine *mxs_dma; | ||
614 | struct resource *iores; | ||
615 | int ret, i; | ||
616 | |||
617 | mxs_dma = kzalloc(sizeof(*mxs_dma), GFP_KERNEL); | ||
618 | if (!mxs_dma) | ||
619 | return -ENOMEM; | ||
620 | |||
621 | mxs_dma->dev_id = id_entry->driver_data; | ||
622 | |||
623 | iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
624 | |||
625 | if (!request_mem_region(iores->start, resource_size(iores), | ||
626 | pdev->name)) { | ||
627 | ret = -EBUSY; | ||
628 | goto err_request_region; | ||
629 | } | ||
630 | |||
631 | mxs_dma->base = ioremap(iores->start, resource_size(iores)); | ||
632 | if (!mxs_dma->base) { | ||
633 | ret = -ENOMEM; | ||
634 | goto err_ioremap; | ||
635 | } | ||
636 | |||
637 | mxs_dma->clk = clk_get(&pdev->dev, NULL); | ||
638 | if (IS_ERR(mxs_dma->clk)) { | ||
639 | ret = PTR_ERR(mxs_dma->clk); | ||
640 | goto err_clk; | ||
641 | } | ||
642 | |||
643 | dma_cap_set(DMA_SLAVE, mxs_dma->dma_device.cap_mask); | ||
644 | dma_cap_set(DMA_CYCLIC, mxs_dma->dma_device.cap_mask); | ||
645 | |||
646 | INIT_LIST_HEAD(&mxs_dma->dma_device.channels); | ||
647 | |||
648 | /* Initialize channel parameters */ | ||
649 | for (i = 0; i < MXS_DMA_CHANNELS; i++) { | ||
650 | struct mxs_dma_chan *mxs_chan = &mxs_dma->mxs_chans[i]; | ||
651 | |||
652 | mxs_chan->mxs_dma = mxs_dma; | ||
653 | mxs_chan->chan.device = &mxs_dma->dma_device; | ||
654 | |||
655 | tasklet_init(&mxs_chan->tasklet, mxs_dma_tasklet, | ||
656 | (unsigned long) mxs_chan); | ||
657 | |||
658 | |||
659 | /* Add the channel to mxs_chan list */ | ||
660 | list_add_tail(&mxs_chan->chan.device_node, | ||
661 | &mxs_dma->dma_device.channels); | ||
662 | } | ||
663 | |||
664 | ret = mxs_dma_init(mxs_dma); | ||
665 | if (ret) | ||
666 | goto err_init; | ||
667 | |||
668 | mxs_dma->dma_device.dev = &pdev->dev; | ||
669 | |||
670 | /* mxs_dma gets 65535 bytes maximum sg size */ | ||
671 | mxs_dma->dma_device.dev->dma_parms = &mxs_dma->dma_parms; | ||
672 | dma_set_max_seg_size(mxs_dma->dma_device.dev, MAX_XFER_BYTES); | ||
673 | |||
674 | mxs_dma->dma_device.device_alloc_chan_resources = mxs_dma_alloc_chan_resources; | ||
675 | mxs_dma->dma_device.device_free_chan_resources = mxs_dma_free_chan_resources; | ||
676 | mxs_dma->dma_device.device_tx_status = mxs_dma_tx_status; | ||
677 | mxs_dma->dma_device.device_prep_slave_sg = mxs_dma_prep_slave_sg; | ||
678 | mxs_dma->dma_device.device_prep_dma_cyclic = mxs_dma_prep_dma_cyclic; | ||
679 | mxs_dma->dma_device.device_control = mxs_dma_control; | ||
680 | mxs_dma->dma_device.device_issue_pending = mxs_dma_issue_pending; | ||
681 | |||
682 | ret = dma_async_device_register(&mxs_dma->dma_device); | ||
683 | if (ret) { | ||
684 | dev_err(mxs_dma->dma_device.dev, "unable to register\n"); | ||
685 | goto err_init; | ||
686 | } | ||
687 | |||
688 | dev_info(mxs_dma->dma_device.dev, "initialized\n"); | ||
689 | |||
690 | return 0; | ||
691 | |||
692 | err_init: | ||
693 | clk_put(mxs_dma->clk); | ||
694 | err_clk: | ||
695 | iounmap(mxs_dma->base); | ||
696 | err_ioremap: | ||
697 | release_mem_region(iores->start, resource_size(iores)); | ||
698 | err_request_region: | ||
699 | kfree(mxs_dma); | ||
700 | return ret; | ||
701 | } | ||
702 | |||
703 | static struct platform_device_id mxs_dma_type[] = { | ||
704 | { | ||
705 | .name = "mxs-dma-apbh", | ||
706 | .driver_data = MXS_DMA_APBH, | ||
707 | }, { | ||
708 | .name = "mxs-dma-apbx", | ||
709 | .driver_data = MXS_DMA_APBX, | ||
710 | } | ||
711 | }; | ||
712 | |||
713 | static struct platform_driver mxs_dma_driver = { | ||
714 | .driver = { | ||
715 | .name = "mxs-dma", | ||
716 | }, | ||
717 | .id_table = mxs_dma_type, | ||
718 | }; | ||
719 | |||
720 | static int __init mxs_dma_module_init(void) | ||
721 | { | ||
722 | return platform_driver_probe(&mxs_dma_driver, mxs_dma_probe); | ||
723 | } | ||
724 | subsys_initcall(mxs_dma_module_init); | ||