Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

[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
Loading
Loading
Loading
Loading
+25 −2
Original line number Diff line number Diff line
@@ -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;
}