--- linux-2.6.23.12/sound/pci/es1938.c.trydma 2008-01-10 19:50:14.000000000 +0000 +++ linux-2.6.23.12/sound/pci/es1938.c 2008-01-29 07:33:53.000000000 +0000 @@ -227,6 +227,8 @@ unsigned int dma2_start; unsigned int dma1_shift; unsigned int dma2_shift; + void *last_capture_dmaaddr; + unsigned int active; spinlock_t reg_lock; @@ -770,19 +772,42 @@ 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 implemention 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 stat, count; + unsigned int diff; + struct snd_pcm_runtime *runtime; + + runtime = substream->runtime; + 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; }