Commit e5463720 authored by Jaroslav Kysela's avatar Jaroslav Kysela

[ALSA] snd-hda-intel: use WALLCLK register to check for early irqs

Use 24Mhz WALLCLK register to ignore too early interrupts and
wrong interrupt status. The bad timing confuses the higher ALSA
layer and causes audio skipping. More information about behaviour
and debugging can be found in kernel bz#15912.

https://bugzilla.kernel.org/show_bug.cgi?id=15912Signed-off-by: default avatarJaroslav Kysela <perex@perex.cz>
parent beaffc39
...@@ -174,7 +174,7 @@ MODULE_DESCRIPTION("Intel HDA driver"); ...@@ -174,7 +174,7 @@ MODULE_DESCRIPTION("Intel HDA driver");
#define ICH6_GSTS_FSTS (1 << 1) /* flush status */ #define ICH6_GSTS_FSTS (1 << 1) /* flush status */
#define ICH6_REG_INTCTL 0x20 #define ICH6_REG_INTCTL 0x20
#define ICH6_REG_INTSTS 0x24 #define ICH6_REG_INTSTS 0x24
#define ICH6_REG_WALCLK 0x30 #define ICH6_REG_WALLCLK 0x30 /* 24Mhz source */
#define ICH6_REG_SYNC 0x34 #define ICH6_REG_SYNC 0x34
#define ICH6_REG_CORBLBASE 0x40 #define ICH6_REG_CORBLBASE 0x40
#define ICH6_REG_CORBUBASE 0x44 #define ICH6_REG_CORBUBASE 0x44
...@@ -340,8 +340,8 @@ struct azx_dev { ...@@ -340,8 +340,8 @@ struct azx_dev {
unsigned int period_bytes; /* size of the period in bytes */ unsigned int period_bytes; /* size of the period in bytes */
unsigned int frags; /* number for period in the play buffer */ unsigned int frags; /* number for period in the play buffer */
unsigned int fifo_size; /* FIFO size */ unsigned int fifo_size; /* FIFO size */
unsigned long start_jiffies; /* start + minimum jiffies */ unsigned long start_wallclk; /* start + minimum wallclk */
unsigned long min_jiffies; /* minimum jiffies before position is valid */ unsigned long period_wallclk; /* wallclk for period */
void __iomem *sd_addr; /* stream descriptor pointer */ void __iomem *sd_addr; /* stream descriptor pointer */
...@@ -361,7 +361,6 @@ struct azx_dev { ...@@ -361,7 +361,6 @@ struct azx_dev {
unsigned int opened :1; unsigned int opened :1;
unsigned int running :1; unsigned int running :1;
unsigned int irq_pending :1; unsigned int irq_pending :1;
unsigned int start_flag: 1; /* stream full start flag */
/* /*
* For VIA: * For VIA:
* A flag to ensure DMA position is 0 * A flag to ensure DMA position is 0
...@@ -1676,8 +1675,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream) ...@@ -1676,8 +1675,9 @@ static int azx_pcm_prepare(struct snd_pcm_substream *substream)
return err; return err;
} }
azx_dev->min_jiffies = (runtime->period_size * HZ) / /* wallclk has 24Mhz clock source */
(runtime->rate * 2); azx_dev->period_wallclk = (((runtime->period_size * 24000) /
runtime->rate) * 1000);
azx_setup_controller(chip, azx_dev); azx_setup_controller(chip, azx_dev);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1; azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
...@@ -1731,14 +1731,15 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) ...@@ -1731,14 +1731,15 @@ static int azx_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
if (s->pcm->card != substream->pcm->card) if (s->pcm->card != substream->pcm->card)
continue; continue;
azx_dev = get_azx_dev(s); azx_dev = get_azx_dev(s);
if (rstart) { if (start) {
azx_dev->start_flag = 1; azx_dev->start_wallclk = azx_readl(chip, WALLCLK);
azx_dev->start_jiffies = jiffies + azx_dev->min_jiffies; if (!rstart)
} azx_dev->start_wallclk -=
if (start) azx_dev->period_wallclk;
azx_stream_start(chip, azx_dev); azx_stream_start(chip, azx_dev);
else } else {
azx_stream_stop(chip, azx_dev); azx_stream_stop(chip, azx_dev);
}
azx_dev->running = start; azx_dev->running = start;
} }
spin_unlock(&chip->reg_lock); spin_unlock(&chip->reg_lock);
...@@ -1885,13 +1886,14 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream) ...@@ -1885,13 +1886,14 @@ static snd_pcm_uframes_t azx_pcm_pointer(struct snd_pcm_substream *substream)
*/ */
static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
{ {
u32 wallclk;
unsigned int pos; unsigned int pos;
int stream; int stream;
if (azx_dev->start_flag && wallclk = azx_readl(chip, WALLCLK);
time_before_eq(jiffies, azx_dev->start_jiffies)) if ((wallclk - azx_dev->start_wallclk) <
(azx_dev->period_wallclk * 2) / 3)
return -1; /* bogus (too early) interrupt */ return -1; /* bogus (too early) interrupt */
azx_dev->start_flag = 0;
stream = azx_dev->substream->stream; stream = azx_dev->substream->stream;
pos = azx_get_position(chip, azx_dev); pos = azx_get_position(chip, azx_dev);
...@@ -1906,13 +1908,12 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) ...@@ -1906,13 +1908,12 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
chip->position_fix[stream] = POS_FIX_POSBUF; chip->position_fix[stream] = POS_FIX_POSBUF;
} }
if (!bdl_pos_adj[chip->dev_index])
return 1; /* no delayed ack */
if (WARN_ONCE(!azx_dev->period_bytes, if (WARN_ONCE(!azx_dev->period_bytes,
"hda-intel: zero azx_dev->period_bytes")) "hda-intel: zero azx_dev->period_bytes"))
return 0; /* this shouldn't happen! */ return 0; /* this shouldn't happen! */
if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2) if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2)
return 0; /* NG - it's below the period boundary */ return 0; /* NG - it's below the period boundary */
azx_dev->start_wallclk = wallclk;
return 1; /* OK, it's fine */ return 1; /* OK, it's fine */
} }
...@@ -1922,7 +1923,7 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev) ...@@ -1922,7 +1923,7 @@ static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
static void azx_irq_pending_work(struct work_struct *work) static void azx_irq_pending_work(struct work_struct *work)
{ {
struct azx *chip = container_of(work, struct azx, irq_pending_work); struct azx *chip = container_of(work, struct azx, irq_pending_work);
int i, pending; int i, pending, ok;
if (!chip->irq_pending_warned) { if (!chip->irq_pending_warned) {
printk(KERN_WARNING printk(KERN_WARNING
...@@ -1941,11 +1942,14 @@ static void azx_irq_pending_work(struct work_struct *work) ...@@ -1941,11 +1942,14 @@ static void azx_irq_pending_work(struct work_struct *work)
!azx_dev->substream || !azx_dev->substream ||
!azx_dev->running) !azx_dev->running)
continue; continue;
if (azx_position_ok(chip, azx_dev)) { ok = azx_position_ok(chip, azx_dev);
if (ok > 0) {
azx_dev->irq_pending = 0; azx_dev->irq_pending = 0;
spin_unlock(&chip->reg_lock); spin_unlock(&chip->reg_lock);
snd_pcm_period_elapsed(azx_dev->substream); snd_pcm_period_elapsed(azx_dev->substream);
spin_lock(&chip->reg_lock); spin_lock(&chip->reg_lock);
} else if (ok < 0) {
pending = 0; /* too early */
} else } else
pending++; pending++;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment