[ALSA] es1938 - improve capture hw pointer reads

With the Solo1 (es1938) I got a lot of xrun's during capture on my machine.
Tracing that down it seems to be comming from reading ocassionaly bad hw
pointers from the chip.  This patch uses more checking to avoid that false
pointer reads.
Failed reads are giving back the last good value read instead of spinning in
a tight loop, which seems more appropriate to me in an interrupt. I think I
saw this trick used in another driver

Signed-off-by: Hermann Lauer <Hermann.Lauer@iwr.uni-heidelberg.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Hermann Lauer 2008-01-30 08:25:13 +01:00 committed by Mercurial server
parent 4979bca9dc
commit 19e2e3c304

View file

@ -226,6 +226,7 @@ struct es1938 {
unsigned int dma2_start;
unsigned int dma1_shift;
unsigned int dma2_shift;
unsigned int last_capture_dmaaddr;
unsigned int active;
spinlock_t reg_lock;
@ -528,6 +529,7 @@ static void snd_es1938_capture_setdma(struct es1938 *chip)
outb(1, SLDM_REG(chip, DMAMASK));
outb(0x14, SLDM_REG(chip, DMAMODE));
outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
chip->last_capture_dmaaddr = chip->dma1_start;
outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
/* 3. Unmask DMA */
outb(0, SLDM_REG(chip, DMAMASK));
@ -769,19 +771,40 @@ static int snd_es1938_playback_prepare(struct snd_pcm_substream *substream)
return -EINVAL;
}
/* during the incrementing of dma counters the DMA register reads sometimes
returns garbage. To ensure a valid hw pointer, the following checks which
should be very unlikely to fail are used:
- is the current DMA address in the valid DMA range ?
- is the sum of DMA address and DMA counter pointing to the last DMA byte ?
One can argue this could differ by one byte depending on which register is
updated first, so the implementation below allows for that.
*/
static snd_pcm_uframes_t snd_es1938_capture_pointer(struct snd_pcm_substream *substream)
{
struct es1938 *chip = snd_pcm_substream_chip(substream);
size_t ptr;
#if 0
size_t old, new;
#if 1
/* This stuff is *needed*, don't ask why - AB */
old = inw(SLDM_REG(chip, DMACOUNT));
while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old)
old = new;
ptr = chip->dma1_size - 1 - new;
#else
ptr = inl(SLDM_REG(chip, DMAADDR)) - chip->dma1_start;
size_t count;
unsigned int diff;
ptr = inl(SLDM_REG(chip, DMAADDR));
count = inw(SLDM_REG(chip, DMACOUNT));
diff = chip->dma1_start + chip->dma1_size - ptr - count;
if (diff > 3 || ptr < chip->dma1_start
|| ptr >= chip->dma1_start+chip->dma1_size)
ptr = chip->last_capture_dmaaddr; /* bad, use last saved */
else
chip->last_capture_dmaaddr = ptr; /* good, remember it */
ptr -= chip->dma1_start;
#endif
return ptr >> chip->dma1_shift;
}