Commit 19e2e3c3 authored by Hermann Lauer's avatar Hermann Lauer Committed by Mercurial server

[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: default avatarHermann Lauer <Hermann.Lauer@iwr.uni-heidelberg.de>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
Signed-off-by: default avatarJaroslav Kysela <perex@perex.cz>
parent 4979bca9
...@@ -226,6 +226,7 @@ struct es1938 { ...@@ -226,6 +226,7 @@ struct es1938 {
unsigned int dma2_start; unsigned int dma2_start;
unsigned int dma1_shift; unsigned int dma1_shift;
unsigned int dma2_shift; unsigned int dma2_shift;
unsigned int last_capture_dmaaddr;
unsigned int active; unsigned int active;
spinlock_t reg_lock; spinlock_t reg_lock;
...@@ -528,6 +529,7 @@ static void snd_es1938_capture_setdma(struct es1938 *chip) ...@@ -528,6 +529,7 @@ static void snd_es1938_capture_setdma(struct es1938 *chip)
outb(1, SLDM_REG(chip, DMAMASK)); outb(1, SLDM_REG(chip, DMAMASK));
outb(0x14, SLDM_REG(chip, DMAMODE)); outb(0x14, SLDM_REG(chip, DMAMODE));
outl(chip->dma1_start, SLDM_REG(chip, DMAADDR)); outl(chip->dma1_start, SLDM_REG(chip, DMAADDR));
chip->last_capture_dmaaddr = chip->dma1_start;
outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT)); outw(chip->dma1_size - 1, SLDM_REG(chip, DMACOUNT));
/* 3. Unmask DMA */ /* 3. Unmask DMA */
outb(0, SLDM_REG(chip, DMAMASK)); outb(0, SLDM_REG(chip, DMAMASK));
...@@ -769,19 +771,40 @@ static int snd_es1938_playback_prepare(struct snd_pcm_substream *substream) ...@@ -769,19 +771,40 @@ static int snd_es1938_playback_prepare(struct snd_pcm_substream *substream)
return -EINVAL; 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) static snd_pcm_uframes_t snd_es1938_capture_pointer(struct snd_pcm_substream *substream)
{ {
struct es1938 *chip = snd_pcm_substream_chip(substream); struct es1938 *chip = snd_pcm_substream_chip(substream);
size_t ptr; size_t ptr;
#if 0
size_t old, new; size_t old, new;
#if 1
/* This stuff is *needed*, don't ask why - AB */ /* This stuff is *needed*, don't ask why - AB */
old = inw(SLDM_REG(chip, DMACOUNT)); old = inw(SLDM_REG(chip, DMACOUNT));
while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old) while ((new = inw(SLDM_REG(chip, DMACOUNT))) != old)
old = new; old = new;
ptr = chip->dma1_size - 1 - new; ptr = chip->dma1_size - 1 - new;
#else #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 #endif
return ptr >> chip->dma1_shift; return ptr >> chip->dma1_shift;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment