[ALSA] sparc dbri: ring buffered version
Krzysztof Helt [Mon, 21 Aug 2006 17:30:57 +0000 (19:30 +0200)]
It is a complete rework of low level layer to work on ring
buffers for comands and data descriptors. This removes annoying
noise due to delay in data buffer switching.

Signed-off-by: Krzysztof Helt <krzysztof.h1@wp.pl>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@suse.cz>

sound/sparc/dbri.c

index 5696f79..3fb2ede 100644 (file)
@@ -2,6 +2,8 @@
  * Driver for DBRI sound chip found on Sparcs.
  * Copyright (C) 2004, 2005 Martin Habets (mhabets@users.sourceforge.net)
  *
+ * Converted to ring buffered version by Krzysztof Helt (krzysztof.h1@wp.pl)
+ *
  * Based entirely upon drivers/sbus/audio/dbri.c which is:
  * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
  * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org)
@@ -260,7 +262,7 @@ struct dbri_mem {
  * the CPU and the DBRI
  */
 struct dbri_dma {
-       volatile s32 cmd[DBRI_NO_CMDS]; /* Place for commands       */
+       s32 cmd[DBRI_NO_CMDS];                  /* Place for commands */
        volatile s32 intr[DBRI_INT_BLK];        /* Interrupt field  */
        struct dbri_mem desc[DBRI_NO_DESCS];    /* Xmit/receive descriptors */
 };
@@ -284,7 +286,6 @@ struct dbri_pipe {
 struct dbri_streaminfo {
        struct snd_pcm_substream *substream;
        u32 dvma_buffer;        /* Device view of Alsa DMA buffer */
-       int left;               /* # of bytes left in DMA buffer  */
        int size;               /* Size of DMA buffer             */
        size_t offset;          /* offset in user buffer          */
        int pipe;               /* Data pipe used                 */
@@ -305,11 +306,11 @@ struct snd_dbri {
 
        void __iomem *regs;     /* dbri HW regs */
        int dbri_irqp;          /* intr queue pointer */
-       int wait_send;          /* sequence of command buffers send */
-       int wait_ackd;          /* sequence of command buffers acknowledged */
 
        struct dbri_pipe pipes[DBRI_NO_PIPES];  /* DBRI's 32 data pipes */
        int next_desc[DBRI_NO_DESCS];           /* Index of next desc, or -1 */
+       spinlock_t cmdlock;     /* Protects cmd queue accesses */
+       s32 *cmdptr;            /* Pointer to the last queued cmd */
 
        int chi_bpf;
 
@@ -544,7 +545,7 @@ struct snd_dbri {
 #define DBRI_TD_TBC    (1<<0)  /* Transmit buffer Complete */
 #define DBRI_TD_STATUS(v)       ((v)&0xff)     /* Transmit status */
                        /* Maximum buffer size per TD: almost 8Kb */
-#define DBRI_TD_MAXCNT ((1 << 13) - 1)
+#define DBRI_TD_MAXCNT ((1 << 13) - 4)
 
 /* Receive descriptor defines */
 #define DBRI_RD_F      (1<<31) /* End of Frame */
@@ -608,79 +609,110 @@ The list is terminated with a WAIT command, which generates a
 CPU interrupt to signal completion.
 
 Since the DBRI can run in parallel with the CPU, several means of
-synchronization present themselves.  The method implemented here is close
-to the original scheme (Rudolf's), and uses 2 counters (wait_send and
-wait_ackd) to synchronize the command buffer between the CPU and the DBRI.
+synchronization present themselves. The method implemented here is only
+to use the dbri_cmdwait() to wait for execution of batch of sent commands.
 
-A more sophisticated scheme might involve a circular command buffer
-or an array of command buffers.  A routine could fill one with
-commands and link it onto a list.  When a interrupt signaled
-completion of the current command buffer, look on the list for
-the next one.
+A circular command buffer is used here. A new command is being added 
+while other can be executed. The scheme works by adding two WAIT commands
+after each sent batch of commands. When the next batch is prepared it is
+added after the WAIT commands then the WAITs are replaced with single JUMP
+command to the new batch. The the DBRI is forced to reread the last WAIT 
+command (replaced by the JUMP by then). If the DBRI is still executing 
+previous commands the request to reread the WAIT command is ignored.
 
 Every time a routine wants to write commands to the DBRI, it must
-first call dbri_cmdlock() and get an initial pointer into dbri->dma->cmd
-in return. dbri_cmdlock() will block if the previous commands have not
-been completed yet. After this the commands can be written to the buffer,
-and dbri_cmdsend() is called with the final pointer value to send them
-to the DBRI.
+first call dbri_cmdlock() and get pointer to a free space in 
+dbri->dma->cmd buffer. After this, the commands can be written to 
+the buffer, and dbri_cmdsend() is called with the final pointer value 
+to send them to the DBRI.
 
 */
 
 static void dbri_process_interrupt_buffer(struct snd_dbri * dbri);
 
-enum dbri_lock { NoGetLock, GetLock };
 #define MAXLOOPS 10
-
-static volatile s32 *dbri_cmdlock(struct snd_dbri * dbri, enum dbri_lock get)
+/*
+ * Wait for the current command string to execute
+ */
+static void dbri_cmdwait(struct snd_dbri *dbri)
 {
        int maxloops = MAXLOOPS;
 
-#ifndef SMP
-       if ((get == GetLock) && spin_is_locked(&dbri->lock)) {
-               printk(KERN_ERR "DBRI: cmdlock called while in spinlock.");
-       }
-#endif
-
        /* Delay if previous commands are still being processed */
-       while ((--maxloops) > 0 && (dbri->wait_send != dbri->wait_ackd)) {
+       while ((--maxloops) > 0 && (sbus_readl(dbri->regs + REG0) & D_P))
                msleep_interruptible(1);
-       }
+
        if (maxloops == 0) {
-               printk(KERN_ERR "DBRI: Chip never completed command buffer %d\n",
-                       dbri->wait_send);
+               printk(KERN_ERR "DBRI: Chip never completed command buffer\n");
        } else {
                dprintk(D_CMD, "Chip completed command buffer (%d)\n",
                        MAXLOOPS - maxloops - 1);
        }
+}
+/*
+ * Lock the command queue and returns pointer to a space for len cmd words
+ * It locks the cmdlock spinlock.
+ */
+static s32 *dbri_cmdlock(struct snd_dbri * dbri, int len)
+{
+       /* Space for 2 WAIT cmds (replaced later by 1 JUMP cmd) */
+       len += 2;
+       spin_lock(&dbri->cmdlock);
+       if (dbri->cmdptr - dbri->dma->cmd + len < DBRI_NO_CMDS - 2)
+               return dbri->cmdptr + 2;
+       else if (len < sbus_readl(dbri->regs + REG8) - dbri->dma_dvma)
+               return dbri->dma->cmd;
+       else
+               printk(KERN_ERR "DBRI: no space for commands.");
 
-       /*if (get == GetLock) spin_lock(&dbri->lock); */
-       return &dbri->dma->cmd[0];
+       return 0;
 }
 
-static void dbri_cmdsend(struct snd_dbri * dbri, volatile s32 * cmd)
+/*
+ * Send prepared cmd string. It works by writting a JMP cmd into
+ * the last WAIT cmd and force DBRI to reread the cmd.
+ * The JMP cmd points to the new cmd string.
+ * It also releases the cmdlock spinlock.
+ */
+static void dbri_cmdsend(struct snd_dbri * dbri, s32 * cmd,int len)
 {
-       volatile s32 *ptr;
+       s32 *ptr;
+       s32 tmp, addr;
+       static int wait_id = 0;
 
-       for (ptr = &dbri->dma->cmd[0]; ptr < cmd; ptr++) {
-               dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
-       }
+       wait_id++;
+       wait_id &= 0xffff;      /* restrict it to a 16 bit counter. */
+       *(cmd) = DBRI_CMD(D_WAIT, 1, wait_id);
+       *(cmd+1) = DBRI_CMD(D_WAIT, 1, wait_id);
 
-       if ((cmd - &dbri->dma->cmd[0]) >= DBRI_NO_CMDS - 1) {
-               printk(KERN_ERR "DBRI: Command buffer overflow! (bug in driver)\n");
-               /* Ignore the last part. */
-               cmd = &dbri->dma->cmd[DBRI_NO_CMDS - 3];
-       }
+       /* Replace the last command with JUMP */
+       addr = dbri->dma_dvma + (cmd - len - dbri->dma->cmd) * sizeof(s32);
+       *(dbri->cmdptr+1) = addr;
+       *(dbri->cmdptr) = DBRI_CMD(D_JUMP, 0, 0);
 
-       dbri->wait_send++;
-       dbri->wait_send &= 0xffff;      /* restrict it to a 16 bit counter. */
-       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
-       *(cmd++) = DBRI_CMD(D_WAIT, 1, dbri->wait_send);
+#ifdef DBRI_DEBUG
+       if (cmd > dbri->cmdptr )
+               for (ptr = dbri->cmdptr; ptr < cmd+2; ptr++) {
+                       dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+               }
+       else {
+               ptr = dbri->cmdptr;
+               dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+               ptr = dbri->cmdptr+1;
+               dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+               for (ptr = dbri->dma->cmd; ptr < cmd+2; ptr++) {
+                       dprintk(D_CMD, "cmd: %lx:%08x\n", (unsigned long)ptr, *ptr);
+               }
+       }
+#endif
 
-       /* Set command pointer and signal it is valid. */
-       sbus_writel(dbri->dma_dvma, dbri->regs + REG8);
+       /* Reread the last command */
+       tmp = sbus_readl(dbri->regs + REG0);
+       tmp |= D_P;
+       sbus_writel(tmp, dbri->regs + REG0);
 
-       /*spin_unlock(&dbri->lock); */
+       dbri->cmdptr = cmd;
+       spin_unlock(&dbri->cmdlock);
 }
 
 /* Lock must be held when calling this */
@@ -709,7 +741,7 @@ static void dbri_reset(struct snd_dbri * dbri)
 /* Lock must not be held before calling this */
 static void dbri_initialize(struct snd_dbri * dbri)
 {
-       volatile s32 *cmd;
+       s32 *cmd;
        u32 dma_addr;
        unsigned long flags;
        int n;
@@ -718,14 +750,11 @@ static void dbri_initialize(struct snd_dbri * dbri)
 
        dbri_reset(dbri);
 
-       cmd = dbri_cmdlock(dbri, NoGetLock);
-       dprintk(D_GEN, "init: cmd: %p, int: %p\n",
-               &dbri->dma->cmd[0], &dbri->dma->intr[0]);
-
        /* Initialize pipes */
        for (n = 0; n < DBRI_NO_PIPES; n++)
                dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1;
 
+       spin_lock_init(&dbri->cmdlock);
        /*
         * Initialize the interrupt ringbuffer.
         */
@@ -735,10 +764,19 @@ static void dbri_initialize(struct snd_dbri * dbri)
        /*
         * Set up the interrupt queue
         */
+       spin_lock(&dbri->cmdlock);
+       cmd = dbri->cmdptr = dbri->dma->cmd;
        *(cmd++) = DBRI_CMD(D_IIQ, 0, 0);
        *(cmd++) = dma_addr;
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+       dbri->cmdptr = cmd;
+       *(cmd++) = DBRI_CMD(D_WAIT, 1, 0);
+       *(cmd++) = DBRI_CMD(D_WAIT, 1, 0);
+       dma_addr = dbri->dma_dvma + dbri_dma_off(cmd, 0);
+       sbus_writel(dma_addr, dbri->regs + REG8);
+       spin_unlock(&dbri->cmdlock);
+       dbri_cmdwait(dbri);
 
-       dbri_cmdsend(dbri, cmd);
        spin_unlock_irqrestore(&dbri->lock, flags);
 }
 
@@ -770,7 +808,7 @@ static void reset_pipe(struct snd_dbri * dbri, int pipe)
 {
        int sdp;
        int desc;
-       volatile int *cmd;
+       s32 *cmd;
 
        if (pipe < 0 || pipe > DBRI_MAX_PIPE) {
                printk(KERN_ERR "DBRI: reset_pipe called with illegal pipe number\n");
@@ -783,16 +821,18 @@ static void reset_pipe(struct snd_dbri * dbri, int pipe)
                return;
        }
 
-       cmd = dbri_cmdlock(dbri, NoGetLock);
+       cmd = dbri_cmdlock(dbri, 3);
        *(cmd++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P);
        *(cmd++) = 0;
-       dbri_cmdsend(dbri, cmd);
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+       dbri_cmdsend(dbri, cmd, 3);
 
        desc = dbri->pipes[pipe].first_desc;
-       while (desc != -1) {
-               dbri->dma->desc[desc].nda = dbri->dma->desc[desc].ba = 0;
-               desc = dbri->next_desc[desc];
-       }
+       if ( desc >= 0)
+               do {
+                       dbri->dma->desc[desc].nda = dbri->dma->desc[desc].ba = 0;
+                       desc = dbri->next_desc[desc];
+               } while (desc != -1 && desc != dbri->pipes[pipe].first_desc);
 
        dbri->pipes[pipe].desc = -1;
        dbri->pipes[pipe].first_desc = -1;
@@ -828,7 +868,7 @@ static void link_time_slot(struct snd_dbri * dbri, int pipe,
                           int prevpipe, int nextpipe,
                           int length, int cycle)
 {
-       volatile s32 *cmd;
+       s32 *cmd;
        int val;
 
        if (pipe < 0 || pipe > DBRI_MAX_PIPE 
@@ -847,11 +887,10 @@ static void link_time_slot(struct snd_dbri * dbri, int pipe,
        }
 
        dbri->pipes[prevpipe].nextpipe = pipe;
-
        dbri->pipes[pipe].nextpipe = nextpipe;
        dbri->pipes[pipe].length = length;
 
-       cmd = dbri_cmdlock(dbri, NoGetLock);
+       cmd = dbri_cmdlock(dbri, 4);
 
        if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
                /* Deal with CHI special case:
@@ -874,25 +913,27 @@ static void link_time_slot(struct snd_dbri * dbri, int pipe,
                    D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
                *(cmd++) = 0;
        }
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
 
-       dbri_cmdsend(dbri, cmd);
+       dbri_cmdsend(dbri, cmd, 4);
 }
 
 static void unlink_time_slot(struct snd_dbri * dbri, int pipe,
                             enum in_or_out direction, int prevpipe,
                             int nextpipe)
 {
-       volatile s32 *cmd;
+       s32 *cmd;
        int val;
 
        if (pipe < 0 || pipe > DBRI_MAX_PIPE 
-                       || prevpipe < 0 || prevpipe > DBRI_MAX_PIPE) {
+                       || prevpipe < 0 || prevpipe > DBRI_MAX_PIPE
+                       || nextpipe < 0 || nextpipe > DBRI_MAX_PIPE) {
                printk(KERN_ERR 
                    "DBRI: unlink_time_slot called with illegal pipe number\n");
                return;
        }
 
-       cmd = dbri_cmdlock(dbri, NoGetLock);
+       cmd = dbri_cmdlock(dbri, 4);
 
        if (direction == PIPEinput) {
                val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe;
@@ -905,8 +946,9 @@ static void unlink_time_slot(struct snd_dbri * dbri, int pipe,
                *(cmd++) = 0;
                *(cmd++) = D_TS_NEXT(nextpipe);
        }
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
 
-       dbri_cmdsend(dbri, cmd);
+       dbri_cmdsend(dbri, cmd, 4);
 }
 
 /* xmit_fixed() / recv_fixed()
@@ -925,7 +967,7 @@ static void unlink_time_slot(struct snd_dbri * dbri, int pipe,
  */
 static void xmit_fixed(struct snd_dbri * dbri, int pipe, unsigned int data)
 {
-       volatile s32 *cmd;
+       s32 *cmd;
 
        if (pipe < 16 || pipe > DBRI_MAX_PIPE) {
                printk(KERN_ERR "DBRI: xmit_fixed: Illegal pipe number\n");
@@ -952,12 +994,14 @@ static void xmit_fixed(struct snd_dbri * dbri, int pipe, unsigned int data)
        if (dbri->pipes[pipe].sdp & D_SDP_MSB)
                data = reverse_bytes(data, dbri->pipes[pipe].length);
 
-       cmd = dbri_cmdlock(dbri, GetLock);
+       cmd = dbri_cmdlock(dbri, 3);
 
        *(cmd++) = DBRI_CMD(D_SSP, 0, pipe);
        *(cmd++) = data;
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
 
-       dbri_cmdsend(dbri, cmd);
+       dbri_cmdsend(dbri, cmd, 3);
+       dbri_cmdwait(dbri);
 }
 
 static void recv_fixed(struct snd_dbri * dbri, int pipe, volatile __u32 * ptr)
@@ -991,6 +1035,8 @@ static void recv_fixed(struct snd_dbri * dbri, int pipe, volatile __u32 * ptr)
  * and work by building chains of descriptors which identify the
  * data buffers.  Buffers too large for a single descriptor will
  * be spread across multiple descriptors.
+ *
+ * All descriptors create a ring buffer.
  */
 static int setup_descs(struct snd_dbri * dbri, int streamno, unsigned int period)
 {
@@ -1051,14 +1097,13 @@ static int setup_descs(struct snd_dbri * dbri, int streamno, unsigned int period
                        return -1;
                }
 
-               if (len > DBRI_TD_MAXCNT) {
-                       mylen = DBRI_TD_MAXCNT; /* 8KB - 1 */
-               } else {
+               if (len > DBRI_TD_MAXCNT)
+                       mylen = DBRI_TD_MAXCNT; /* 8KB - 4 */
+               else
                        mylen = len;
-               }
-               if (mylen > period) {
+
+               if (mylen > period)
                        mylen = period;
-               }
 
                dbri->next_desc[desc] = -1;
                dbri->dma->desc[desc].ba = dvma_buffer;
@@ -1067,17 +1112,17 @@ static int setup_descs(struct snd_dbri * dbri, int streamno, unsigned int period
                if (streamno == DBRI_PLAY) {
                        dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen);
                        dbri->dma->desc[desc].word4 = 0;
-                       if (first_desc != -1)
-                               dbri->dma->desc[desc].word1 |= DBRI_TD_M;
+                       dbri->dma->desc[desc].word1 |= 
+                           DBRI_TD_F | DBRI_TD_B;
                } else {
                        dbri->dma->desc[desc].word1 = 0;
                        dbri->dma->desc[desc].word4 =
                            DBRI_RD_B | DBRI_RD_BCNT(mylen);
                }
 
-               if (first_desc == -1) {
+               if (first_desc == -1)
                        first_desc = desc;
-               } else {
+               else {
                        dbri->next_desc[last_desc] = desc;
                        dbri->dma->desc[last_desc].nda =
                            dbri->dma_dvma + dbri_dma_off(desc, desc);
@@ -1093,21 +1138,28 @@ static int setup_descs(struct snd_dbri * dbri, int streamno, unsigned int period
                return -1;
        }
 
-       dbri->dma->desc[last_desc].word1 &= ~DBRI_TD_M;
        if (streamno == DBRI_PLAY) {
                dbri->dma->desc[last_desc].word1 |=
-                   DBRI_TD_I | DBRI_TD_F | DBRI_TD_B;
+                   DBRI_TD_F | DBRI_TD_B;
+               dbri->dma->desc[last_desc].nda =
+                   dbri->dma_dvma + dbri_dma_off(desc, first_desc);
+               dbri->next_desc[last_desc] = first_desc;
        }
        dbri->pipes[info->pipe].first_desc = first_desc;
        dbri->pipes[info->pipe].desc = first_desc;
 
-       for (desc = first_desc; desc != -1; desc = dbri->next_desc[desc]) {
+#ifdef DBRI_DEBUG
+       for (desc = first_desc; desc != -1; ) {
                dprintk(D_DESC, "DESC %d: %08x %08x %08x %08x\n",
                        desc,
                        dbri->dma->desc[desc].word1,
                        dbri->dma->desc[desc].ba,
                        dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4);
+                       desc = dbri->next_desc[desc];
+                       if ( desc == first_desc )
+                               break;
        }
+#endif
        return 0;
 }
 
@@ -1127,43 +1179,24 @@ enum master_or_slave { CHImaster, CHIslave };
 static void reset_chi(struct snd_dbri * dbri, enum master_or_slave master_or_slave,
                      int bits_per_frame)
 {
-       volatile s32 *cmd;
+       s32 *cmd;
        int val;
-       static int chi_initialized = 0; /* FIXME: mutex? */
-
-       if (!chi_initialized) {
 
-               cmd = dbri_cmdlock(dbri, GetLock);
+       /* Set CHI Anchor: Pipe 16 */
 
-               /* Set CHI Anchor: Pipe 16 */
-
-               val = D_DTS_VO | D_DTS_VI | D_DTS_INS 
-                       | D_DTS_PRVIN(16) | D_PIPE(16) | D_DTS_PRVOUT(16);
-               *(cmd++) = DBRI_CMD(D_DTS, 0, val);
-               *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
-               *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
+       cmd = dbri_cmdlock(dbri, 4);
+       val = D_DTS_VO | D_DTS_VI | D_DTS_INS 
+               | D_DTS_PRVIN(16) | D_PIPE(16) | D_DTS_PRVOUT(16);
+       *(cmd++) = DBRI_CMD(D_DTS, 0, val);
+       *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
+       *(cmd++) = D_TS_ANCHOR | D_TS_NEXT(16);
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
+       dbri_cmdsend(dbri, cmd, 4);
 
-               dbri->pipes[16].sdp = 1;
-               dbri->pipes[16].nextpipe = 16;
+       dbri->pipes[16].sdp = 1;
+       dbri->pipes[16].nextpipe = 16;
 
-#if 0
-               chi_initialized++;
-#endif
-       } else {
-               int pipe;
-
-               for (pipe = 0; pipe < DBRI_NO_PIPES; pipe++ )
-                       if ( pipe != 16 ) {
-                               if (dbri->pipes[pipe].sdp & D_SDP_TO_SER)
-                                       unlink_time_slot(dbri, pipe, PIPEoutput,
-                                                        16, dbri->pipes[pipe].nextpipe);
-                               else
-                                       unlink_time_slot(dbri, pipe, PIPEinput,
-                                                        16, dbri->pipes[pipe].nextpipe);
-                       }
-  
-               cmd = dbri_cmdlock(dbri, GetLock);
-       }
+       cmd = dbri_cmdlock(dbri, 4);
 
        if (master_or_slave == CHIslave) {
                /* Setup DBRI for CHI Slave - receive clock, frame sync (FS)
@@ -1202,8 +1235,9 @@ static void reset_chi(struct snd_dbri * dbri, enum master_or_slave master_or_sla
 
        *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
        *(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE | D_CDM_XEN | D_CDM_REN);
+       *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
 
-       dbri_cmdsend(dbri, cmd);
+       dbri_cmdsend(dbri, cmd, 4);
 }
 
 /*
@@ -1240,6 +1274,8 @@ static void cs4215_setup_pipes(struct snd_dbri * dbri)
        setup_pipe(dbri, 17, D_SDP_FIXED | D_SDP_TO_SER | D_SDP_MSB);
        setup_pipe(dbri, 18, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
        setup_pipe(dbri, 19, D_SDP_FIXED | D_SDP_FROM_SER | D_SDP_MSB);
+
+       dbri_cmdwait(dbri);
 }
 
 static int cs4215_init_data(struct cs4215 *mm)
@@ -1271,7 +1307,7 @@ static int cs4215_init_data(struct cs4215 *mm)
        mm->status = 0;
        mm->version = 0xff;
        mm->precision = 8;      /* For ULAW */
-       mm->channels = 2;
+       mm->channels = 1;
 
        return 0;
 }
@@ -1554,7 +1590,6 @@ static int cs4215_init(struct snd_dbri * dbri)
        }
 
        cs4215_setup_pipes(dbri);
-
        cs4215_init_data(&dbri->mm);
 
        /* Enable capture of the status & version timeslots. */
@@ -1583,9 +1618,7 @@ buffer and calls dbri_process_one_interrupt() for each interrupt word.
 Complicated interrupts are handled by dedicated functions (which
 appear first in this file).  Any pending interrupts can be serviced by
 calling dbri_process_interrupt_buffer(), which works even if the CPU's
-interrupts are disabled.  This function is used by dbri_cmdlock()
-to make sure we're synced up with the chip before each command sequence,
-even if we're running cli'ed.
+interrupts are disabled.
 
 */
 
@@ -1594,11 +1627,10 @@ even if we're running cli'ed.
  * Transmit the current TD's for recording/playing, if needed.
  * For playback, ALSA has filled the DMA memory with new data (we hope).
  */
-static void xmit_descs(unsigned long data)
+static void xmit_descs(struct snd_dbri *dbri)
 {
-       struct snd_dbri *dbri = (struct snd_dbri *) data;
        struct dbri_streaminfo *info;
-       volatile s32 *cmd;
+       s32 *cmd;
        unsigned long flags;
        int first_td;
 
@@ -1609,7 +1641,7 @@ static void xmit_descs(unsigned long data)
        info = &dbri->stream_info[DBRI_REC];
        spin_lock_irqsave(&dbri->lock, flags);
 
-       if ((info->left >= info->size) && (info->pipe >= 0)) {
+       if (info->pipe >= 0) {
                first_td = dbri->pipes[info->pipe].first_desc;
 
                dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td);
@@ -1619,16 +1651,15 @@ static void xmit_descs(unsigned long data)
                        goto play;
                }
 
-               cmd = dbri_cmdlock(dbri, NoGetLock);
+               cmd = dbri_cmdlock(dbri, 2);
                *(cmd++) = DBRI_CMD(D_SDP, 0,
                                    dbri->pipes[info->pipe].sdp
                                    | D_SDP_P | D_SDP_EVERY | D_SDP_C);
                *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td);
-               dbri_cmdsend(dbri, cmd);
+               dbri_cmdsend(dbri, cmd, 2);
 
                /* Reset our admin of the pipe & bytes read. */
                dbri->pipes[info->pipe].desc = first_td;
-               info->left = 0;
        }
 
 play:
@@ -1638,33 +1669,27 @@ play:
        info = &dbri->stream_info[DBRI_PLAY];
        spin_lock_irqsave(&dbri->lock, flags);
 
-       if ((info->left <= 0) && (info->pipe >= 0)) {
+       if (info->pipe >= 0) {
                first_td = dbri->pipes[info->pipe].first_desc;
 
                dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td);
 
                /* Stream could be closed by the time we run. */
-               if (first_td < 0) {
-                       spin_unlock_irqrestore(&dbri->lock, flags);
-                       return;
-               }
-
-               cmd = dbri_cmdlock(dbri, NoGetLock);
-               *(cmd++) = DBRI_CMD(D_SDP, 0,
-                                   dbri->pipes[info->pipe].sdp
-                                   | D_SDP_P | D_SDP_EVERY | D_SDP_C);
-               *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td);
-               dbri_cmdsend(dbri, cmd);
+               if (first_td >= 0) {
+                       cmd = dbri_cmdlock(dbri, 2);
+                       *(cmd++) = DBRI_CMD(D_SDP, 0,
+                                           dbri->pipes[info->pipe].sdp
+                                           | D_SDP_P | D_SDP_EVERY | D_SDP_C);
+                       *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td);
+                       dbri_cmdsend(dbri, cmd, 2);
 
-               /* Reset our admin of the pipe & bytes written. */
-               dbri->pipes[info->pipe].desc = first_td;
-               info->left = info->size;
+                       /* Reset our admin of the pipe & bytes written. */
+                       dbri->pipes[info->pipe].desc = first_td;
+               }
        }
        spin_unlock_irqrestore(&dbri->lock, flags);
 }
 
-static DECLARE_TASKLET(xmit_descs_task, xmit_descs, 0);
-
 /* transmission_complete_intr()
  *
  * Called by main interrupt handler when DBRI signals transmission complete
@@ -1684,7 +1709,6 @@ static void transmission_complete_intr(struct snd_dbri * dbri, int pipe)
        struct dbri_streaminfo *info;
        int td;
        int status;
-       int len;
 
        info = &dbri->stream_info[DBRI_PLAY];
 
@@ -1703,20 +1727,7 @@ static void transmission_complete_intr(struct snd_dbri * dbri, int pipe)
                dprintk(D_INT, "TD %d, status 0x%02x\n", td, status);
 
                dbri->dma->desc[td].word4 = 0;  /* Reset it for next time. */
-               len = DBRI_RD_CNT(dbri->dma->desc[td].word1);
-               info->offset += len;
-               info->left -= len;
-
-               /* On the last TD, transmit them all again. */
-               if (dbri->next_desc[td] == -1) {
-                       if (info->left > 0) {
-                               printk(KERN_WARNING
-                                      "%d bytes left after last transfer.\n",
-                                      info->left);
-                               info->left = 0;
-                       }
-                       tasklet_schedule(&xmit_descs_task);
-               }
+               info->offset += DBRI_RD_CNT(dbri->dma->desc[td].word1);
 
                td = dbri->next_desc[td];
                dbri->pipes[pipe].desc = td;
@@ -1749,7 +1760,6 @@ static void reception_complete_intr(struct snd_dbri * dbri, int pipe)
 
        info = &dbri->stream_info[DBRI_REC];
        info->offset += DBRI_RD_CNT(status);
-       info->left += DBRI_RD_CNT(status);
 
        /* FIXME: Check status */
 
@@ -1757,6 +1767,7 @@ static void reception_complete_intr(struct snd_dbri * dbri, int pipe)
                rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status));
 
        /* On the last TD, transmit them all again. */
+#if 0
        if (dbri->next_desc[rd] == -1) {
                if (info->left > info->size) {
                        printk(KERN_WARNING
@@ -1765,6 +1776,7 @@ static void reception_complete_intr(struct snd_dbri * dbri, int pipe)
                }
                tasklet_schedule(&xmit_descs_task);
        }
+#endif
 
        /* Notify ALSA */
        if (spin_is_locked(&dbri->lock)) {
@@ -1793,16 +1805,11 @@ static void dbri_process_one_interrupt(struct snd_dbri * dbri, int x)
                        channel, code, rval);
        }
 
-       if (channel == D_INTR_CMD && command == D_WAIT) {
-               dbri->wait_ackd = val;
-               if (dbri->wait_send != val) {
-                       printk(KERN_ERR "Processing wait command %d when %d was send.\n",
-                              val, dbri->wait_send);
-               }
-               return;
-       }
-
        switch (code) {
+       case D_INTR_CMDI:
+               if (command != D_WAIT)
+                       printk(KERN_ERR "DBRI: Command read interrupt\n");
+               break;
        case D_INTR_BRDY:
                reception_complete_intr(dbri, channel);
                break;
@@ -1815,8 +1822,10 @@ static void dbri_process_one_interrupt(struct snd_dbri * dbri, int x)
                 * resend SDP command with clear pipe bit (C) set
                 */
                {
-                       volatile s32 *cmd;
-
+       /* FIXME: do something useful in case of underrun */
+                       printk(KERN_ERR "DBRI: Underrun error\n");
+#if 0
+                       s32 *cmd;
                        int pipe = channel;
                        int td = dbri->pipes[pipe].desc;
 
@@ -1827,6 +1836,7 @@ static void dbri_process_one_interrupt(struct snd_dbri * dbri, int x)
                                            | D_SDP_P | D_SDP_C | D_SDP_2SAME);
                        *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td);
                        dbri_cmdsend(dbri, cmd);
+#endif
                }
                break;
        case D_INTR_FXDT:
@@ -1847,9 +1857,7 @@ static void dbri_process_one_interrupt(struct snd_dbri * dbri, int x)
 /* dbri_process_interrupt_buffer advances through the DBRI's interrupt
  * buffer until it finds a zero word (indicating nothing more to do
  * right now).  Non-zero words require processing and are handed off
- * to dbri_process_one_interrupt AFTER advancing the pointer.  This
- * order is important since we might recurse back into this function
- * and need to make sure the pointer has been advanced first.
+ * to dbri_process_one_interrupt AFTER advancing the pointer.
  */
 static void dbri_process_interrupt_buffer(struct snd_dbri * dbri)
 {
@@ -1919,8 +1927,6 @@ static irqreturn_t snd_dbri_interrupt(int irq, void *dev_id,
 
        dbri_process_interrupt_buffer(dbri);
 
-       /* FIXME: Write 0 into regs to ACK interrupt */
-
        spin_unlock(&dbri->lock);
 
        return IRQ_HANDLED;
@@ -1962,7 +1968,6 @@ static int snd_dbri_open(struct snd_pcm_substream *substream)
 
        spin_lock_irqsave(&dbri->lock, flags);
        info->substream = substream;
-       info->left = 0;
        info->offset = 0;
        info->dvma_buffer = 0;
        info->pipe = -1;
@@ -1980,7 +1985,6 @@ static int snd_dbri_close(struct snd_pcm_substream *substream)
 
        dprintk(D_USR, "close audio output.\n");
        info->substream = NULL;
-       info->left = 0;
        info->offset = 0;
 
        return 0;
@@ -2062,10 +2066,8 @@ static int snd_dbri_prepare(struct snd_pcm_substream *substream)
        info->size = snd_pcm_lib_buffer_bytes(substream);
        if (DBRI_STREAMNO(substream) == DBRI_PLAY)
                info->pipe = 4; /* Send pipe */
-       else {
+       else
                info->pipe = 6; /* Receive pipe */
-               info->left = info->size;        /* To trigger submittal */
-       }
 
        spin_lock_irq(&dbri->lock);
 
@@ -2093,14 +2095,11 @@ static int snd_dbri_trigger(struct snd_pcm_substream *substream, int cmd)
        case SNDRV_PCM_TRIGGER_START:
                dprintk(D_USR, "start audio, period is %d bytes\n",
                        (int)snd_pcm_lib_period_bytes(substream));
-               /* Enable & schedule the tasklet that re-submits the TDs. */
-               xmit_descs_task.data = (unsigned long)dbri;
-               tasklet_schedule(&xmit_descs_task);
+               /* Re-submit the TDs. */
+               xmit_descs(dbri);
                break;
        case SNDRV_PCM_TRIGGER_STOP:
                dprintk(D_USR, "stop audio.\n");
-               /* Make the tasklet bail out immediately. */
-               xmit_descs_task.data = 0;
                reset_pipe(dbri, info->pipe);
                break;
        default:
@@ -2118,8 +2117,8 @@ static snd_pcm_uframes_t snd_dbri_pointer(struct snd_pcm_substream *substream)
 
        ret = bytes_to_frames(substream->runtime, info->offset)
                % substream->runtime->buffer_size;
-       dprintk(D_USR, "I/O pointer: %ld frames, %d bytes left.\n",
-               ret, info->left);
+       dprintk(D_USR, "I/O pointer: %ld frames of %ld.\n",
+               ret, substream->runtime->buffer_size);
        return ret;
 }