ALSA: hda - Fix VIA recording problem
authorJoseph Chan <josephchan@via.com.tw>
Tue, 26 Aug 2008 12:38:03 +0000 (14:38 +0200)
committerJaroslav Kysela <perex@perex.cz>
Fri, 29 Aug 2008 08:06:12 +0000 (10:06 +0200)
Add a workaround for bad DMA-position reporting on VIA chipset.

Signed-off-by: Joseph Chan <josephchan@via.com.tw>
[modified and cleaned up by tiwai]
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
sound/pci/hda/hda_intel.c

index 4f80248..52a3adf 100644 (file)
@@ -286,6 +286,11 @@ enum {
 #define INTEL_SCH_HDA_DEVC      0x78
 #define INTEL_SCH_HDA_DEVC_NOSNOOP       (0x1<<11)
 
+/* Define IN stream 0 FIFO size offset in VIA controller */
+#define VIA_IN_STREAM0_FIFO_SIZE_OFFSET        0x90
+/* Define VIA HD Audio Device ID*/
+#define VIA_HDAC_DEVICE_ID             0x3288
+
 
 /*
  */
@@ -317,6 +322,12 @@ struct azx_dev {
        unsigned int running :1;
        unsigned int irq_pending :1;
        unsigned int irq_ignore :1;
+       /*
+        * For VIA:
+        *  A flag to ensure DMA position is 0
+        *  when link position is not greater than FIFO size
+        */
+       unsigned int insufficient :1;
 };
 
 /* CORB/RIRB */
@@ -379,6 +390,7 @@ struct azx {
        unsigned int polling_mode :1;
        unsigned int msi :1;
        unsigned int irq_pending_warned :1;
+       unsigned int via_dmapos_patch :1; /* enable DMA-position fix for VIA */
 
        /* for debugging */
        unsigned int last_cmd;  /* last issued command (to sync) */
@@ -818,6 +830,11 @@ static void azx_int_clear(struct azx *chip)
 /* start a stream */
 static void azx_stream_start(struct azx *chip, struct azx_dev *azx_dev)
 {
+       /*
+        * Before stream start, initialize parameter
+        */
+       azx_dev->insufficient = 1;
+
        /* enable SIE */
        azx_writeb(chip, INTCTL,
                   azx_readb(chip, INTCTL) | (1 << azx_dev->index));
@@ -1148,7 +1165,8 @@ static int azx_setup_controller(struct azx *chip, struct azx_dev *azx_dev)
 
        /* enable the position buffer */
        if (chip->position_fix == POS_FIX_POSBUF ||
-           chip->position_fix == POS_FIX_AUTO) {
+           chip->position_fix == POS_FIX_AUTO ||
+           chip->via_dmapos_patch) {
                if (!(azx_readl(chip, DPLBASE) & ICH6_DPLBASE_ENABLE))
                        azx_writel(chip, DPLBASE,
                                (u32)chip->posbuf.addr | ICH6_DPLBASE_ENABLE);
@@ -1504,13 +1522,71 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
        return 0;
 }
 
+/* get the current DMA position with correction on VIA chips */
+static unsigned int azx_via_get_position(struct azx *chip,
+                                        struct azx_dev *azx_dev)
+{
+       unsigned int link_pos, mini_pos, bound_pos;
+       unsigned int mod_link_pos, mod_dma_pos, mod_mini_pos;
+       unsigned int fifo_size;
+
+       link_pos = azx_sd_readl(azx_dev, SD_LPIB);
+       if (azx_dev->index >= 4) {
+               /* Playback, no problem using link position */
+               return link_pos;
+       }
+
+       /* Capture */
+       /* For new chipset,
+        * use mod to get the DMA position just like old chipset
+        */
+       mod_dma_pos = le32_to_cpu(*azx_dev->posbuf);
+       mod_dma_pos %= azx_dev->period_bytes;
+
+       /* azx_dev->fifo_size can't get FIFO size of in stream.
+        * Get from base address + offset.
+        */
+       fifo_size = readw(chip->remap_addr + VIA_IN_STREAM0_FIFO_SIZE_OFFSET);
+
+       if (azx_dev->insufficient) {
+               /* Link position never gather than FIFO size */
+               if (link_pos <= fifo_size)
+                       return 0;
+
+               azx_dev->insufficient = 0;
+       }
+
+       if (link_pos <= fifo_size)
+               mini_pos = azx_dev->bufsize + link_pos - fifo_size;
+       else
+               mini_pos = link_pos - fifo_size;
+
+       /* Find nearest previous boudary */
+       mod_mini_pos = mini_pos % azx_dev->period_bytes;
+       mod_link_pos = link_pos % azx_dev->period_bytes;
+       if (mod_link_pos >= fifo_size)
+               bound_pos = link_pos - mod_link_pos;
+       else if (mod_dma_pos >= mod_mini_pos)
+               bound_pos = mini_pos - mod_mini_pos;
+       else {
+               bound_pos = mini_pos - mod_mini_pos + azx_dev->period_bytes;
+               if (bound_pos >= azx_dev->bufsize)
+                       bound_pos = 0;
+       }
+
+       /* Calculate real DMA position we want */
+       return bound_pos + mod_dma_pos;
+}
+
 static unsigned int azx_get_position(struct azx *chip,
                                     struct azx_dev *azx_dev)
 {
        unsigned int pos;
 
-       if (chip->position_fix == POS_FIX_POSBUF ||
-           chip->position_fix == POS_FIX_AUTO) {
+       if (chip->via_dmapos_patch)
+               pos = azx_via_get_position(chip, azx_dev);
+       else if (chip->position_fix == POS_FIX_POSBUF ||
+                chip->position_fix == POS_FIX_AUTO) {
                /* use the position buffer */
                pos = le32_to_cpu(*azx_dev->posbuf);
        } else {
@@ -1946,6 +2022,15 @@ static int __devinit check_position_fix(struct azx *chip, int fix)
 {
        const struct snd_pci_quirk *q;
 
+       /* Check VIA HD Audio Controller exist */
+       if (chip->pci->vendor == PCI_VENDOR_ID_VIA &&
+           chip->pci->device == VIA_HDAC_DEVICE_ID) {
+               chip->via_dmapos_patch = 1;
+               /* Use link position directly, avoid any transfer problem. */
+               return POS_FIX_LPIB;
+       }
+       chip->via_dmapos_patch = 0;
+
        if (fix == POS_FIX_AUTO) {
                q = snd_pci_quirk_lookup(chip->pci, position_fix_list);
                if (q) {