]>
Commit | Line | Data |
---|---|---|
5cccd37e | 1 | /* |
2 | * DMA helper routines for Freescale STMP37XX/STMP378X | |
3 | * | |
4 | * Author: dmitry pervushin <dpervushin@embeddedalley.com> | |
5 | * | |
6 | * Copyright 2008 Freescale Semiconductor, Inc. All Rights Reserved. | |
7 | * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. | |
8 | */ | |
9 | ||
10 | /* | |
11 | * The code contained herein is licensed under the GNU General Public | |
12 | * License. You may obtain a copy of the GNU General Public License | |
13 | * Version 2 or later at the following locations: | |
14 | * | |
15 | * http://www.opensource.org/licenses/gpl-license.html | |
16 | * http://www.gnu.org/copyleft/gpl.html | |
17 | */ | |
5a0e3ad6 | 18 | #include <linux/gfp.h> |
5cccd37e | 19 | #include <linux/kernel.h> |
20 | #include <linux/device.h> | |
21 | #include <linux/dmapool.h> | |
22 | #include <linux/sysdev.h> | |
23 | #include <linux/cpufreq.h> | |
24 | ||
25 | #include <asm/page.h> | |
26 | ||
98f420b2 | 27 | #include <mach/platform.h> |
5cccd37e | 28 | #include <mach/dma.h> |
29 | #include <mach/regs-apbx.h> | |
30 | #include <mach/regs-apbh.h> | |
31 | ||
32 | static const size_t pool_item_size = sizeof(struct stmp3xxx_dma_command); | |
33 | static const size_t pool_alignment = 8; | |
34 | static struct stmp3xxx_dma_user { | |
35 | void *pool; | |
36 | int inuse; | |
37 | const char *name; | |
38 | } channels[MAX_DMA_CHANNELS]; | |
39 | ||
5cccd37e | 40 | #define IS_VALID_CHANNEL(ch) ((ch) >= 0 && (ch) < MAX_DMA_CHANNELS) |
41 | #define IS_USED(ch) (channels[ch].inuse) | |
42 | ||
43 | int stmp3xxx_dma_request(int ch, struct device *dev, const char *name) | |
44 | { | |
45 | struct stmp3xxx_dma_user *user; | |
46 | int err = 0; | |
47 | ||
48 | user = channels + ch; | |
49 | if (!IS_VALID_CHANNEL(ch)) { | |
50 | err = -ENODEV; | |
51 | goto out; | |
52 | } | |
53 | if (IS_USED(ch)) { | |
54 | err = -EBUSY; | |
55 | goto out; | |
56 | } | |
57 | /* Create a pool to allocate dma commands from */ | |
58 | user->pool = dma_pool_create(name, dev, pool_item_size, | |
59 | pool_alignment, PAGE_SIZE); | |
60 | if (user->pool == NULL) { | |
61 | err = -ENOMEM; | |
62 | goto out; | |
63 | } | |
64 | user->name = name; | |
65 | user->inuse++; | |
66 | out: | |
67 | return err; | |
68 | } | |
69 | EXPORT_SYMBOL(stmp3xxx_dma_request); | |
70 | ||
71 | int stmp3xxx_dma_release(int ch) | |
72 | { | |
73 | struct stmp3xxx_dma_user *user = channels + ch; | |
74 | int err = 0; | |
75 | ||
76 | if (!IS_VALID_CHANNEL(ch)) { | |
77 | err = -ENODEV; | |
78 | goto out; | |
79 | } | |
80 | if (!IS_USED(ch)) { | |
81 | err = -EBUSY; | |
82 | goto out; | |
83 | } | |
84 | BUG_ON(user->pool == NULL); | |
85 | dma_pool_destroy(user->pool); | |
86 | user->inuse--; | |
87 | out: | |
88 | return err; | |
89 | } | |
90 | EXPORT_SYMBOL(stmp3xxx_dma_release); | |
91 | ||
92 | int stmp3xxx_dma_read_semaphore(int channel) | |
93 | { | |
94 | int sem = -1; | |
95 | ||
98f420b2 | 96 | switch (STMP3XXX_DMA_BUS(channel)) { |
5cccd37e | 97 | case STMP3XXX_BUS_APBH: |
98f420b2 | 98 | sem = __raw_readl(REGS_APBH_BASE + HW_APBH_CHn_SEMA + |
99 | STMP3XXX_DMA_CHANNEL(channel) * 0x70); | |
100 | sem &= BM_APBH_CHn_SEMA_PHORE; | |
101 | sem >>= BP_APBH_CHn_SEMA_PHORE; | |
5cccd37e | 102 | break; |
103 | ||
104 | case STMP3XXX_BUS_APBX: | |
98f420b2 | 105 | sem = __raw_readl(REGS_APBX_BASE + HW_APBX_CHn_SEMA + |
106 | STMP3XXX_DMA_CHANNEL(channel) * 0x70); | |
107 | sem &= BM_APBX_CHn_SEMA_PHORE; | |
108 | sem >>= BP_APBX_CHn_SEMA_PHORE; | |
5cccd37e | 109 | break; |
110 | default: | |
111 | BUG(); | |
112 | } | |
113 | return sem; | |
114 | } | |
115 | EXPORT_SYMBOL(stmp3xxx_dma_read_semaphore); | |
116 | ||
117 | int stmp3xxx_dma_allocate_command(int channel, | |
118 | struct stmp3xxx_dma_descriptor *descriptor) | |
119 | { | |
120 | struct stmp3xxx_dma_user *user = channels + channel; | |
121 | int err = 0; | |
122 | ||
123 | if (!IS_VALID_CHANNEL(channel)) { | |
124 | err = -ENODEV; | |
125 | goto out; | |
126 | } | |
127 | if (!IS_USED(channel)) { | |
128 | err = -EBUSY; | |
129 | goto out; | |
130 | } | |
131 | if (descriptor == NULL) { | |
132 | err = -EINVAL; | |
133 | goto out; | |
134 | } | |
135 | ||
136 | /* Allocate memory for a command from the buffer */ | |
137 | descriptor->command = | |
138 | dma_pool_alloc(user->pool, GFP_KERNEL, &descriptor->handle); | |
139 | ||
140 | /* Check it worked */ | |
141 | if (!descriptor->command) { | |
142 | err = -ENOMEM; | |
143 | goto out; | |
144 | } | |
145 | ||
146 | memset(descriptor->command, 0, pool_item_size); | |
147 | out: | |
148 | WARN_ON(err); | |
149 | return err; | |
150 | } | |
151 | EXPORT_SYMBOL(stmp3xxx_dma_allocate_command); | |
152 | ||
153 | int stmp3xxx_dma_free_command(int channel, | |
154 | struct stmp3xxx_dma_descriptor *descriptor) | |
155 | { | |
156 | int err = 0; | |
157 | ||
158 | if (!IS_VALID_CHANNEL(channel)) { | |
159 | err = -ENODEV; | |
160 | goto out; | |
161 | } | |
162 | if (!IS_USED(channel)) { | |
163 | err = -EBUSY; | |
164 | goto out; | |
165 | } | |
166 | ||
167 | /* Return the command memory to the pool */ | |
168 | dma_pool_free(channels[channel].pool, descriptor->command, | |
169 | descriptor->handle); | |
170 | ||
171 | /* Initialise descriptor so we're not tempted to use it */ | |
172 | descriptor->command = NULL; | |
173 | descriptor->handle = 0; | |
174 | descriptor->virtual_buf_ptr = NULL; | |
175 | descriptor->next_descr = NULL; | |
176 | ||
177 | WARN_ON(err); | |
178 | out: | |
179 | return err; | |
180 | } | |
181 | EXPORT_SYMBOL(stmp3xxx_dma_free_command); | |
182 | ||
183 | void stmp3xxx_dma_go(int channel, | |
184 | struct stmp3xxx_dma_descriptor *head, u32 semaphore) | |
185 | { | |
98f420b2 | 186 | int ch = STMP3XXX_DMA_CHANNEL(channel); |
187 | void __iomem *c, *s; | |
5cccd37e | 188 | |
98f420b2 | 189 | switch (STMP3XXX_DMA_BUS(channel)) { |
5cccd37e | 190 | case STMP3XXX_BUS_APBH: |
98f420b2 | 191 | c = REGS_APBH_BASE + HW_APBH_CHn_NXTCMDAR + 0x70 * ch; |
192 | s = REGS_APBH_BASE + HW_APBH_CHn_SEMA + 0x70 * ch; | |
5cccd37e | 193 | break; |
194 | ||
195 | case STMP3XXX_BUS_APBX: | |
98f420b2 | 196 | c = REGS_APBX_BASE + HW_APBX_CHn_NXTCMDAR + 0x70 * ch; |
197 | s = REGS_APBX_BASE + HW_APBX_CHn_SEMA + 0x70 * ch; | |
5cccd37e | 198 | break; |
98f420b2 | 199 | |
200 | default: | |
201 | return; | |
5cccd37e | 202 | } |
98f420b2 | 203 | |
204 | /* Set next command */ | |
205 | __raw_writel(head->handle, c); | |
206 | /* Set counting semaphore (kicks off transfer). Assumes | |
207 | peripheral has been set up correctly */ | |
208 | __raw_writel(semaphore, s); | |
5cccd37e | 209 | } |
210 | EXPORT_SYMBOL(stmp3xxx_dma_go); | |
211 | ||
212 | int stmp3xxx_dma_running(int channel) | |
213 | { | |
98f420b2 | 214 | switch (STMP3XXX_DMA_BUS(channel)) { |
5cccd37e | 215 | case STMP3XXX_BUS_APBH: |
98f420b2 | 216 | return (__raw_readl(REGS_APBH_BASE + HW_APBH_CHn_SEMA + |
217 | 0x70 * STMP3XXX_DMA_CHANNEL(channel))) & | |
218 | BM_APBH_CHn_SEMA_PHORE; | |
5cccd37e | 219 | |
220 | case STMP3XXX_BUS_APBX: | |
98f420b2 | 221 | return (__raw_readl(REGS_APBX_BASE + HW_APBX_CHn_SEMA + |
222 | 0x70 * STMP3XXX_DMA_CHANNEL(channel))) & | |
223 | BM_APBX_CHn_SEMA_PHORE; | |
5cccd37e | 224 | default: |
225 | BUG(); | |
226 | return 0; | |
227 | } | |
228 | } | |
229 | EXPORT_SYMBOL(stmp3xxx_dma_running); | |
230 | ||
231 | /* | |
232 | * Circular dma chain management | |
233 | */ | |
234 | void stmp3xxx_dma_free_chain(struct stmp37xx_circ_dma_chain *chain) | |
235 | { | |
236 | int i; | |
237 | ||
238 | for (i = 0; i < chain->total_count; i++) | |
239 | stmp3xxx_dma_free_command( | |
98f420b2 | 240 | STMP3XXX_DMA(chain->channel, chain->bus), |
5cccd37e | 241 | &chain->chain[i]); |
242 | } | |
243 | EXPORT_SYMBOL(stmp3xxx_dma_free_chain); | |
244 | ||
245 | int stmp3xxx_dma_make_chain(int ch, struct stmp37xx_circ_dma_chain *chain, | |
246 | struct stmp3xxx_dma_descriptor descriptors[], | |
247 | unsigned items) | |
248 | { | |
249 | int i; | |
250 | int err = 0; | |
251 | ||
252 | if (items == 0) | |
253 | return err; | |
254 | ||
255 | for (i = 0; i < items; i++) { | |
256 | err = stmp3xxx_dma_allocate_command(ch, &descriptors[i]); | |
257 | if (err) { | |
258 | WARN_ON(err); | |
259 | /* | |
260 | * Couldn't allocate the whole chain. | |
261 | * deallocate what has been allocated | |
262 | */ | |
263 | if (i) { | |
264 | do { | |
265 | stmp3xxx_dma_free_command(ch, | |
266 | &descriptors | |
267 | [i]); | |
2cc0bab8 | 268 | } while (i-- > 0); |
5cccd37e | 269 | } |
270 | return err; | |
271 | } | |
272 | ||
273 | /* link them! */ | |
274 | if (i > 0) { | |
275 | descriptors[i - 1].next_descr = &descriptors[i]; | |
276 | descriptors[i - 1].command->next = | |
277 | descriptors[i].handle; | |
278 | } | |
279 | } | |
280 | ||
281 | /* make list circular */ | |
282 | descriptors[items - 1].next_descr = &descriptors[0]; | |
283 | descriptors[items - 1].command->next = descriptors[0].handle; | |
284 | ||
285 | chain->total_count = items; | |
286 | chain->chain = descriptors; | |
287 | chain->free_index = 0; | |
288 | chain->active_index = 0; | |
289 | chain->cooked_index = 0; | |
290 | chain->free_count = items; | |
291 | chain->active_count = 0; | |
292 | chain->cooked_count = 0; | |
98f420b2 | 293 | chain->bus = STMP3XXX_DMA_BUS(ch); |
294 | chain->channel = STMP3XXX_DMA_CHANNEL(ch); | |
5cccd37e | 295 | return err; |
296 | } | |
297 | EXPORT_SYMBOL(stmp3xxx_dma_make_chain); | |
298 | ||
299 | void stmp37xx_circ_clear_chain(struct stmp37xx_circ_dma_chain *chain) | |
300 | { | |
98f420b2 | 301 | BUG_ON(stmp3xxx_dma_running(STMP3XXX_DMA(chain->channel, chain->bus))); |
5cccd37e | 302 | chain->free_index = 0; |
303 | chain->active_index = 0; | |
304 | chain->cooked_index = 0; | |
305 | chain->free_count = chain->total_count; | |
306 | chain->active_count = 0; | |
307 | chain->cooked_count = 0; | |
308 | } | |
309 | EXPORT_SYMBOL(stmp37xx_circ_clear_chain); | |
310 | ||
311 | void stmp37xx_circ_advance_free(struct stmp37xx_circ_dma_chain *chain, | |
312 | unsigned count) | |
313 | { | |
314 | BUG_ON(chain->cooked_count < count); | |
315 | ||
316 | chain->cooked_count -= count; | |
317 | chain->cooked_index += count; | |
318 | chain->cooked_index %= chain->total_count; | |
319 | chain->free_count += count; | |
320 | } | |
321 | EXPORT_SYMBOL(stmp37xx_circ_advance_free); | |
322 | ||
323 | void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain, | |
324 | unsigned count) | |
325 | { | |
98f420b2 | 326 | void __iomem *c; |
327 | u32 mask_clr, mask; | |
5cccd37e | 328 | BUG_ON(chain->free_count < count); |
329 | ||
330 | chain->free_count -= count; | |
331 | chain->free_index += count; | |
332 | chain->free_index %= chain->total_count; | |
333 | chain->active_count += count; | |
334 | ||
335 | switch (chain->bus) { | |
336 | case STMP3XXX_BUS_APBH: | |
98f420b2 | 337 | c = REGS_APBH_BASE + HW_APBH_CHn_SEMA + 0x70 * chain->channel; |
338 | mask_clr = BM_APBH_CHn_SEMA_INCREMENT_SEMA; | |
339 | mask = BF(count, APBH_CHn_SEMA_INCREMENT_SEMA); | |
5cccd37e | 340 | break; |
5cccd37e | 341 | case STMP3XXX_BUS_APBX: |
98f420b2 | 342 | c = REGS_APBX_BASE + HW_APBX_CHn_SEMA + 0x70 * chain->channel; |
343 | mask_clr = BM_APBX_CHn_SEMA_INCREMENT_SEMA; | |
344 | mask = BF(count, APBX_CHn_SEMA_INCREMENT_SEMA); | |
5cccd37e | 345 | break; |
5cccd37e | 346 | default: |
347 | BUG(); | |
98f420b2 | 348 | return; |
5cccd37e | 349 | } |
98f420b2 | 350 | |
351 | /* Set counting semaphore (kicks off transfer). Assumes | |
352 | peripheral has been set up correctly */ | |
353 | stmp3xxx_clearl(mask_clr, c); | |
354 | stmp3xxx_setl(mask, c); | |
5cccd37e | 355 | } |
356 | EXPORT_SYMBOL(stmp37xx_circ_advance_active); | |
357 | ||
358 | unsigned stmp37xx_circ_advance_cooked(struct stmp37xx_circ_dma_chain *chain) | |
359 | { | |
360 | unsigned cooked; | |
361 | ||
362 | cooked = chain->active_count - | |
98f420b2 | 363 | stmp3xxx_dma_read_semaphore(STMP3XXX_DMA(chain->channel, chain->bus)); |
5cccd37e | 364 | |
365 | chain->active_count -= cooked; | |
366 | chain->active_index += cooked; | |
367 | chain->active_index %= chain->total_count; | |
368 | ||
369 | chain->cooked_count += cooked; | |
370 | ||
371 | return cooked; | |
372 | } | |
373 | EXPORT_SYMBOL(stmp37xx_circ_advance_cooked); | |
374 | ||
375 | void stmp3xxx_dma_set_alt_target(int channel, int function) | |
376 | { | |
377 | #if defined(CONFIG_ARCH_STMP37XX) | |
378 | unsigned bits = 4; | |
379 | #elif defined(CONFIG_ARCH_STMP378X) | |
380 | unsigned bits = 2; | |
381 | #else | |
382 | #error wrong arch | |
383 | #endif | |
98f420b2 | 384 | int shift = STMP3XXX_DMA_CHANNEL(channel) * bits; |
5cccd37e | 385 | unsigned mask = (1<<bits) - 1; |
98f420b2 | 386 | void __iomem *c; |
5cccd37e | 387 | |
388 | BUG_ON(function < 0 || function >= (1<<bits)); | |
389 | pr_debug("%s: channel = %d, using mask %x, " | |
390 | "shift = %d\n", __func__, channel, mask, shift); | |
391 | ||
98f420b2 | 392 | switch (STMP3XXX_DMA_BUS(channel)) { |
5cccd37e | 393 | case STMP3XXX_BUS_APBH: |
98f420b2 | 394 | c = REGS_APBH_BASE + HW_APBH_DEVSEL; |
5cccd37e | 395 | break; |
396 | case STMP3XXX_BUS_APBX: | |
98f420b2 | 397 | c = REGS_APBX_BASE + HW_APBX_DEVSEL; |
5cccd37e | 398 | break; |
399 | default: | |
400 | BUG(); | |
401 | } | |
98f420b2 | 402 | stmp3xxx_clearl(mask << shift, c); |
403 | stmp3xxx_setl(mask << shift, c); | |
5cccd37e | 404 | } |
405 | EXPORT_SYMBOL(stmp3xxx_dma_set_alt_target); | |
406 | ||
407 | void stmp3xxx_dma_suspend(void) | |
408 | { | |
98f420b2 | 409 | stmp3xxx_setl(BM_APBH_CTRL0_CLKGATE, REGS_APBH_BASE + HW_APBH_CTRL0); |
410 | stmp3xxx_setl(BM_APBX_CTRL0_CLKGATE, REGS_APBX_BASE + HW_APBX_CTRL0); | |
5cccd37e | 411 | } |
412 | ||
413 | void stmp3xxx_dma_resume(void) | |
414 | { | |
98f420b2 | 415 | stmp3xxx_clearl(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST, |
416 | REGS_APBH_BASE + HW_APBH_CTRL0); | |
417 | stmp3xxx_clearl(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST, | |
418 | REGS_APBX_BASE + HW_APBX_CTRL0); | |
5cccd37e | 419 | } |
420 | ||
421 | #ifdef CONFIG_CPU_FREQ | |
422 | ||
423 | struct dma_notifier_block { | |
424 | struct notifier_block nb; | |
425 | void *data; | |
426 | }; | |
427 | ||
428 | static int dma_cpufreq_notifier(struct notifier_block *self, | |
429 | unsigned long phase, void *p) | |
430 | { | |
431 | switch (phase) { | |
432 | case CPUFREQ_POSTCHANGE: | |
433 | stmp3xxx_dma_resume(); | |
434 | break; | |
435 | ||
436 | case CPUFREQ_PRECHANGE: | |
437 | stmp3xxx_dma_suspend(); | |
438 | break; | |
439 | ||
440 | default: | |
441 | break; | |
442 | } | |
443 | ||
444 | return NOTIFY_DONE; | |
445 | } | |
446 | ||
447 | static struct dma_notifier_block dma_cpufreq_nb = { | |
448 | .nb = { | |
449 | .notifier_call = dma_cpufreq_notifier, | |
450 | }, | |
451 | }; | |
452 | #endif /* CONFIG_CPU_FREQ */ | |
453 | ||
454 | void __init stmp3xxx_dma_init(void) | |
455 | { | |
98f420b2 | 456 | stmp3xxx_clearl(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST, |
457 | REGS_APBH_BASE + HW_APBH_CTRL0); | |
458 | stmp3xxx_clearl(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST, | |
459 | REGS_APBX_BASE + HW_APBX_CTRL0); | |
5cccd37e | 460 | #ifdef CONFIG_CPU_FREQ |
461 | cpufreq_register_notifier(&dma_cpufreq_nb.nb, | |
462 | CPUFREQ_TRANSITION_NOTIFIER); | |
463 | #endif /* CONFIG_CPU_FREQ */ | |
5cccd37e | 464 | } |