summary refs log tree commit diff
path: root/sound
diff options
context:
space:
mode:
Diffstat (limited to 'sound')
-rw-r--r--sound/arm/aaci.c285
-rw-r--r--sound/arm/aaci.h9
2 files changed, 136 insertions, 158 deletions
diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c
index 7c1fc64cb53d..d0cead38d5fb 100644
--- a/sound/arm/aaci.c
+++ b/sound/arm/aaci.c
@@ -210,6 +210,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 
 	if (mask & ISR_RXINTR) {
 		struct aaci_runtime *aacirun = &aaci->capture;
+		bool period_elapsed = false;
 		void *ptr;
 
 		if (!aacirun->substream || !aacirun->start) {
@@ -222,15 +223,12 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 
 		ptr = aacirun->ptr;
 		do {
-			unsigned int len = aacirun->fifosz;
+			unsigned int len = aacirun->fifo_bytes;
 			u32 val;
 
 			if (aacirun->bytes <= 0) {
 				aacirun->bytes += aacirun->period;
-				aacirun->ptr = ptr;
-				spin_unlock(&aacirun->lock);
-				snd_pcm_period_elapsed(aacirun->substream);
-				spin_lock(&aacirun->lock);
+				period_elapsed = true;
 			}
 			if (!(aacirun->cr & CR_EN))
 				break;
@@ -260,6 +258,9 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 		aacirun->ptr = ptr;
 
 		spin_unlock(&aacirun->lock);
+
+		if (period_elapsed)
+			snd_pcm_period_elapsed(aacirun->substream);
 	}
 
 	if (mask & ISR_URINTR) {
@@ -269,6 +270,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 
 	if (mask & ISR_TXINTR) {
 		struct aaci_runtime *aacirun = &aaci->playback;
+		bool period_elapsed = false;
 		void *ptr;
 
 		if (!aacirun->substream || !aacirun->start) {
@@ -281,15 +283,12 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 
 		ptr = aacirun->ptr;
 		do {
-			unsigned int len = aacirun->fifosz;
+			unsigned int len = aacirun->fifo_bytes;
 			u32 val;
 
 			if (aacirun->bytes <= 0) {
 				aacirun->bytes += aacirun->period;
-				aacirun->ptr = ptr;
-				spin_unlock(&aacirun->lock);
-				snd_pcm_period_elapsed(aacirun->substream);
-				spin_lock(&aacirun->lock);
+				period_elapsed = true;
 			}
 			if (!(aacirun->cr & CR_EN))
 				break;
@@ -319,6 +318,9 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
 		aacirun->ptr = ptr;
 
 		spin_unlock(&aacirun->lock);
+
+		if (period_elapsed)
+			snd_pcm_period_elapsed(aacirun->substream);
 	}
 }
 
@@ -361,7 +363,7 @@ static struct snd_pcm_hardware aaci_hw_info = {
 
 	/* rates are setup from the AC'97 codec */
 	.channels_min		= 2,
-	.channels_max		= 6,
+	.channels_max		= 2,
 	.buffer_bytes_max	= 64 * 1024,
 	.period_bytes_min	= 256,
 	.period_bytes_max	= PAGE_SIZE,
@@ -369,12 +371,46 @@ static struct snd_pcm_hardware aaci_hw_info = {
 	.periods_max		= PAGE_SIZE / 16,
 };
 
-static int __aaci_pcm_open(struct aaci *aaci,
-			   struct snd_pcm_substream *substream,
-			   struct aaci_runtime *aacirun)
+/*
+ * We can support two and four channel audio.  Unfortunately
+ * six channel audio requires a non-standard channel ordering:
+ *   2 -> FL(3), FR(4)
+ *   4 -> FL(3), FR(4), SL(7), SR(8)
+ *   6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required)
+ *        FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual)
+ * This requires an ALSA configuration file to correct.
+ */
+static int aaci_rule_channels(struct snd_pcm_hw_params *p,
+	struct snd_pcm_hw_rule *rule)
+{
+	static unsigned int channel_list[] = { 2, 4, 6 };
+	struct aaci *aaci = rule->private;
+	unsigned int mask = 1 << 0, slots;
+
+	/* pcms[0] is the our 5.1 PCM instance. */
+	slots = aaci->ac97_bus->pcms[0].r[0].slots;
+	if (slots & (1 << AC97_SLOT_PCM_SLEFT)) {
+		mask |= 1 << 1;
+		if (slots & (1 << AC97_SLOT_LFE))
+			mask |= 1 << 2;
+	}
+
+	return snd_interval_list(hw_param_interval(p, rule->var),
+				 ARRAY_SIZE(channel_list), channel_list, mask);
+}
+
+static int aaci_pcm_open(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
-	int ret;
+	struct aaci *aaci = substream->private_data;
+	struct aaci_runtime *aacirun;
+	int ret = 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		aacirun = &aaci->playback;
+	} else {
+		aacirun = &aaci->capture;
+	}
 
 	aacirun->substream = substream;
 	runtime->private_data = aacirun;
@@ -382,27 +418,37 @@ static int __aaci_pcm_open(struct aaci *aaci,
 	runtime->hw.rates = aacirun->pcm->rates;
 	snd_pcm_limit_hw_rates(runtime);
 
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
-	    aacirun->pcm->r[1].slots)
-		snd_ac97_pcm_double_rate_rules(runtime);
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		runtime->hw.channels_max = 6;
+
+		/* Add rule describing channel dependency. */
+		ret = snd_pcm_hw_rule_add(substream->runtime, 0,
+					  SNDRV_PCM_HW_PARAM_CHANNELS,
+					  aaci_rule_channels, aaci,
+					  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+		if (ret)
+			return ret;
+
+		if (aacirun->pcm->r[1].slots)
+			snd_ac97_pcm_double_rate_rules(runtime);
+	}
 
 	/*
-	 * FIXME: ALSA specifies fifo_size in bytes.  If we're in normal
-	 * mode, each 32-bit word contains one sample.  If we're in
-	 * compact mode, each 32-bit word contains two samples, effectively
-	 * halving the FIFO size.  However, we don't know for sure which
-	 * we'll be using at this point.  We set this to the lower limit.
+	 * ALSA wants the byte-size of the FIFOs.  As we only support
+	 * 16-bit samples, this is twice the FIFO depth irrespective
+	 * of whether it's in compact mode or not.
 	 */
-	runtime->hw.fifo_size = aaci->fifosize * 2;
-
-	ret = request_irq(aaci->dev->irq[0], aaci_irq, IRQF_SHARED|IRQF_DISABLED,
-			  DRIVER_NAME, aaci);
-	if (ret)
-		goto out;
-
-	return 0;
+	runtime->hw.fifo_size = aaci->fifo_depth * 2;
+
+	mutex_lock(&aaci->irq_lock);
+	if (!aaci->users++) {
+		ret = request_irq(aaci->dev->irq[0], aaci_irq,
+			   IRQF_SHARED | IRQF_DISABLED, DRIVER_NAME, aaci);
+		if (ret != 0)
+			aaci->users--;
+	}
+	mutex_unlock(&aaci->irq_lock);
 
- out:
 	return ret;
 }
 
@@ -418,7 +464,11 @@ static int aaci_pcm_close(struct snd_pcm_substream *substream)
 	WARN_ON(aacirun->cr & CR_EN);
 
 	aacirun->substream = NULL;
-	free_irq(aaci->dev->irq[0], aaci);
+
+	mutex_lock(&aaci->irq_lock);
+	if (!--aaci->users)
+		free_irq(aaci->dev->irq[0], aaci);
+	mutex_unlock(&aaci->irq_lock);
 
 	return 0;
 }
@@ -444,12 +494,21 @@ static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
 	return 0;
 }
 
+/* Channel to slot mask */
+static const u32 channels_to_slotmask[] = {
+	[2] = CR_SL3 | CR_SL4,
+	[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,
+	[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,
+};
+
 static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
-			      struct aaci_runtime *aacirun,
 			      struct snd_pcm_hw_params *params)
 {
+	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	unsigned int channels = params_channels(params);
+	unsigned int rate = params_rate(params);
+	int dbl = rate > 48000;
 	int err;
-	struct aaci *aaci = substream->private_data;
 
 	aaci_pcm_hw_free(substream);
 	if (aacirun->pcm_open) {
@@ -457,22 +516,28 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
 		aacirun->pcm_open = 0;
 	}
 
+	/* channels is already limited to 2, 4, or 6 by aaci_rule_channels */
+	if (dbl && channels != 2)
+		return -EINVAL;
+
 	err = snd_pcm_lib_malloc_pages(substream,
 				       params_buffer_bytes(params));
 	if (err >= 0) {
-		unsigned int rate = params_rate(params);
-		int dbl = rate > 48000;
+		struct aaci *aaci = substream->private_data;
 
-		err = snd_ac97_pcm_open(aacirun->pcm, rate,
-					params_channels(params),
+		err = snd_ac97_pcm_open(aacirun->pcm, rate, channels,
 					aacirun->pcm->r[dbl].slots);
 
 		aacirun->pcm_open = err == 0;
 		aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;
-		aacirun->fifosz = aaci->fifosize * 4;
-
-		if (aacirun->cr & CR_COMPACT)
-			aacirun->fifosz >>= 1;
+		aacirun->cr |= channels_to_slotmask[channels + dbl * 2];
+
+		/*
+		 * fifo_bytes is the number of bytes we transfer to/from
+		 * the FIFO, including padding.  So that's x4.  As we're
+		 * in compact mode, the FIFO is half the size.
+		 */
+		aacirun->fifo_bytes = aaci->fifo_depth * 4 / 2;
 	}
 
 	return err;
@@ -483,11 +548,11 @@ static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct aaci_runtime *aacirun = runtime->private_data;
 
+	aacirun->period	= snd_pcm_lib_period_bytes(substream);
 	aacirun->start	= runtime->dma_area;
 	aacirun->end	= aacirun->start + snd_pcm_lib_buffer_bytes(substream);
 	aacirun->ptr	= aacirun->start;
-	aacirun->period	=
-	aacirun->bytes	= frames_to_bytes(runtime, runtime->period_size);
+	aacirun->bytes	= aacirun->period;
 
 	return 0;
 }
@@ -505,89 +570,6 @@ static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream)
 /*
  * Playback specific ALSA stuff
  */
-static const u32 channels_to_txmask[] = {
-	[2] = CR_SL3 | CR_SL4,
-	[4] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8,
-	[6] = CR_SL3 | CR_SL4 | CR_SL7 | CR_SL8 | CR_SL6 | CR_SL9,
-};
-
-/*
- * We can support two and four channel audio.  Unfortunately
- * six channel audio requires a non-standard channel ordering:
- *   2 -> FL(3), FR(4)
- *   4 -> FL(3), FR(4), SL(7), SR(8)
- *   6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required)
- *        FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual)
- * This requires an ALSA configuration file to correct.
- */
-static unsigned int channel_list[] = { 2, 4, 6 };
-
-static int
-aaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule)
-{
-	struct aaci *aaci = rule->private;
-	unsigned int chan_mask = 1 << 0, slots;
-
-	/*
-	 * pcms[0] is the our 5.1 PCM instance.
-	 */
-	slots = aaci->ac97_bus->pcms[0].r[0].slots;
-	if (slots & (1 << AC97_SLOT_PCM_SLEFT)) {
-		chan_mask |= 1 << 1;
-		if (slots & (1 << AC97_SLOT_LFE))
-			chan_mask |= 1 << 2;
-	}
-
-	return snd_interval_list(hw_param_interval(p, rule->var),
-				 ARRAY_SIZE(channel_list), channel_list,
-				 chan_mask);
-}
-
-static int aaci_pcm_open(struct snd_pcm_substream *substream)
-{
-	struct aaci *aaci = substream->private_data;
-	int ret;
-
-	/*
-	 * Add rule describing channel dependency.
-	 */
-	ret = snd_pcm_hw_rule_add(substream->runtime, 0,
-				  SNDRV_PCM_HW_PARAM_CHANNELS,
-				  aaci_rule_channels, aaci,
-				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-	if (ret)
-		return ret;
-
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
-		ret = __aaci_pcm_open(aaci, substream, &aaci->playback);
-	} else {
-		ret = __aaci_pcm_open(aaci, substream, &aaci->capture);
-	}
-	return ret;
-}
-
-static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream,
-				       struct snd_pcm_hw_params *params)
-{
-	struct aaci_runtime *aacirun = substream->runtime->private_data;
-	unsigned int channels = params_channels(params);
-	int ret;
-
-	WARN_ON(channels >= ARRAY_SIZE(channels_to_txmask) ||
-		!channels_to_txmask[channels]);
-
-	ret = aaci_pcm_hw_params(substream, aacirun, params);
-
-	/*
-	 * Enable FIFO, compact mode, 16 bits per sample.
-	 * FIXME: double rate slots?
-	 */
-	if (ret >= 0)
-		aacirun->cr |= channels_to_txmask[channels];
-
-	return ret;
-}
-
 static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
 {
 	u32 ie;
@@ -657,27 +639,13 @@ static struct snd_pcm_ops aaci_playback_ops = {
 	.open		= aaci_pcm_open,
 	.close		= aaci_pcm_close,
 	.ioctl		= snd_pcm_lib_ioctl,
-	.hw_params	= aaci_pcm_playback_hw_params,
+	.hw_params	= aaci_pcm_hw_params,
 	.hw_free	= aaci_pcm_hw_free,
 	.prepare	= aaci_pcm_prepare,
 	.trigger	= aaci_pcm_playback_trigger,
 	.pointer	= aaci_pcm_pointer,
 };
 
-static int aaci_pcm_capture_hw_params(struct snd_pcm_substream *substream,
-				      struct snd_pcm_hw_params *params)
-{
-	struct aaci_runtime *aacirun = substream->runtime->private_data;
-	int ret;
-
-	ret = aaci_pcm_hw_params(substream, aacirun, params);
-	if (ret >= 0)
-		/* Line in record: slot 3 and 4 */
-		aacirun->cr |= CR_SL3 | CR_SL4;
-
-	return ret;
-}
-
 static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun)
 {
 	u32 ie;
@@ -774,7 +742,7 @@ static struct snd_pcm_ops aaci_capture_ops = {
 	.open		= aaci_pcm_open,
 	.close		= aaci_pcm_close,
 	.ioctl		= snd_pcm_lib_ioctl,
-	.hw_params	= aaci_pcm_capture_hw_params,
+	.hw_params	= aaci_pcm_hw_params,
 	.hw_free	= aaci_pcm_hw_free,
 	.prepare	= aaci_pcm_capture_prepare,
 	.trigger	= aaci_pcm_capture_trigger,
@@ -941,12 +909,13 @@ static struct aaci * __devinit aaci_init_card(struct amba_device *dev)
 	strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
 	strlcpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname));
 	snprintf(card->longname, sizeof(card->longname),
-		 "%s at 0x%016llx, irq %d",
-		 card->shortname, (unsigned long long)dev->res.start,
-		 dev->irq[0]);
+		 "%s PL%03x rev%u at 0x%08llx, irq %d",
+		 card->shortname, amba_part(dev), amba_rev(dev),
+		 (unsigned long long)dev->res.start, dev->irq[0]);
 
 	aaci = card->private_data;
 	mutex_init(&aaci->ac97_sem);
+	mutex_init(&aaci->irq_lock);
 	aaci->card = card;
 	aaci->dev = dev;
 
@@ -984,6 +953,10 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
 	struct aaci_runtime *aacirun = &aaci->playback;
 	int i;
 
+	/*
+	 * Enable the channel, but don't assign it to any slots, so
+	 * it won't empty onto the AC'97 link.
+	 */
 	writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);
 
 	for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
@@ -1002,7 +975,7 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
 	writel(aaci->maincr, aaci->base + AACI_MAINCR);
 
 	/*
-	 * If we hit 4096, we failed.  Go back to the specified
+	 * If we hit 4096 entries, we failed.  Go back to the specified
 	 * fifo depth.
 	 */
 	if (i == 4096)
@@ -1011,7 +984,8 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
 	return i;
 }
 
-static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id)
+static int __devinit aaci_probe(struct amba_device *dev,
+	const struct amba_id *id)
 {
 	struct aaci *aaci;
 	int ret, i;
@@ -1067,11 +1041,12 @@ static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id)
 
 	/*
 	 * Size the FIFOs (must be multiple of 16).
+	 * This is the number of entries in the FIFO.
 	 */
-	aaci->fifosize = aaci_size_fifo(aaci);
-	if (aaci->fifosize & 15) {
-		printk(KERN_WARNING "AACI: fifosize = %d not supported\n",
-		       aaci->fifosize);
+	aaci->fifo_depth = aaci_size_fifo(aaci);
+	if (aaci->fifo_depth & 15) {
+		printk(KERN_WARNING "AACI: FIFO depth %d not supported\n",
+		       aaci->fifo_depth);
 		ret = -ENODEV;
 		goto out;
 	}
@@ -1084,8 +1059,8 @@ static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id)
 
 	ret = snd_card_register(aaci->card);
 	if (ret == 0) {
-		dev_info(&dev->dev, "%s, fifo %d\n", aaci->card->longname,
-			 aaci->fifosize);
+		dev_info(&dev->dev, "%s\n", aaci->card->longname);
+		dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth);
 		amba_set_drvdata(dev, aaci->card);
 		return ret;
 	}
diff --git a/sound/arm/aaci.h b/sound/arm/aaci.h
index 6a4a2eebdda1..5791bd5bd2ab 100644
--- a/sound/arm/aaci.h
+++ b/sound/arm/aaci.h
@@ -210,6 +210,8 @@ struct aaci_runtime {
 	u32			cr;
 	struct snd_pcm_substream	*substream;
 
+	unsigned int		period;	/* byte size of a "period" */
+
 	/*
 	 * PIO support
 	 */
@@ -217,15 +219,16 @@ struct aaci_runtime {
 	void			*end;
 	void			*ptr;
 	int			bytes;
-	unsigned int		period;
-	unsigned int		fifosz;
+	unsigned int		fifo_bytes;
 };
 
 struct aaci {
 	struct amba_device	*dev;
 	struct snd_card		*card;
 	void			__iomem *base;
-	unsigned int		fifosize;
+	unsigned int		fifo_depth;
+	unsigned int		users;
+	struct mutex		irq_lock;
 
 	/* AC'97 */
 	struct mutex		ac97_sem;