[ALSA] sparc dbri: ring buffered version

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>
This commit is contained in:
Krzysztof Helt 2006-08-21 19:30:57 +02:00 committed by Jaroslav Kysela
parent 294a30dc8c
commit 1be54c824b
1 changed files with 192 additions and 193 deletions

View File

@ -2,6 +2,8 @@
* Driver for DBRI sound chip found on Sparcs. * Driver for DBRI sound chip found on Sparcs.
* Copyright (C) 2004, 2005 Martin Habets (mhabets@users.sourceforge.net) * 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: * Based entirely upon drivers/sbus/audio/dbri.c which is:
* Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de) * Copyright (C) 1997 Rudolf Koenig (rfkoenig@immd4.informatik.uni-erlangen.de)
* Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org) * Copyright (C) 1998, 1999 Brent Baccala (baccala@freesoft.org)
@ -260,7 +262,7 @@ struct dbri_mem {
* the CPU and the DBRI * the CPU and the DBRI
*/ */
struct dbri_dma { 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 */ volatile s32 intr[DBRI_INT_BLK]; /* Interrupt field */
struct dbri_mem desc[DBRI_NO_DESCS]; /* Xmit/receive descriptors */ struct dbri_mem desc[DBRI_NO_DESCS]; /* Xmit/receive descriptors */
}; };
@ -284,7 +286,6 @@ struct dbri_pipe {
struct dbri_streaminfo { struct dbri_streaminfo {
struct snd_pcm_substream *substream; struct snd_pcm_substream *substream;
u32 dvma_buffer; /* Device view of Alsa DMA buffer */ u32 dvma_buffer; /* Device view of Alsa DMA buffer */
int left; /* # of bytes left in DMA buffer */
int size; /* Size of DMA buffer */ int size; /* Size of DMA buffer */
size_t offset; /* offset in user buffer */ size_t offset; /* offset in user buffer */
int pipe; /* Data pipe used */ int pipe; /* Data pipe used */
@ -305,11 +306,11 @@ struct snd_dbri {
void __iomem *regs; /* dbri HW regs */ void __iomem *regs; /* dbri HW regs */
int dbri_irqp; /* intr queue pointer */ 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 */ struct dbri_pipe pipes[DBRI_NO_PIPES]; /* DBRI's 32 data pipes */
int next_desc[DBRI_NO_DESCS]; /* Index of next desc, or -1 */ 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; int chi_bpf;
@ -544,7 +545,7 @@ struct snd_dbri {
#define DBRI_TD_TBC (1<<0) /* Transmit buffer Complete */ #define DBRI_TD_TBC (1<<0) /* Transmit buffer Complete */
#define DBRI_TD_STATUS(v) ((v)&0xff) /* Transmit status */ #define DBRI_TD_STATUS(v) ((v)&0xff) /* Transmit status */
/* Maximum buffer size per TD: almost 8Kb */ /* Maximum buffer size per TD: almost 8Kb */
#define DBRI_TD_MAXCNT ((1 << 13) - 1) #define DBRI_TD_MAXCNT ((1 << 13) - 4)
/* Receive descriptor defines */ /* Receive descriptor defines */
#define DBRI_RD_F (1<<31) /* End of Frame */ #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. CPU interrupt to signal completion.
Since the DBRI can run in parallel with the CPU, several means of Since the DBRI can run in parallel with the CPU, several means of
synchronization present themselves. The method implemented here is close synchronization present themselves. The method implemented here is only
to the original scheme (Rudolf's), and uses 2 counters (wait_send and to use the dbri_cmdwait() to wait for execution of batch of sent commands.
wait_ackd) to synchronize the command buffer between the CPU and the DBRI.
A more sophisticated scheme might involve a circular command buffer A circular command buffer is used here. A new command is being added
or an array of command buffers. A routine could fill one with while other can be executed. The scheme works by adding two WAIT commands
commands and link it onto a list. When a interrupt signaled after each sent batch of commands. When the next batch is prepared it is
completion of the current command buffer, look on the list for added after the WAIT commands then the WAITs are replaced with single JUMP
the next one. 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 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 first call dbri_cmdlock() and get pointer to a free space in
in return. dbri_cmdlock() will block if the previous commands have not dbri->dma->cmd buffer. After this, the commands can be written to
been completed yet. After this the commands can be written to the buffer, the buffer, and dbri_cmdsend() is called with the final pointer value
and dbri_cmdsend() is called with the final pointer value to send them to send them to the DBRI.
to the DBRI.
*/ */
static void dbri_process_interrupt_buffer(struct snd_dbri * dbri); static void dbri_process_interrupt_buffer(struct snd_dbri * dbri);
enum dbri_lock { NoGetLock, GetLock };
#define MAXLOOPS 10 #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; 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 */ /* 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); msleep_interruptible(1);
}
if (maxloops == 0) { if (maxloops == 0) {
printk(KERN_ERR "DBRI: Chip never completed command buffer %d\n", printk(KERN_ERR "DBRI: Chip never completed command buffer\n");
dbri->wait_send);
} else { } else {
dprintk(D_CMD, "Chip completed command buffer (%d)\n", dprintk(D_CMD, "Chip completed command buffer (%d)\n",
MAXLOOPS - maxloops - 1); 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 0;
return &dbri->dma->cmd[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++) { 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);
/* 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);
#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); 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
if ((cmd - &dbri->dma->cmd[0]) >= DBRI_NO_CMDS - 1) { /* Reread the last command */
printk(KERN_ERR "DBRI: Command buffer overflow! (bug in driver)\n"); tmp = sbus_readl(dbri->regs + REG0);
/* Ignore the last part. */ tmp |= D_P;
cmd = &dbri->dma->cmd[DBRI_NO_CMDS - 3]; sbus_writel(tmp, dbri->regs + REG0);
}
dbri->wait_send++; dbri->cmdptr = cmd;
dbri->wait_send &= 0xffff; /* restrict it to a 16 bit counter. */ spin_unlock(&dbri->cmdlock);
*(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
*(cmd++) = DBRI_CMD(D_WAIT, 1, dbri->wait_send);
/* Set command pointer and signal it is valid. */
sbus_writel(dbri->dma_dvma, dbri->regs + REG8);
/*spin_unlock(&dbri->lock); */
} }
/* Lock must be held when calling this */ /* 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 */ /* Lock must not be held before calling this */
static void dbri_initialize(struct snd_dbri * dbri) static void dbri_initialize(struct snd_dbri * dbri)
{ {
volatile s32 *cmd; s32 *cmd;
u32 dma_addr; u32 dma_addr;
unsigned long flags; unsigned long flags;
int n; int n;
@ -718,14 +750,11 @@ static void dbri_initialize(struct snd_dbri * dbri)
dbri_reset(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 */ /* Initialize pipes */
for (n = 0; n < DBRI_NO_PIPES; n++) for (n = 0; n < DBRI_NO_PIPES; n++)
dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1; dbri->pipes[n].desc = dbri->pipes[n].first_desc = -1;
spin_lock_init(&dbri->cmdlock);
/* /*
* Initialize the interrupt ringbuffer. * Initialize the interrupt ringbuffer.
*/ */
@ -735,10 +764,19 @@ static void dbri_initialize(struct snd_dbri * dbri)
/* /*
* Set up the interrupt queue * Set up the interrupt queue
*/ */
spin_lock(&dbri->cmdlock);
cmd = dbri->cmdptr = dbri->dma->cmd;
*(cmd++) = DBRI_CMD(D_IIQ, 0, 0); *(cmd++) = DBRI_CMD(D_IIQ, 0, 0);
*(cmd++) = dma_addr; *(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); spin_unlock_irqrestore(&dbri->lock, flags);
} }
@ -770,7 +808,7 @@ static void reset_pipe(struct snd_dbri * dbri, int pipe)
{ {
int sdp; int sdp;
int desc; int desc;
volatile int *cmd; s32 *cmd;
if (pipe < 0 || pipe > DBRI_MAX_PIPE) { if (pipe < 0 || pipe > DBRI_MAX_PIPE) {
printk(KERN_ERR "DBRI: reset_pipe called with illegal pipe number\n"); 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; 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++) = DBRI_CMD(D_SDP, 0, sdp | D_SDP_C | D_SDP_P);
*(cmd++) = 0; *(cmd++) = 0;
dbri_cmdsend(dbri, cmd); *(cmd++) = DBRI_CMD(D_PAUSE, 0, 0);
dbri_cmdsend(dbri, cmd, 3);
desc = dbri->pipes[pipe].first_desc; desc = dbri->pipes[pipe].first_desc;
while (desc != -1) { if ( desc >= 0)
dbri->dma->desc[desc].nda = dbri->dma->desc[desc].ba = 0; do {
desc = dbri->next_desc[desc]; 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].desc = -1;
dbri->pipes[pipe].first_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 prevpipe, int nextpipe,
int length, int cycle) int length, int cycle)
{ {
volatile s32 *cmd; s32 *cmd;
int val; int val;
if (pipe < 0 || pipe > DBRI_MAX_PIPE 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[prevpipe].nextpipe = pipe;
dbri->pipes[pipe].nextpipe = nextpipe; dbri->pipes[pipe].nextpipe = nextpipe;
dbri->pipes[pipe].length = length; dbri->pipes[pipe].length = length;
cmd = dbri_cmdlock(dbri, NoGetLock); cmd = dbri_cmdlock(dbri, 4);
if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) { if (dbri->pipes[pipe].sdp & D_SDP_TO_SER) {
/* Deal with CHI special case: /* 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); D_TS_LEN(length) | D_TS_CYCLE(cycle) | D_TS_NEXT(nextpipe);
*(cmd++) = 0; *(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, static void unlink_time_slot(struct snd_dbri * dbri, int pipe,
enum in_or_out direction, int prevpipe, enum in_or_out direction, int prevpipe,
int nextpipe) int nextpipe)
{ {
volatile s32 *cmd; s32 *cmd;
int val; int val;
if (pipe < 0 || pipe > DBRI_MAX_PIPE 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 printk(KERN_ERR
"DBRI: unlink_time_slot called with illegal pipe number\n"); "DBRI: unlink_time_slot called with illegal pipe number\n");
return; return;
} }
cmd = dbri_cmdlock(dbri, NoGetLock); cmd = dbri_cmdlock(dbri, 4);
if (direction == PIPEinput) { if (direction == PIPEinput) {
val = D_DTS_VI | D_DTS_DEL | D_DTS_PRVIN(prevpipe) | pipe; 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++) = 0;
*(cmd++) = D_TS_NEXT(nextpipe); *(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() /* 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) 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) { if (pipe < 16 || pipe > DBRI_MAX_PIPE) {
printk(KERN_ERR "DBRI: xmit_fixed: Illegal pipe number\n"); 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) if (dbri->pipes[pipe].sdp & D_SDP_MSB)
data = reverse_bytes(data, dbri->pipes[pipe].length); 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++) = DBRI_CMD(D_SSP, 0, pipe);
*(cmd++) = data; *(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) 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 * and work by building chains of descriptors which identify the
* data buffers. Buffers too large for a single descriptor will * data buffers. Buffers too large for a single descriptor will
* be spread across multiple descriptors. * be spread across multiple descriptors.
*
* All descriptors create a ring buffer.
*/ */
static int setup_descs(struct snd_dbri * dbri, int streamno, unsigned int period) 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; return -1;
} }
if (len > DBRI_TD_MAXCNT) { if (len > DBRI_TD_MAXCNT)
mylen = DBRI_TD_MAXCNT; /* 8KB - 1 */ mylen = DBRI_TD_MAXCNT; /* 8KB - 4 */
} else { else
mylen = len; mylen = len;
}
if (mylen > period) { if (mylen > period)
mylen = period; mylen = period;
}
dbri->next_desc[desc] = -1; dbri->next_desc[desc] = -1;
dbri->dma->desc[desc].ba = dvma_buffer; 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) { if (streamno == DBRI_PLAY) {
dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen); dbri->dma->desc[desc].word1 = DBRI_TD_CNT(mylen);
dbri->dma->desc[desc].word4 = 0; dbri->dma->desc[desc].word4 = 0;
if (first_desc != -1) dbri->dma->desc[desc].word1 |=
dbri->dma->desc[desc].word1 |= DBRI_TD_M; DBRI_TD_F | DBRI_TD_B;
} else { } else {
dbri->dma->desc[desc].word1 = 0; dbri->dma->desc[desc].word1 = 0;
dbri->dma->desc[desc].word4 = dbri->dma->desc[desc].word4 =
DBRI_RD_B | DBRI_RD_BCNT(mylen); DBRI_RD_B | DBRI_RD_BCNT(mylen);
} }
if (first_desc == -1) { if (first_desc == -1)
first_desc = desc; first_desc = desc;
} else { else {
dbri->next_desc[last_desc] = desc; dbri->next_desc[last_desc] = desc;
dbri->dma->desc[last_desc].nda = dbri->dma->desc[last_desc].nda =
dbri->dma_dvma + dbri_dma_off(desc, desc); 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; return -1;
} }
dbri->dma->desc[last_desc].word1 &= ~DBRI_TD_M;
if (streamno == DBRI_PLAY) { if (streamno == DBRI_PLAY) {
dbri->dma->desc[last_desc].word1 |= 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].first_desc = first_desc;
dbri->pipes[info->pipe].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", dprintk(D_DESC, "DESC %d: %08x %08x %08x %08x\n",
desc, desc,
dbri->dma->desc[desc].word1, dbri->dma->desc[desc].word1,
dbri->dma->desc[desc].ba, dbri->dma->desc[desc].ba,
dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4); dbri->dma->desc[desc].nda, dbri->dma->desc[desc].word4);
desc = dbri->next_desc[desc];
if ( desc == first_desc )
break;
} }
#endif
return 0; 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, static void reset_chi(struct snd_dbri * dbri, enum master_or_slave master_or_slave,
int bits_per_frame) int bits_per_frame)
{ {
volatile s32 *cmd; s32 *cmd;
int val; int val;
static int chi_initialized = 0; /* FIXME: mutex? */
if (!chi_initialized) { /* Set CHI Anchor: Pipe 16 */
cmd = dbri_cmdlock(dbri, GetLock); 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);
/* Set CHI Anchor: Pipe 16 */ dbri->pipes[16].sdp = 1;
dbri->pipes[16].nextpipe = 16;
val = D_DTS_VO | D_DTS_VI | D_DTS_INS cmd = dbri_cmdlock(dbri, 4);
| 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);
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);
}
if (master_or_slave == CHIslave) { if (master_or_slave == CHIslave) {
/* Setup DBRI for CHI Slave - receive clock, frame sync (FS) /* 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_PAUSE, 0, 0);
*(cmd++) = DBRI_CMD(D_CDM, 0, D_CDM_XCE | D_CDM_XEN | D_CDM_REN); *(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, 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, 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); 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) static int cs4215_init_data(struct cs4215 *mm)
@ -1271,7 +1307,7 @@ static int cs4215_init_data(struct cs4215 *mm)
mm->status = 0; mm->status = 0;
mm->version = 0xff; mm->version = 0xff;
mm->precision = 8; /* For ULAW */ mm->precision = 8; /* For ULAW */
mm->channels = 2; mm->channels = 1;
return 0; return 0;
} }
@ -1554,7 +1590,6 @@ static int cs4215_init(struct snd_dbri * dbri)
} }
cs4215_setup_pipes(dbri); cs4215_setup_pipes(dbri);
cs4215_init_data(&dbri->mm); cs4215_init_data(&dbri->mm);
/* Enable capture of the status & version timeslots. */ /* 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 Complicated interrupts are handled by dedicated functions (which
appear first in this file). Any pending interrupts can be serviced by appear first in this file). Any pending interrupts can be serviced by
calling dbri_process_interrupt_buffer(), which works even if the CPU's calling dbri_process_interrupt_buffer(), which works even if the CPU's
interrupts are disabled. This function is used by dbri_cmdlock() interrupts are disabled.
to make sure we're synced up with the chip before each command sequence,
even if we're running cli'ed.
*/ */
@ -1594,11 +1627,10 @@ even if we're running cli'ed.
* Transmit the current TD's for recording/playing, if needed. * Transmit the current TD's for recording/playing, if needed.
* For playback, ALSA has filled the DMA memory with new data (we hope). * 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; struct dbri_streaminfo *info;
volatile s32 *cmd; s32 *cmd;
unsigned long flags; unsigned long flags;
int first_td; int first_td;
@ -1609,7 +1641,7 @@ static void xmit_descs(unsigned long data)
info = &dbri->stream_info[DBRI_REC]; info = &dbri->stream_info[DBRI_REC];
spin_lock_irqsave(&dbri->lock, flags); 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; first_td = dbri->pipes[info->pipe].first_desc;
dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td); dprintk(D_DESC, "xmit_descs rec @ TD %d\n", first_td);
@ -1619,16 +1651,15 @@ static void xmit_descs(unsigned long data)
goto play; goto play;
} }
cmd = dbri_cmdlock(dbri, NoGetLock); cmd = dbri_cmdlock(dbri, 2);
*(cmd++) = DBRI_CMD(D_SDP, 0, *(cmd++) = DBRI_CMD(D_SDP, 0,
dbri->pipes[info->pipe].sdp dbri->pipes[info->pipe].sdp
| D_SDP_P | D_SDP_EVERY | D_SDP_C); | D_SDP_P | D_SDP_EVERY | D_SDP_C);
*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, first_td); *(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. */ /* Reset our admin of the pipe & bytes read. */
dbri->pipes[info->pipe].desc = first_td; dbri->pipes[info->pipe].desc = first_td;
info->left = 0;
} }
play: play:
@ -1638,33 +1669,27 @@ play:
info = &dbri->stream_info[DBRI_PLAY]; info = &dbri->stream_info[DBRI_PLAY];
spin_lock_irqsave(&dbri->lock, flags); 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; first_td = dbri->pipes[info->pipe].first_desc;
dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td); dprintk(D_DESC, "xmit_descs play @ TD %d\n", first_td);
/* Stream could be closed by the time we run. */ /* Stream could be closed by the time we run. */
if (first_td < 0) { if (first_td >= 0) {
spin_unlock_irqrestore(&dbri->lock, flags); cmd = dbri_cmdlock(dbri, 2);
return; *(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;
} }
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);
/* Reset our admin of the pipe & bytes written. */
dbri->pipes[info->pipe].desc = first_td;
info->left = info->size;
} }
spin_unlock_irqrestore(&dbri->lock, flags); spin_unlock_irqrestore(&dbri->lock, flags);
} }
static DECLARE_TASKLET(xmit_descs_task, xmit_descs, 0);
/* transmission_complete_intr() /* transmission_complete_intr()
* *
* Called by main interrupt handler when DBRI signals transmission complete * 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; struct dbri_streaminfo *info;
int td; int td;
int status; int status;
int len;
info = &dbri->stream_info[DBRI_PLAY]; 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); dprintk(D_INT, "TD %d, status 0x%02x\n", td, status);
dbri->dma->desc[td].word4 = 0; /* Reset it for next time. */ dbri->dma->desc[td].word4 = 0; /* Reset it for next time. */
len = DBRI_RD_CNT(dbri->dma->desc[td].word1); info->offset += 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);
}
td = dbri->next_desc[td]; td = dbri->next_desc[td];
dbri->pipes[pipe].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 = &dbri->stream_info[DBRI_REC];
info->offset += DBRI_RD_CNT(status); info->offset += DBRI_RD_CNT(status);
info->left += DBRI_RD_CNT(status);
/* FIXME: Check 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)); rd, DBRI_RD_STATUS(status), DBRI_RD_CNT(status));
/* On the last TD, transmit them all again. */ /* On the last TD, transmit them all again. */
#if 0
if (dbri->next_desc[rd] == -1) { if (dbri->next_desc[rd] == -1) {
if (info->left > info->size) { if (info->left > info->size) {
printk(KERN_WARNING printk(KERN_WARNING
@ -1765,6 +1776,7 @@ static void reception_complete_intr(struct snd_dbri * dbri, int pipe)
} }
tasklet_schedule(&xmit_descs_task); tasklet_schedule(&xmit_descs_task);
} }
#endif
/* Notify ALSA */ /* Notify ALSA */
if (spin_is_locked(&dbri->lock)) { 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); 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) { switch (code) {
case D_INTR_CMDI:
if (command != D_WAIT)
printk(KERN_ERR "DBRI: Command read interrupt\n");
break;
case D_INTR_BRDY: case D_INTR_BRDY:
reception_complete_intr(dbri, channel); reception_complete_intr(dbri, channel);
break; 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 * 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 pipe = channel;
int td = dbri->pipes[pipe].desc; 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); | D_SDP_P | D_SDP_C | D_SDP_2SAME);
*(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td); *(cmd++) = dbri->dma_dvma + dbri_dma_off(desc, td);
dbri_cmdsend(dbri, cmd); dbri_cmdsend(dbri, cmd);
#endif
} }
break; break;
case D_INTR_FXDT: 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 /* dbri_process_interrupt_buffer advances through the DBRI's interrupt
* buffer until it finds a zero word (indicating nothing more to do * buffer until it finds a zero word (indicating nothing more to do
* right now). Non-zero words require processing and are handed off * right now). Non-zero words require processing and are handed off
* to dbri_process_one_interrupt AFTER advancing the pointer. This * to dbri_process_one_interrupt AFTER advancing the pointer.
* order is important since we might recurse back into this function
* and need to make sure the pointer has been advanced first.
*/ */
static void dbri_process_interrupt_buffer(struct snd_dbri * dbri) 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); dbri_process_interrupt_buffer(dbri);
/* FIXME: Write 0 into regs to ACK interrupt */
spin_unlock(&dbri->lock); spin_unlock(&dbri->lock);
return IRQ_HANDLED; return IRQ_HANDLED;
@ -1962,7 +1968,6 @@ static int snd_dbri_open(struct snd_pcm_substream *substream)
spin_lock_irqsave(&dbri->lock, flags); spin_lock_irqsave(&dbri->lock, flags);
info->substream = substream; info->substream = substream;
info->left = 0;
info->offset = 0; info->offset = 0;
info->dvma_buffer = 0; info->dvma_buffer = 0;
info->pipe = -1; info->pipe = -1;
@ -1980,7 +1985,6 @@ static int snd_dbri_close(struct snd_pcm_substream *substream)
dprintk(D_USR, "close audio output.\n"); dprintk(D_USR, "close audio output.\n");
info->substream = NULL; info->substream = NULL;
info->left = 0;
info->offset = 0; info->offset = 0;
return 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); info->size = snd_pcm_lib_buffer_bytes(substream);
if (DBRI_STREAMNO(substream) == DBRI_PLAY) if (DBRI_STREAMNO(substream) == DBRI_PLAY)
info->pipe = 4; /* Send pipe */ info->pipe = 4; /* Send pipe */
else { else
info->pipe = 6; /* Receive pipe */ info->pipe = 6; /* Receive pipe */
info->left = info->size; /* To trigger submittal */
}
spin_lock_irq(&dbri->lock); 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: case SNDRV_PCM_TRIGGER_START:
dprintk(D_USR, "start audio, period is %d bytes\n", dprintk(D_USR, "start audio, period is %d bytes\n",
(int)snd_pcm_lib_period_bytes(substream)); (int)snd_pcm_lib_period_bytes(substream));
/* Enable & schedule the tasklet that re-submits the TDs. */ /* Re-submit the TDs. */
xmit_descs_task.data = (unsigned long)dbri; xmit_descs(dbri);
tasklet_schedule(&xmit_descs_task);
break; break;
case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_STOP:
dprintk(D_USR, "stop audio.\n"); dprintk(D_USR, "stop audio.\n");
/* Make the tasklet bail out immediately. */
xmit_descs_task.data = 0;
reset_pipe(dbri, info->pipe); reset_pipe(dbri, info->pipe);
break; break;
default: 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) ret = bytes_to_frames(substream->runtime, info->offset)
% substream->runtime->buffer_size; % substream->runtime->buffer_size;
dprintk(D_USR, "I/O pointer: %ld frames, %d bytes left.\n", dprintk(D_USR, "I/O pointer: %ld frames of %ld.\n",
ret, info->left); ret, substream->runtime->buffer_size);
return ret; return ret;
} }