[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=15912

Signed-off-by: Jaroslav Kysela <perex@perex.cz>
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index cad9b70..0a6c55b 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -174,7 +174,7 @@
 #define   ICH6_GSTS_FSTS	(1 << 1)   /* flush status */
 #define ICH6_REG_INTCTL			0x20
 #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_CORBLBASE		0x40
 #define ICH6_REG_CORBUBASE		0x44
@@ -340,8 +340,8 @@
 	unsigned int period_bytes; /* size of the period in bytes */
 	unsigned int frags;	/* number for period in the play buffer */
 	unsigned int fifo_size;	/* FIFO size */
-	unsigned long start_jiffies;	/* start + minimum jiffies */
-	unsigned long min_jiffies;	/* minimum jiffies before position is valid */
+	unsigned long start_wallclk;	/* start + minimum wallclk */
+	unsigned long period_wallclk;	/* wallclk for period */
 
 	void __iomem *sd_addr;	/* stream descriptor pointer */
 
@@ -361,7 +361,6 @@
 	unsigned int opened :1;
 	unsigned int running :1;
 	unsigned int irq_pending :1;
-	unsigned int start_flag: 1;	/* stream full start flag */
 	/*
 	 * For VIA:
 	 *  A flag to ensure DMA position is 0
@@ -1676,8 +1675,9 @@
 			return err;
 	}
 
-	azx_dev->min_jiffies = (runtime->period_size * HZ) /
-						(runtime->rate * 2);
+	/* wallclk has 24Mhz clock source */
+	azx_dev->period_wallclk = (((runtime->period_size * 24000) /
+						runtime->rate) * 1000);
 	azx_setup_controller(chip, azx_dev);
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 		azx_dev->fifo_size = azx_sd_readw(azx_dev, SD_FIFOSIZE) + 1;
@@ -1731,14 +1731,15 @@
 		if (s->pcm->card != substream->pcm->card)
 			continue;
 		azx_dev = get_azx_dev(s);
-		if (rstart) {
-			azx_dev->start_flag = 1;
-			azx_dev->start_jiffies = jiffies + azx_dev->min_jiffies;
-		}
-		if (start)
+		if (start) {
+			azx_dev->start_wallclk = azx_readl(chip, WALLCLK);
+			if (!rstart)
+				azx_dev->start_wallclk -=
+						azx_dev->period_wallclk;
 			azx_stream_start(chip, azx_dev);
-		else
+		} else {
 			azx_stream_stop(chip, azx_dev);
+		}
 		azx_dev->running = start;
 	}
 	spin_unlock(&chip->reg_lock);
@@ -1885,13 +1886,14 @@
  */
 static int azx_position_ok(struct azx *chip, struct azx_dev *azx_dev)
 {
+	u32 wallclk;
 	unsigned int pos;
 	int stream;
 
-	if (azx_dev->start_flag &&
-	    time_before_eq(jiffies, azx_dev->start_jiffies))
+	wallclk = azx_readl(chip, WALLCLK);
+	if ((wallclk - azx_dev->start_wallclk) <
+				(azx_dev->period_wallclk * 2) / 3)
 		return -1;	/* bogus (too early) interrupt */
-	azx_dev->start_flag = 0;
 
 	stream = azx_dev->substream->stream;
 	pos = azx_get_position(chip, azx_dev);
@@ -1906,13 +1908,12 @@
 			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,
 		      "hda-intel: zero azx_dev->period_bytes"))
 		return 0; /* this shouldn't happen! */
 	if (pos % azx_dev->period_bytes > azx_dev->period_bytes / 2)
 		return 0; /* NG - it's below the period boundary */
+	azx_dev->start_wallclk = wallclk;
 	return 1; /* OK, it's fine */
 }
 
@@ -1922,7 +1923,7 @@
 static void azx_irq_pending_work(struct work_struct *work)
 {
 	struct azx *chip = container_of(work, struct azx, irq_pending_work);
-	int i, pending;
+	int i, pending, ok;
 
 	if (!chip->irq_pending_warned) {
 		printk(KERN_WARNING
@@ -1941,11 +1942,14 @@
 			    !azx_dev->substream ||
 			    !azx_dev->running)
 				continue;
-			if (azx_position_ok(chip, azx_dev)) {
+			ok = azx_position_ok(chip, azx_dev);
+			if (ok > 0) {
 				azx_dev->irq_pending = 0;
 				spin_unlock(&chip->reg_lock);
 				snd_pcm_period_elapsed(azx_dev->substream);
 				spin_lock(&chip->reg_lock);
+			} else if (ok < 0) {
+				pending = 0;	/* too early */
 			} else
 				pending++;
 		}