]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blob - sound/arm/omap-alsa-dma.c
6bcf0f7c76480adcf366a1bfe173ab2b696bacdf
[linux-2.6-omap-h63xx.git] / sound / arm / omap-alsa-dma.c
1 /*
2  * sound/arm/omap-alsa-dma.c
3  *
4  * Common audio DMA handling for the OMAP processors
5  *
6  * Copyright (C) 2006 Mika Laitio <lamikr@cc.jyu.fi>
7  *
8  * Copyright (C) 2005 Instituto Nokia de Tecnologia - INdT - Manaus Brazil
9  * 
10  * Copyright (C) 2004 Texas Instruments, Inc.
11  *
12  * Copyright (C) 2000, 2001 Nicolas Pitre <nico@cam.org>
13  *
14  * This package is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License version 2 as
16  * published by the Free Software Foundation.
17  *
18  * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
20  * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
21  *
22  * History:
23  *
24  * 2004-06-07   Sriram Kannan   - Created new file from omap_audio_dma_intfc.c. This file
25  *                                will contain only the DMA interface and buffer handling of OMAP
26  *                                audio driver.
27  *
28  * 2004-06-22   Sriram Kannan   - removed legacy code (auto-init). Self-linking of DMA logical channel.
29  *
30  * 2004-08-12   Nishanth Menon  - Modified to integrate Audio requirements on 1610,1710 platforms
31  *
32  * 2004-11-01   Nishanth Menon  - 16xx platform code base modified to support multi channel chaining.
33  *
34  * 2004-12-15   Nishanth Menon  - Improved 16xx platform channel logic introduced - tasklets, queue handling updated
35  * 
36  * 2005-07-19   INdT Kernel Team - Alsa port. Creation of new file omap-alsa-dma.c based in
37  *                                 omap-audio-dma-intfc.c oss file. Support for aic23 codec.
38  *                                 Removal of buffer handling (Alsa does that), modifications
39  *      in dma handling and port to alsa structures.
40  *
41  * 2005-12-18   Dirk Behme      - Added L/R Channel Interchange fix as proposed by Ajaya Babu
42  */
43
44 #include <linux/config.h>
45 #include <linux/module.h>
46 #include <linux/init.h>
47 #include <linux/types.h>
48 #include <linux/fs.h>
49 #include <linux/mm.h>
50 #include <linux/slab.h>
51 #include <linux/sched.h>
52 #include <linux/poll.h>
53 #include <linux/pm.h>
54 #include <linux/errno.h>
55 #include <linux/sound.h>
56 #include <linux/soundcard.h>
57 #include <linux/sysrq.h>
58 #include <linux/interrupt.h>
59 #include <linux/dma-mapping.h>
60
61 #include <asm/uaccess.h>
62 #include <asm/io.h>
63 #include <asm/hardware.h>
64 #include <asm/semaphore.h>
65
66 #include <asm/arch/dma.h>
67 #include "omap-alsa-dma.h"
68
69 #include <asm/arch/mcbsp.h>
70
71 #undef DEBUG
72 #define DEBUG
73 #ifdef DEBUG
74 #define DPRINTK(ARGS...)  printk(KERN_INFO "<%s>: ",__FUNCTION__);printk(ARGS)
75 #define FN_IN printk(KERN_INFO "[%s]: start\n", __FUNCTION__)
76 #define FN_OUT(n) printk(KERN_INFO "[%s]: end(%u)\n",__FUNCTION__, n)
77 #else
78
79 #define DPRINTK( x... )
80 #define FN_IN
81 #define FN_OUT(x)
82 #endif
83
84 #define ERR(ARGS...) printk(KERN_ERR "{%s}-ERROR: ", __FUNCTION__);printk(ARGS);
85
86 /* Channel Queue Handling macros
87  * tail always points to the current free entry
88  * Head always points to the current entry being used
89  * end is either head or tail
90  */
91
92 #define AUDIO_QUEUE_INIT(s) s->dma_q_head = s->dma_q_tail = s->dma_q_count = 0;
93 #define AUDIO_QUEUE_FULL(s) (nr_linked_channels == s->dma_q_count)
94 #define AUDIO_QUEUE_LAST(s) (1 == s->dma_q_count)
95 #define AUDIO_QUEUE_EMPTY(s) (0 == s->dma_q_count)
96 #define __AUDIO_INCREMENT_QUEUE(end) ((end)=((end)+1) % nr_linked_channels)
97 #define AUDIO_INCREMENT_HEAD(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_head); s->dma_q_count--;
98 #define AUDIO_INCREMENT_TAIL(s) __AUDIO_INCREMENT_QUEUE(s->dma_q_tail); s->dma_q_count++;
99
100 /* DMA buffer fragmentation sizes */
101 #define MAX_DMA_SIZE             0x1000000 /* todo: sync with alsa */
102 //#define CUT_DMA_SIZE           0x1000
103 /* TODO: To be moved to more appropriate location */
104 #define DCSR_ERROR           0x3
105 #define DCSR_END_BLOCK       (1 << 5)
106 #define DCSR_SYNC_SET        (1 << 6)
107
108 #define DCCR_FS              (1 << 5)
109 #define DCCR_PRIO            (1 << 6)
110 #define DCCR_EN              (1 << 7)
111 #define DCCR_AI              (1 << 8)
112 #define DCCR_REPEAT          (1 << 9)
113 /* if 0 the channel works in 3.1 compatible mode*/
114 #define DCCR_N31COMP         (1 << 10)
115 #define DCCR_EP              (1 << 11)
116 #define DCCR_SRC_AMODE_BIT   12
117 #define DCCR_SRC_AMODE_MASK  (0x3<<12)
118 #define DCCR_DST_AMODE_BIT   14
119 #define DCCR_DST_AMODE_MASK  (0x3<<14)
120 #define AMODE_CONST          0x0
121 #define AMODE_POST_INC       0x1
122 #define AMODE_SINGLE_INDEX   0x2
123 #define AMODE_DOUBLE_INDEX   0x3
124
125 /**************************** DATA STRUCTURES *****************************************/
126
127 static spinlock_t dma_list_lock = SPIN_LOCK_UNLOCKED;
128
129 static char nr_linked_channels = 1;
130
131 /*********************************** MODULE SPECIFIC FUNCTIONS ***********************/
132
133 static void sound_dma_irq_handler(int lch, u16 ch_status, void *data);
134 static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
135                                      u_int dma_size);
136 static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
137                                         u_int dma_size);
138 static int audio_start_dma_chain(struct audio_stream * s);
139
140 /***************************************************************************************
141  *
142  * DMA channel requests
143  *
144  **************************************************************************************/
145 static void omap_sound_dma_link_lch(void *data)
146 {
147
148         struct audio_stream *s = (struct audio_stream *) data;
149         int *chan = s->lch;
150         int i;
151
152         FN_IN;
153         if (s->linked) {
154                 FN_OUT(1);
155                 return;
156         }
157         for (i = 0; i < nr_linked_channels; i++) {
158                 int cur_chan = chan[i];
159                 int nex_chan =
160                     ((nr_linked_channels - 1 ==
161                       i) ? chan[0] : chan[i + 1]);
162                 omap_dma_link_lch(cur_chan, nex_chan);
163         }
164         s->linked = 1;
165         FN_OUT(0);
166 }
167
168 int omap_request_alsa_sound_dma(int device_id, const char *device_name,
169                            void *data, int **channels)
170 {
171         int i, err = 0;
172         int *chan = NULL;
173         FN_IN;
174         if (unlikely((NULL == channels) || (NULL == device_name))) {
175                 BUG();
176                 return -EPERM;
177         }
178         /* Try allocate memory for the num channels */
179         *channels =
180             (int *) kmalloc(sizeof(int) * nr_linked_channels, GFP_KERNEL);
181         chan = *channels;
182         if (NULL == chan) {
183                 ERR("No Memory for channel allocs!\n");
184                 FN_OUT(-ENOMEM);
185                 return -ENOMEM;
186         }
187         spin_lock(&dma_list_lock);
188         for (i = 0; i < nr_linked_channels; i++) {
189                 err = omap_request_dma(device_id,
190                                 device_name,
191                                 sound_dma_irq_handler,
192                                 data,
193                                 &chan[i]);
194
195                 /* Handle Failure condition here */
196                 if (err < 0) {
197                         int j;
198                         for (j = 0; j < i; j++) {
199                                 omap_free_dma(chan[j]);
200                         }
201                         spin_unlock(&dma_list_lock);
202                         kfree(chan);
203                         *channels = NULL;
204                         ERR("Error in requesting channel %d=0x%x\n", i,
205                             err);
206                         FN_OUT(err);
207                         return err;
208                 }
209         }
210
211         /* Chain the channels together */
212         if (!cpu_is_omap1510())
213                 omap_sound_dma_link_lch(data);
214
215         spin_unlock(&dma_list_lock);
216         FN_OUT(0);
217         return 0;
218 }
219
220 /***************************************************************************************
221  *
222  * DMA channel requests Freeing
223  *
224  **************************************************************************************/
225 static void omap_sound_dma_unlink_lch(void *data)
226 {
227         struct audio_stream *s = (struct audio_stream *)data;
228         int *chan = s->lch;
229         int i;
230
231         FN_IN;
232         if (!s->linked) {
233                 FN_OUT(1);
234                 return;
235         }
236         for (i = 0; i < nr_linked_channels; i++) {
237                 int cur_chan = chan[i];
238                 int nex_chan =
239                     ((nr_linked_channels - 1 ==
240                       i) ? chan[0] : chan[i + 1]);
241                 omap_dma_unlink_lch(cur_chan, nex_chan);
242         }
243         s->linked = 0;
244         FN_OUT(0);
245 }
246
247 int omap_free_alsa_sound_dma(void *data, int **channels)
248 {
249         int i;
250         int *chan = NULL;
251
252         FN_IN;
253         if (unlikely(NULL == channels)) {
254                 BUG();
255                 return -EPERM;
256         }
257         if (unlikely(NULL == *channels)) {
258                 BUG();
259                 return -EPERM;
260         }
261         chan = (*channels);
262
263         if (!cpu_is_omap1510())
264                 omap_sound_dma_unlink_lch(data);
265         for (i = 0; i < nr_linked_channels; i++) {
266                 int cur_chan = chan[i];
267                 omap_stop_dma(cur_chan);
268                 omap_free_dma(cur_chan);
269         }
270         kfree(*channels);
271         *channels = NULL;
272         FN_OUT(0);
273         return 0;
274 }
275
276 /***************************************************************************************
277  *
278  * Stop all the DMA channels of the stream
279  *
280  **************************************************************************************/
281 void omap_stop_alsa_sound_dma(struct audio_stream *s)
282 {
283         int *chan = s->lch;
284         int i;
285
286         FN_IN;
287         if (unlikely(NULL == chan)) {
288                 BUG();
289                 return;
290         }
291         for (i = 0; i < nr_linked_channels; i++) {
292                 int cur_chan = chan[i];
293                 omap_stop_dma(cur_chan);
294         }
295         s->started = 0;
296         FN_OUT(0);
297         return;
298 }
299 /***************************************************************************************
300  *
301  * Clear any pending transfers
302  *
303  **************************************************************************************/
304 void omap_clear_alsa_sound_dma(struct audio_stream * s)
305 {
306         FN_IN;
307         omap_clear_dma(s->lch[s->dma_q_head]);
308         FN_OUT(0);
309         return;
310 }
311
312 /***************************************************************************************
313  *
314  * DMA related functions
315  *
316  **************************************************************************************/
317 static int audio_set_dma_params_play(int channel, dma_addr_t dma_ptr,
318                                      u_int dma_size)
319 {
320         int dt = 0x1;           /* data type 16 */
321         int cen = 32;           /* Stereo */
322         int cfn = dma_size / (2 * cen);
323
324         FN_IN;
325         omap_set_dma_dest_params(channel, 0x05, 0x00,
326                                  (OMAP1510_MCBSP1_BASE + 0x06),
327                                  0, 0);
328         omap_set_dma_src_params(channel, 0x00, 0x01, dma_ptr,
329                                 0, 0);
330         omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0);
331         FN_OUT(0);
332         return 0;
333 }
334
335 static int audio_set_dma_params_capture(int channel, dma_addr_t dma_ptr,
336                                         u_int dma_size)
337 {
338         int dt = 0x1;           /* data type 16 */
339         int cen = 32;           /* stereo */
340         int cfn = dma_size / (2 * cen);
341
342         FN_IN;
343         omap_set_dma_src_params(channel, 0x05, 0x00,
344                                 (OMAP1510_MCBSP1_BASE + 0x02),
345                                 0, 0);
346         omap_set_dma_dest_params(channel, 0x00, 0x01, dma_ptr, 0, 0);
347         omap_set_dma_transfer_params(channel, dt, cen, cfn, 0x00, 0, 0);
348         FN_OUT(0);
349         return 0;
350 }
351
352 static int audio_start_dma_chain(struct audio_stream *s)
353 {
354         int channel = s->lch[s->dma_q_head];
355         FN_IN;
356         if (!s->started) {
357                 s->hw_stop();      /* stops McBSP Interface */
358                 omap_start_dma(channel);
359                 s->started = 1;
360                 s->hw_start();     /* start McBSP interface */
361         }
362         /* else the dma itself will progress forward with out our help */
363         FN_OUT(0);
364         return 0;
365 }
366
367 /* Start DMA -
368  * Do the initial set of work to initialize all the channels as required.
369  * We shall then initate a transfer
370  */
371 int omap_start_alsa_sound_dma(struct audio_stream *s,
372                         dma_addr_t dma_ptr,
373                         u_int dma_size)
374 {
375         int ret = -EPERM;
376
377         FN_IN;
378
379         if (unlikely(dma_size > MAX_DMA_SIZE)) {
380                 ERR("DmaSoundDma: Start: overflowed %d-%d\n", dma_size,
381                     MAX_DMA_SIZE);
382                 return -EOVERFLOW;
383         }
384         //if (AUDIO_QUEUE_FULL(s)) {
385         //      ret = -2;
386         //      goto sound_out;
387         //}
388
389         if (s->stream_id == SNDRV_PCM_STREAM_PLAYBACK) {
390                 /*playback */
391                 ret =
392                     audio_set_dma_params_play(s->lch[s->dma_q_tail],
393                                               dma_ptr, dma_size);
394         } else {
395                 ret =
396                     audio_set_dma_params_capture(s->lch[s->dma_q_tail],
397                                                  dma_ptr, dma_size);
398         }
399         if (ret != 0) {
400                 ret = -3;       /* indicate queue full */
401                 goto sound_out;
402         }
403         AUDIO_INCREMENT_TAIL(s);
404         ret = audio_start_dma_chain(s);
405         if (ret) {
406                 ERR("dma start failed");
407         }
408       sound_out:
409         FN_OUT(ret);
410         return ret;
411
412 }
413
414 /* 
415  * ISRs have to be short and smart.. 
416  * Here we call alsa handling, after some error checking
417  */
418 static void sound_dma_irq_handler(int sound_curr_lch, u16 ch_status,
419                                   void *data)
420 {
421         int dma_status = ch_status;
422         struct audio_stream *s = (struct audio_stream *) data;
423         FN_IN;
424
425         /*
426          * some register checkings
427          */ 
428         DPRINTK("lch=%d,status=0x%x, dma_status=%d, data=%p\n",
429                 sound_curr_lch, ch_status, dma_status, data);
430
431         if (dma_status & (DCSR_ERROR)) {
432                 OMAP_DMA_CCR_REG(sound_curr_lch) &= ~DCCR_EN;
433                 ERR("DCSR_ERROR!\n");
434                 FN_OUT(-1);
435                 return;
436         }
437
438         if (ch_status & DCSR_END_BLOCK) 
439                 callback_omap_alsa_sound_dma(s);
440         FN_OUT(0);
441         return;
442 }
443
444 MODULE_AUTHOR("Texas Instruments");
445 MODULE_DESCRIPTION("Common DMA handling for Audio driver on OMAP processors");
446 MODULE_LICENSE("GPL");
447
448 EXPORT_SYMBOL(omap_start_alsa_sound_dma);
449 EXPORT_SYMBOL(omap_clear_alsa_sound_dma);
450 EXPORT_SYMBOL(omap_request_alsa_sound_dma);
451 EXPORT_SYMBOL(omap_free_alsa_sound_dma);
452 EXPORT_SYMBOL(omap_stop_alsa_sound_dma);