aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/plat-stmp3xxx/dma.c
diff options
context:
space:
mode:
authordmitry pervushin <dpervushin@embeddedalley.com>2009-04-23 07:24:13 -0400
committerRussell King <rmk+kernel@arm.linux.org.uk>2009-04-27 05:28:08 -0400
commit5cccd37ea15970846a93b4b01fafd6e043bafe8e (patch)
tree4ab99b59f91964028fbba128d8ae086f60bd8c82 /arch/arm/plat-stmp3xxx/dma.c
parente317872ac532fd845c597e55ceb5a9bceee878c1 (diff)
[ARM] 5477/1: Freescale STMP platform support [6/10]
Sources: common STMP3xxx platform support Signed-off-by: dmitry pervushin <dpervushin@embeddedalley.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'arch/arm/plat-stmp3xxx/dma.c')
-rw-r--r--arch/arm/plat-stmp3xxx/dma.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/arch/arm/plat-stmp3xxx/dma.c b/arch/arm/plat-stmp3xxx/dma.c
new file mode 100644
index 000000000000..cf42de05e568
--- /dev/null
+++ b/arch/arm/plat-stmp3xxx/dma.c
@@ -0,0 +1,462 @@
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 */
18#include <linux/kernel.h>
19#include <linux/device.h>
20#include <linux/dmapool.h>
21#include <linux/sysdev.h>
22#include <linux/cpufreq.h>
23
24#include <asm/page.h>
25
26#include <mach/dma.h>
27#include <mach/regs-apbx.h>
28#include <mach/regs-apbh.h>
29
30static const size_t pool_item_size = sizeof(struct stmp3xxx_dma_command);
31static const size_t pool_alignment = 8;
32static struct stmp3xxx_dma_user {
33 void *pool;
34 int inuse;
35 const char *name;
36} channels[MAX_DMA_CHANNELS];
37
38static inline int dmach(int ch)
39{
40 return ch % 16;
41}
42
43static inline int dmabus(int ch)
44{
45 return ch / 16;
46}
47
48#define IS_VALID_CHANNEL(ch) ((ch) >= 0 && (ch) < MAX_DMA_CHANNELS)
49#define IS_USED(ch) (channels[ch].inuse)
50
51int stmp3xxx_dma_request(int ch, struct device *dev, const char *name)
52{
53 struct stmp3xxx_dma_user *user;
54 int err = 0;
55
56 user = channels + ch;
57 if (!IS_VALID_CHANNEL(ch)) {
58 err = -ENODEV;
59 goto out;
60 }
61 if (IS_USED(ch)) {
62 err = -EBUSY;
63 goto out;
64 }
65 /* Create a pool to allocate dma commands from */
66 user->pool = dma_pool_create(name, dev, pool_item_size,
67 pool_alignment, PAGE_SIZE);
68 if (user->pool == NULL) {
69 err = -ENOMEM;
70 goto out;
71 }
72 user->name = name;
73 user->inuse++;
74out:
75 return err;
76}
77EXPORT_SYMBOL(stmp3xxx_dma_request);
78
79int stmp3xxx_dma_release(int ch)
80{
81 struct stmp3xxx_dma_user *user = channels + ch;
82 int err = 0;
83
84 if (!IS_VALID_CHANNEL(ch)) {
85 err = -ENODEV;
86 goto out;
87 }
88 if (!IS_USED(ch)) {
89 err = -EBUSY;
90 goto out;
91 }
92 BUG_ON(user->pool == NULL);
93 dma_pool_destroy(user->pool);
94 user->inuse--;
95out:
96 return err;
97}
98EXPORT_SYMBOL(stmp3xxx_dma_release);
99
100int stmp3xxx_dma_read_semaphore(int channel)
101{
102 int sem = -1;
103
104 switch (dmabus(channel)) {
105 case STMP3XXX_BUS_APBH:
106 sem =
107 (HW_APBH_CHn_SEMA_RD(dmach(channel)) &
108 BM_APBH_CHn_SEMA_PHORE) >> BP_APBH_CHn_SEMA_PHORE;
109 break;
110
111 case STMP3XXX_BUS_APBX:
112 sem =
113 (HW_APBX_CHn_SEMA_RD(dmach(channel)) &
114 BM_APBX_CHn_SEMA_PHORE) >> BP_APBX_CHn_SEMA_PHORE;
115 break;
116 default:
117 BUG();
118 }
119 return sem;
120}
121EXPORT_SYMBOL(stmp3xxx_dma_read_semaphore);
122
123int stmp3xxx_dma_allocate_command(int channel,
124 struct stmp3xxx_dma_descriptor *descriptor)
125{
126 struct stmp3xxx_dma_user *user = channels + channel;
127 int err = 0;
128
129 if (!IS_VALID_CHANNEL(channel)) {
130 err = -ENODEV;
131 goto out;
132 }
133 if (!IS_USED(channel)) {
134 err = -EBUSY;
135 goto out;
136 }
137 if (descriptor == NULL) {
138 err = -EINVAL;
139 goto out;
140 }
141
142 /* Allocate memory for a command from the buffer */
143 descriptor->command =
144 dma_pool_alloc(user->pool, GFP_KERNEL, &descriptor->handle);
145
146 /* Check it worked */
147 if (!descriptor->command) {
148 err = -ENOMEM;
149 goto out;
150 }
151
152 memset(descriptor->command, 0, pool_item_size);
153out:
154 WARN_ON(err);
155 return err;
156}
157EXPORT_SYMBOL(stmp3xxx_dma_allocate_command);
158
159int stmp3xxx_dma_free_command(int channel,
160 struct stmp3xxx_dma_descriptor *descriptor)
161{
162 int err = 0;
163
164 if (!IS_VALID_CHANNEL(channel)) {
165 err = -ENODEV;
166 goto out;
167 }
168 if (!IS_USED(channel)) {
169 err = -EBUSY;
170 goto out;
171 }
172
173 /* Return the command memory to the pool */
174 dma_pool_free(channels[channel].pool, descriptor->command,
175 descriptor->handle);
176
177 /* Initialise descriptor so we're not tempted to use it */
178 descriptor->command = NULL;
179 descriptor->handle = 0;
180 descriptor->virtual_buf_ptr = NULL;
181 descriptor->next_descr = NULL;
182
183 WARN_ON(err);
184out:
185 return err;
186}
187EXPORT_SYMBOL(stmp3xxx_dma_free_command);
188
189void stmp3xxx_dma_go(int channel,
190 struct stmp3xxx_dma_descriptor *head, u32 semaphore)
191{
192 int ch = dmach(channel);
193
194 switch (dmabus(channel)) {
195 case STMP3XXX_BUS_APBH:
196 /* Set next command */
197 HW_APBH_CHn_NXTCMDAR_WR(ch, head->handle);
198 /* Set counting semaphore (kicks off transfer). Assumes
199 peripheral has been set up correctly */
200 HW_APBH_CHn_SEMA_WR(ch, semaphore);
201 break;
202
203 case STMP3XXX_BUS_APBX:
204 /* Set next command */
205 HW_APBX_CHn_NXTCMDAR_WR(ch, head->handle);
206 /* Set counting semaphore (kicks off transfer). Assumes
207 peripheral has been set up correctly */
208 HW_APBX_CHn_SEMA_WR(ch, semaphore);
209 break;
210 }
211}
212EXPORT_SYMBOL(stmp3xxx_dma_go);
213
214int stmp3xxx_dma_running(int channel)
215{
216 switch (dmabus(channel)) {
217 case STMP3XXX_BUS_APBH:
218 return HW_APBH_CHn_SEMA_RD(dmach(channel)) &
219 BM_APBH_CHn_SEMA_PHORE;
220
221 case STMP3XXX_BUS_APBX:
222 return HW_APBX_CHn_SEMA_RD(dmach(channel)) &
223 BM_APBX_CHn_SEMA_PHORE;
224
225 default:
226 BUG();
227 return 0;
228 }
229}
230EXPORT_SYMBOL(stmp3xxx_dma_running);
231
232/*
233 * Circular dma chain management
234 */
235void stmp3xxx_dma_free_chain(struct stmp37xx_circ_dma_chain *chain)
236{
237 int i;
238
239 for (i = 0; i < chain->total_count; i++)
240 stmp3xxx_dma_free_command(
241 STMP3xxx_DMA(chain->channel, chain->bus),
242 &chain->chain[i]);
243}
244EXPORT_SYMBOL(stmp3xxx_dma_free_chain);
245
246int stmp3xxx_dma_make_chain(int ch, struct stmp37xx_circ_dma_chain *chain,
247 struct stmp3xxx_dma_descriptor descriptors[],
248 unsigned items)
249{
250 int i;
251 int err = 0;
252
253 if (items == 0)
254 return err;
255
256 for (i = 0; i < items; i++) {
257 err = stmp3xxx_dma_allocate_command(ch, &descriptors[i]);
258 if (err) {
259 WARN_ON(err);
260 /*
261 * Couldn't allocate the whole chain.
262 * deallocate what has been allocated
263 */
264 if (i) {
265 do {
266 stmp3xxx_dma_free_command(ch,
267 &descriptors
268 [i]);
269 } while (i-- >= 0);
270 }
271 return err;
272 }
273
274 /* link them! */
275 if (i > 0) {
276 descriptors[i - 1].next_descr = &descriptors[i];
277 descriptors[i - 1].command->next =
278 descriptors[i].handle;
279 }
280 }
281
282 /* make list circular */
283 descriptors[items - 1].next_descr = &descriptors[0];
284 descriptors[items - 1].command->next = descriptors[0].handle;
285
286 chain->total_count = items;
287 chain->chain = descriptors;
288 chain->free_index = 0;
289 chain->active_index = 0;
290 chain->cooked_index = 0;
291 chain->free_count = items;
292 chain->active_count = 0;
293 chain->cooked_count = 0;
294 chain->bus = dmabus(ch);
295 chain->channel = dmach(ch);
296 return err;
297}
298EXPORT_SYMBOL(stmp3xxx_dma_make_chain);
299
300void stmp37xx_circ_clear_chain(struct stmp37xx_circ_dma_chain *chain)
301{
302 BUG_ON(stmp3xxx_dma_running(STMP3xxx_DMA(chain->channel, chain->bus)) >
303 0);
304 chain->free_index = 0;
305 chain->active_index = 0;
306 chain->cooked_index = 0;
307 chain->free_count = chain->total_count;
308 chain->active_count = 0;
309 chain->cooked_count = 0;
310}
311EXPORT_SYMBOL(stmp37xx_circ_clear_chain);
312
313void stmp37xx_circ_advance_free(struct stmp37xx_circ_dma_chain *chain,
314 unsigned count)
315{
316 BUG_ON(chain->cooked_count < count);
317
318 chain->cooked_count -= count;
319 chain->cooked_index += count;
320 chain->cooked_index %= chain->total_count;
321 chain->free_count += count;
322}
323EXPORT_SYMBOL(stmp37xx_circ_advance_free);
324
325void stmp37xx_circ_advance_active(struct stmp37xx_circ_dma_chain *chain,
326 unsigned count)
327{
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:
337 /* Set counting semaphore (kicks off transfer). Assumes
338 peripheral has been set up correctly */
339 HW_APBH_CHn_SEMA_CLR(chain->channel,
340 BM_APBH_CHn_SEMA_INCREMENT_SEMA);
341 HW_APBH_CHn_SEMA_SET(chain->channel,
342 BF_APBH_CHn_SEMA_INCREMENT_SEMA(count));
343 break;
344
345 case STMP3XXX_BUS_APBX:
346 /* Set counting semaphore (kicks off transfer). Assumes
347 peripheral has been set up correctly */
348 HW_APBX_CHn_SEMA_CLR(chain->channel,
349 BM_APBX_CHn_SEMA_INCREMENT_SEMA);
350 HW_APBX_CHn_SEMA_SET(chain->channel,
351 BF_APBX_CHn_SEMA_INCREMENT_SEMA(count));
352 break;
353
354 default:
355 BUG();
356 }
357}
358EXPORT_SYMBOL(stmp37xx_circ_advance_active);
359
360unsigned stmp37xx_circ_advance_cooked(struct stmp37xx_circ_dma_chain *chain)
361{
362 unsigned cooked;
363
364 cooked = chain->active_count -
365 stmp3xxx_dma_read_semaphore(STMP3xxx_DMA(chain->channel, chain->bus));
366
367 chain->active_count -= cooked;
368 chain->active_index += cooked;
369 chain->active_index %= chain->total_count;
370
371 chain->cooked_count += cooked;
372
373 return cooked;
374}
375EXPORT_SYMBOL(stmp37xx_circ_advance_cooked);
376
377void stmp3xxx_dma_set_alt_target(int channel, int function)
378{
379#if defined(CONFIG_ARCH_STMP37XX)
380 unsigned bits = 4;
381#elif defined(CONFIG_ARCH_STMP378X)
382 unsigned bits = 2;
383#else
384#error wrong arch
385#endif
386 int shift = dmach(channel) * bits;
387 unsigned mask = (1<<bits) - 1;
388
389 BUG_ON(function < 0 || function >= (1<<bits));
390 pr_debug("%s: channel = %d, using mask %x, "
391 "shift = %d\n", __func__, channel, mask, shift);
392
393 switch (dmabus(channel)) {
394 case STMP3XXX_BUS_APBH:
395 HW_APBH_DEVSEL_CLR(mask<<shift);
396 HW_APBH_DEVSEL_SET(function<<shift);
397 break;
398 case STMP3XXX_BUS_APBX:
399 HW_APBX_DEVSEL_CLR(mask<<shift);
400 HW_APBX_DEVSEL_SET(function<<shift);
401 break;
402 default:
403 BUG();
404 }
405}
406EXPORT_SYMBOL(stmp3xxx_dma_set_alt_target);
407
408void stmp3xxx_dma_suspend(void)
409{
410 HW_APBH_CTRL0_SET(BM_APBH_CTRL0_CLKGATE);
411 HW_APBX_CTRL0_SET(BM_APBX_CTRL0_CLKGATE);
412}
413
414void stmp3xxx_dma_resume(void)
415{
416 HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST);
417 HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST);
418}
419
420#ifdef CONFIG_CPU_FREQ
421
422struct dma_notifier_block {
423 struct notifier_block nb;
424 void *data;
425};
426
427static int dma_cpufreq_notifier(struct notifier_block *self,
428 unsigned long phase, void *p)
429{
430 switch (phase) {
431 case CPUFREQ_POSTCHANGE:
432 stmp3xxx_dma_resume();
433 break;
434
435 case CPUFREQ_PRECHANGE:
436 stmp3xxx_dma_suspend();
437 break;
438
439 default:
440 break;
441 }
442
443 return NOTIFY_DONE;
444}
445
446static struct dma_notifier_block dma_cpufreq_nb = {
447 .nb = {
448 .notifier_call = dma_cpufreq_notifier,
449 },
450};
451#endif /* CONFIG_CPU_FREQ */
452
453void __init stmp3xxx_dma_init(void)
454{
455 HW_APBH_CTRL0_CLR(BM_APBH_CTRL0_CLKGATE | BM_APBH_CTRL0_SFTRST);
456 HW_APBX_CTRL0_CLR(BM_APBX_CTRL0_CLKGATE | BM_APBX_CTRL0_SFTRST);
457#ifdef CONFIG_CPU_FREQ
458 cpufreq_register_notifier(&dma_cpufreq_nb.nb,
459 CPUFREQ_TRANSITION_NOTIFIER);
460#endif /* CONFIG_CPU_FREQ */
461
462}