Subject: Re: snd-aoa status update / automatic driver loading

From: Benjamin Berg <[email-masked]>
Date: Sat, 20 May 2006 16:57:12 +0200

On Thu, 2006-18-05 at 09:41 +0900, Charles Plessy wrote: 
> Hi all,
> 
> just a "me too" mail:
> 
> Le Thu, May 18, 2006 at 12:02:01AM +0200, Børge Holen a écrit :
> > I can also remember half way throught a ogg/mp3 playlist when it also 
> > scrambled the output, this has only happened ONCE.
> 
> I experience the same on my 8,1 powermac, but more systematically. It
> takes usually more than one hour of continuous listening before it
> happens, and then it happens sort of stochastically. I am not using
> anything else than xmms, so I did not figure out if it is a xmms or a
> driver problem. Stop/Starting the listening stops the scrambling.

This is exactly the problem that I have experienced since a while now.
The problem is that some interrupts get lost. This results in a broken
address calculation and the new data is written to the wrong place.

The attached patch fixes this by checking the 'frame_count'.
What I don't really understand is, that the first time the interrupt
gets executed, the frame_count is 8 _less_ of what I would have expected
My guess is that the dma controller reads the last 32 bytes, and then
the interrupt gets fired.

diff --git a/soundbus/i2sbus/i2sbus-pcm.c b/soundbus/i2sbus/i2sbus-pcm.c
index 9eadf83..8511234 100644
--- a/soundbus/i2sbus/i2sbus-pcm.c
+++ b/soundbus/i2sbus/i2sbus-pcm.c
@@ -440,6 +440,11 @@ static int i2sbus_pcm_trigger(struct i2s
 			return -ENXIO;
 		}
 
+		/* get the current frame_count - 32 bytes. This is just guessed,
+		   but it seems that the interrupt triggers as soon as the last 32 bytes
+		   are cached or something. */
+		pi->frame_count = in_le32(&i2sdev->intfregs->frame_count) - 0x20 / (pi->substream->runtime->sample_bits / 8);
+
 		/* wake up the chip with the next descriptor */
 		out_le32(&pi->dbdma->control, (RUN|WAKE) | ((RUN|WAKE)<<16));
 		/* off you go! */
@@ -488,13 +493,29 @@ static snd_pcm_uframes_t i2sbus_pcm_poin
 static inline void handle_interrupt(struct i2sbus_dev *i2sdev, int in)
 {
 	struct pcm_info *pi;
+	u32 fc;
+	u32 delta;
 
 	get_pcm_info(i2sdev, in, &pi, NULL);
 	if (!pi->substream) {
 		printk(KERN_INFO "i2sbus: got %s irq while not active!\n", in?"rx":"tx");
 		return;
 	}
-	pi->current_period = (pi->current_period+1) % (pi->periods);
+	
+	fc = in_le32(&i2sdev->intfregs->frame_count);
+	/* a counter overflow does not change the calculation. */
+	delta = fc - pi->frame_count;
+	
+	if (delta <= pi->substream->runtime->period_size) {
+		pi->current_period = pi->current_period + 1;
+		delta = 0;
+	} else while (delta >= pi->substream->runtime->period_size) {
+		pi->current_period = pi->current_period + 1;
+		delta = delta - pi->substream->runtime->period_size;
+	}
+	
+	pi->frame_count = fc - delta;
+	pi->current_period = pi->current_period % pi->periods;
 	snd_pcm_period_elapsed(pi->substream);
 }
 
diff --git a/soundbus/i2sbus/i2sbus.h b/soundbus/i2sbus/i2sbus.h
index b054e02..f5d16aa 100644
--- a/soundbus/i2sbus/i2sbus.h
+++ b/soundbus/i2sbus/i2sbus.h
@@ -41,6 +41,7 @@ struct pcm_info {
 	struct snd_pcm_substream *substream;
 	int current_period;
 	int periods;
+	u32 frame_count;
 	struct dbdma_command_mem dbdma_ring;
 	volatile struct dbdma_regs __iomem *dbdma;
 };