--- linux-2.6.23.12/sound/pci/es1938.c.trydma 2008-01-14 23:46:57.000000000 +0100 +++ linux-2.6.23.12/sound/pci/es1938.c 2008-01-23 01:03:08.000000000 +0100 @@ -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; @@ -775,14 +777,38 @@ struct es1938 *chip = snd_pcm_substream_chip(substream); size_t ptr; size_t old, new; -#if 1 +#if 0 /* 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; + /* 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. + */ + 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; + + /* printk("Es1938debug - capture_pointer %x,diff=%x,fb=%x DMA count: %x, base: %x, status: %x\n", + ptr,diff,((unsigned char*)(runtime->dma_area)-1)[0],count, inl(SLDM_REG(chip, DMAADDR)), inl(SLDM_REG(chip, DMASTATUS))); + */ + if (diff>3 || ptr < chip->dma1_start + || ptr >= chip->dma1_start+chip->dma1_size) + ptr=chip->last_capture_dmaaddr; /* bad, use last saved instead */ + else chip->last_capture_dmaaddr=ptr; /* good, remember it */ + + ptr -= chip->dma1_start; #endif return ptr >> chip->dma1_shift; }