ALSA: dummy - Fix the timer calculation in systimer mode

Fix the expire-time calculation in the systimer mode when the buffer
size isn't aligned to the period size.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c
index 0a798bd..e8e29bf 100644
--- a/sound/drivers/dummy.c
+++ b/sound/drivers/dummy.c
@@ -207,19 +207,18 @@
 	struct timer_list timer;
 	unsigned long base_time;
 	unsigned int frac_pos;	/* fractional sample position (based HZ) */
+	unsigned int frac_period_rest;
 	unsigned int frac_buffer_size;	/* buffer_size * HZ */
 	unsigned int frac_period_size;	/* period_size * HZ */
 	unsigned int rate;
+	int elapsed;
 	struct snd_pcm_substream *substream;
 };
 
 static void dummy_systimer_rearm(struct dummy_systimer_pcm *dpcm)
 {
-	unsigned long frac;
-
-	frac = dpcm->frac_pos % dpcm->frac_period_size;
 	dpcm->timer.expires = jiffies +
-		(dpcm->frac_period_size + dpcm->rate - 1) / dpcm->rate;
+		(dpcm->frac_period_rest + dpcm->rate - 1) / dpcm->rate;
 	add_timer(&dpcm->timer);
 }
 
@@ -230,10 +229,16 @@
 	delta = jiffies - dpcm->base_time;
 	if (!delta)
 		return;
-	dpcm->base_time = jiffies;
-	dpcm->frac_pos += delta * dpcm->rate;
+	dpcm->base_time += delta;
+	delta *= dpcm->rate;
+	dpcm->frac_pos += delta;
 	while (dpcm->frac_pos >= dpcm->frac_buffer_size)
 		dpcm->frac_pos -= dpcm->frac_buffer_size;
+	while (dpcm->frac_period_rest <= delta) {
+		dpcm->elapsed++;
+		dpcm->frac_period_rest += dpcm->frac_period_size;
+	}
+	dpcm->frac_period_rest -= delta;
 }
 
 static int dummy_systimer_start(struct snd_pcm_substream *substream)
@@ -264,6 +269,8 @@
 	dpcm->rate = runtime->rate;
 	dpcm->frac_buffer_size = runtime->buffer_size * HZ;
 	dpcm->frac_period_size = runtime->period_size * HZ;
+	dpcm->frac_period_rest = dpcm->frac_period_size;
+	dpcm->elapsed = 0;
 
 	return 0;
 }
@@ -272,23 +279,29 @@
 {
 	struct dummy_systimer_pcm *dpcm = (struct dummy_systimer_pcm *)data;
 	unsigned long flags;
+	int elapsed = 0;
 	
 	spin_lock_irqsave(&dpcm->lock, flags);
 	dummy_systimer_update(dpcm);
 	dummy_systimer_rearm(dpcm);
+	elapsed = dpcm->elapsed;
+	dpcm->elapsed = 0;
 	spin_unlock_irqrestore(&dpcm->lock, flags);
-	snd_pcm_period_elapsed(dpcm->substream);
+	if (elapsed)
+		snd_pcm_period_elapsed(dpcm->substream);
 }
 
 static snd_pcm_uframes_t
 dummy_systimer_pointer(struct snd_pcm_substream *substream)
 {
 	struct dummy_systimer_pcm *dpcm = substream->runtime->private_data;
+	snd_pcm_uframes_t pos;
 
 	spin_lock(&dpcm->lock);
 	dummy_systimer_update(dpcm);
+	pos = dpcm->frac_pos / HZ;
 	spin_unlock(&dpcm->lock);
-	return dpcm->frac_pos / HZ;
+	return pos;
 }
 
 static int dummy_systimer_create(struct snd_pcm_substream *substream)