summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoseph Chan <josephchan@via.com.tw>2008-08-26 14:38:03 +0200
committerJaroslav Kysela <perex@perex.cz>2008-08-29 10:06:12 +0200
commit0e153474f47aee906107a5c1da0ae83553e5ba6a (patch)
treec9aaf92d3b847a5212549cdb01dcfdbbd7317e7a
parentacdfc9e305b8320c5dee714a08be022441b58c44 (diff)
downloadop-kernel-dev-0e153474f47aee906107a5c1da0ae83553e5ba6a.zip
op-kernel-dev-0e153474f47aee906107a5c1da0ae83553e5ba6a.tar.gz
ALSA: hda - Fix VIA recording problem
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>
-rw-r--r--sound/pci/hda/hda_intel.c91
1 files changed, 88 insertions, 3 deletions
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index 4f80248..52a3adf 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -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) {
OpenPOWER on IntegriCloud