[RFC 6/7][PATCH] AMBA DMA: Patch to allow ARM PrimeCell Advanced Codec Interface to use AMBA DMA.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Provides an example of use of AMBA DMA for audio playback.
Code is targetted at the Versatile development board.

Signed-off-by: Peter M Pearse <[email protected]> 

---

diff -purN arm_amba_pl080err/sound/arm/aaci.c arm_amba_aaci/sound/arm/aaci.c
--- arm_amba_pl080err/sound/arm/aaci.c	2006-10-17 13:29:53.000000000 +0100
+++ arm_amba_aaci/sound/arm/aaci.c	2006-10-18 08:35:36.000000000 +0100
@@ -18,6 +18,9 @@
 #include <linux/interrupt.h>
 #include <linux/err.h>
 #include <linux/amba/bus.h>
+#if defined(CONFIG_ARM_AMBA_DMA) && defined(CONFIG_ARCH_VERSATILE) 
+# include <linux/dma-mapping.h>	
+#endif
 
 #include <asm/io.h>
 #include <asm/irq.h>
@@ -31,7 +34,7 @@
 #include <sound/pcm_params.h>
 
 #include "aaci.h"
-#include "devdma.h"
+#include "devdma.h" /* DMA memory is used for the buffer even if there is
no DMA for this architecture */ 
 
 #define DRIVER_NAME	"aaci-pl041"
 
@@ -122,10 +125,9 @@ static unsigned short aaci_ac97_read(str
 	} while (v & SLFR_1TXB);
 
 	/*
-	 * Give the AC'97 codec more than enough time
-	 * to respond. (42us = ~2 frames at 48kHz.)
+	 * Allow time for AC'97 codec response
 	 */
-	udelay(42);
+	udelay(TIME_TO_RESPOND);
 
 	/*
 	 * Wait for slot 2 to indicate data.
@@ -164,6 +166,61 @@ static inline void aaci_chan_wait_ready(
 /*
  * Interrupt support.
  */
+#if defined(CONFIG_ARM_AMBA_DMA) && defined(CONFIG_ARCH_VERSATILE) 
+
+/* 
+ * Simple handler for chaining to the DMAC interrupt
+ * - called after each DMA packet (half a fifo depth)
+ * ASSUMES local interrupts disabled
+ */
+static irqreturn_t aaci_dma_irq(int irq, void *devid, struct pt_regs *regs)
+{
+
+	struct amba_device *	client	= devid;
+	struct amba_dma_data *	data 	= &(client->dma_data);
+	snd_card_t *		card 	= amba_get_drvdata(client);
+	struct aaci *		aaci 	= card->private_data;
+	struct aaci_runtime *	tsfr	= &aaci->playback;
+
+	/* e.g. check for errors */
+	data->irq_pre(0);	
+
+	/* Update pointer by one packet */
+	spin_lock(&aaci->lock);
+	{
+		tsfr->ptr += aaci->fifosize/2;
+		if (tsfr->ptr >= tsfr->end) {
+			tsfr->ptr = tsfr->start;
+		}
+		
+		tsfr->bytes -= aaci->fifosize;
+
+		if(tsfr->bytes <= 0){
+			/* Starting another period */
+			tsfr->bytes += tsfr->period;
+			spin_unlock(&aaci->lock);
+			snd_pcm_period_elapsed(tsfr->substream);
+			spin_lock(&aaci->lock);
+		}
+	}
+	spin_unlock(&aaci->lock);
+	
+	/*
+	 * Should we examine the FIFO status
+	 * to ensure we don't re-trigger on same DMABREQ
+	 */
+
+	/* e.g clear interrupt on the DMAC */
+	data->irq_post(tsfr->dma_chan);
+	
+	return IRQ_HANDLED;
+}
+
+#endif
+/*
+ * FIFO interrupt
+ * ASSUMES local interrupts disabled
+ */
 static void aaci_fifo_irq(struct aaci *aaci, u32 mask)
 {
 	if (mask & ISR_URINTR) {
@@ -330,12 +387,27 @@ static struct snd_pcm_hardware aaci_hw_i
 	.periods_max		= PAGE_SIZE / 16,
 };
 
+/*
+ *  Note that the interrupt handler is not attached until we prepare the
transfer
+ */
 static int aaci_pcm_open(struct aaci *aaci, struct snd_pcm_substream
*substream,
 			 struct aaci_runtime *aacirun)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	int ret;
 
+#ifdef CONFIG_ARM_AMBA_DMA
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	/* 
+	 * Indicate no channel yet assigned
+	 */
+	aacirun->dma_chan = MAX_DMA_CHANNELS;
+# endif
+
+#endif
+
 	aacirun->substream = substream;
 	runtime->private_data = aacirun;
 	runtime->hw = aaci_hw_info;
@@ -357,17 +429,6 @@ static int aaci_pcm_open(struct aaci *aa
 				  aaci_rule_rate_by_channels, aaci,
 				  SNDRV_PCM_HW_PARAM_CHANNELS,
 				  SNDRV_PCM_HW_PARAM_RATE, -1);
-	if (ret)
-		goto out;
-
-	ret = request_irq(aaci->dev->irq[0], aaci_irq,
IRQF_SHARED|IRQF_DISABLED,
-			  DRIVER_NAME, aaci);
-	if (ret)
-		goto out;
-
-	return 0;
-
- out:
 	return ret;
 }
 
@@ -383,14 +444,43 @@ static int aaci_pcm_close(struct snd_pcm
 	WARN_ON(aacirun->cr & TXCR_TXEN);
 
 	aacirun->substream = NULL;
-	free_irq(aaci->dev->irq[0], aaci);
 
+#ifdef CONFIG_ARM_AMBA_DMA
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	if(MAX_DMA_CHANNELS != aacirun->dma_chan){
+		/* Free DMA channel & interrupt */
+		unsigned long flags;
+		flags = claim_dma_lock();
+		free_dma(aacirun->dma_chan);
+		aacirun->dma_chan = MAX_DMA_CHANNELS; 
+		free_irq(INT_DMAINT, aaci->dev);
+		release_dma_lock(flags);
+	} else {
+
+# endif 
+#endif	
+	/* Free the fifo interrupt */
+ 	free_irq(aaci->dev->irq[0], aaci);
+	aaci->fifo_irq_attached = 0;
+
+#ifdef CONFIG_ARM_AMBA_DMA
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+	}
+# endif 
+#endif	
+	
+	// TODO:: Should only be done when all transfers complete, not just
the playback...
+	writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
+	
 	return 0;
 }
 
+/* There may be multiple calls during one transfer */
 static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
 {
 	struct aaci_runtime *aacirun = substream->runtime->private_data;
+	struct aaci *aaci = substream->private_data;
 
 	/*
 	 * This must not be called with the device enabled.
@@ -404,21 +494,24 @@ static int aaci_pcm_hw_free(struct snd_p
 	/*
 	 * Clear out the DMA and any allocated buffers.
 	 */
-	devdma_hw_free(NULL, substream);
+	devdma_hw_free((struct device *)aaci->dev, substream);
+	aacirun->start = NULL;
 
 	return 0;
 }
 
+/* There may be multiple calls during one transfer */
 static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
 			      struct aaci_runtime *aacirun,
 			      struct snd_pcm_hw_params *params)
 {
 	int err;
+	struct aaci *aaci = substream->private_data;
 
 	aaci_pcm_hw_free(substream);
 
-	err = devdma_hw_alloc(NULL, substream,
-			      params_buffer_bytes(params));
+	err = devdma_hw_alloc((struct device *)aaci->dev, substream,
+			 params_buffer_bytes(params));
 	if (err < 0)
 		goto out;
 
@@ -434,8 +527,19 @@ static int aaci_pcm_hw_params(struct snd
 	return err;
 }
 
+/*
+ * Setup the AACI for the transfer
+ * 
+ * There may be multiple calls during one transfer
+ *
+ * For DMA :-
+ * Finalize the linked list 
+ * - we dont know the period size until here
+ */
 static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
 {
+	int ret = -EINVAL;
+	struct aaci *aaci = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct aaci_runtime *aacirun = runtime->private_data;
 
@@ -445,9 +549,100 @@ static int aaci_pcm_prepare(struct snd_p
 	aacirun->period	=
 	aacirun->bytes	= frames_to_bytes(runtime, runtime->period_size);
 
-	return 0;
+	if(aaci->fifo_irq_attached){
+		/* 
+		 * Note that we don't try for DMA if we already have the
FIFO interrupt
+		 * TODO:: Drop the fifo, retry for DMA
+		 */
+		ret = 0;
+	} else {
+
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+		/*
+		 *	Try for DMA, if fails fall back to fifo interrrupts
+		 *	- first, attempt to attach our handler to the DMA
interrupt
+		 */
+		/* 
+		 * May be a multiple call
+		 * - if so release any assigned channel & re-acquire:- 
+		 * If using the pl080 controller and a different 
+		 * DMA buffer has been allocated 
+		 * we must set up the LLIs again
+		 */
+		if(MAX_DMA_CHANNELS != aacirun->dma_chan){ 
+			unsigned long flags;
+			flags = claim_dma_lock();
+			free_dma(aacirun->dma_chan);
+			aacirun->dma_chan = MAX_DMA_CHANNELS; 
+			free_irq(INT_DMAINT, aaci->dev);
+			release_dma_lock(flags);
+		}
+
+ 		/* The DMA interrupt is requested as shared, it may be
chained to other devices. */
+		if(!request_irq(IRQ_DMAINT, aaci_dma_irq,
SA_SHIRQ|SA_INTERRUPT,
+				DRIVER_NAME, aaci->dev)){
+
+			/* Acquired the DMA interrupt, now request a channel
for playback */
+			int i;
+			/*
+			 * Transfer half a FIFO's worth of data in each DMA
transfer
+			 * - ensures no overrun
+			 */
+			aaci->dev->dma_data.packet_size = aaci->fifosize/2;
+			/* 
+			 * FIFO register offset - DMA only available on
channel 1
+			 */
+			aaci->dev->dma_data.dmac_data = (void *)AACI_DR1;
+
+			for(i = 0; i < MAX_DMA_CHANNELS; i++){
+
+				if(!dma_channel_active(i)){
+				 	set_dma_mode (i, DMA_TO_DEVICE);
+					set_dma_addr (i,
runtime->dma_buffer_p->addr);
+					set_dma_count(i,
runtime->dma_bytes);
+
+					if(!(ret = request_dma(i,
DRIVER_NAME))){
+						aacirun->dma_chan = i;
+						break;
+					}
+
+				}
+			}
+
+			if(MAX_DMA_CHANNELS == aacirun->dma_chan)
+				free_irq(IRQ_DMAINT, aaci->dev);
+
+		} else {
+			printk(KERN_ERR "aaci.c::aaci_pcm_prepare() Couldn't
attach the interrupt handler\n");
+		}
+
+		if(MAX_DMA_CHANNELS == aacirun->dma_chan){
+			/* No DMA available - use fifo interrupts */
+# endif
+
+#endif
+	 	ret = request_irq(aaci->dev->irq[0], aaci_irq, SA_INTERRUPT,
+	 			DRIVER_NAME, aaci);
+	 	aaci->fifo_irq_attached = !ret;
+
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+		} 
+# endif
+
+#endif
+
+	}
+	return ret;
 }
 
+/*
+ * Must be atomic
+ */
 static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream
*substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -554,9 +749,36 @@ static void aaci_pcm_playback_stop(struc
 {
 	u32 ie;
 
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	if(MAX_DMA_CHANNELS != aacirun->dma_chan){
+		struct aaci* aaci = aacirun->substream->private_data;
+		disable_dma(aacirun->dma_chan);
+		/* 
+		 * Disable DMA on the AACI by clearing the 'DMAEnable' bit. 
+		 */	
+		writel(aaci->maincr & ~MAINCR_DMAEN, aaci->base +
AACI_MAINCR);
+	} else {
+# endif
+
+#endif
+
 	ie = readl(aacirun->base + AACI_IE);
 	ie &= ~(IE_URIE|IE_TXIE);
 	writel(ie, aacirun->base + AACI_IE);
+
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	}
+
+# endif
+
+#endif
+
 	aacirun->cr &= ~TXCR_TXEN;
 	aaci_chan_wait_ready(aacirun);
 	writel(aacirun->cr, aacirun->base + AACI_TXCR);
@@ -569,12 +791,45 @@ static void aaci_pcm_playback_start(stru
 	aaci_chan_wait_ready(aacirun);
 	aacirun->cr |= TXCR_TXEN;
 
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+	/*
+	 * Ensure the DMAC finds a BREQ from the AACI
+	 * as soon as the DMAC is enabled
+	 */
+	writel(aacirun->cr, aacirun->base + AACI_TXCR);
+	if(MAX_DMA_CHANNELS != aacirun->dma_chan){
+		struct aaci* aaci = aacirun->substream->private_data;
+		/*
+		 * Set the 'DMAEnable' bit in the AACI PrimeCell. 
+		 */
+		writel(aaci->maincr | MAINCR_DMAEN, aaci->base +
AACI_MAINCR);
+		enable_dma(aacirun->dma_chan);
+	} else {
+# endif
+
+#endif
+
 	ie = readl(aacirun->base + AACI_IE);
 	ie |= IE_URIE | IE_TXIE;
 	writel(ie, aacirun->base + AACI_IE);
 	writel(aacirun->cr, aacirun->base + AACI_TXCR);
+
+#ifdef CONFIG_ARM_AMBA_DMA 
+
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	}
+
+# endif
+
+#endif
 }
 
+/*
+ * Must be atomic
+ */
 static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream,
int cmd)
 {
 	struct aaci *aaci = substream->private_data;
@@ -719,11 +974,10 @@ static int __devinit aaci_probe_ac97(str
 	udelay(2);
 	writel(RESET_NRST, aaci->base + AACI_RESET);
 
-	/*
-	 * Give the AC'97 codec more than enough time
-	 * to wake up. (42us = ~2 frames at 48kHz.)
-	 */
-	udelay(42);
+ 	/*
+	 * Allow time for AC'97 codec response
+ 	 */
+	udelay(TIME_TO_RESPOND);
 
 	ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
 	if (ret)
@@ -789,6 +1043,18 @@ static struct aaci * __devinit aaci_init
 	aaci->card = card;
 	aaci->dev = dev;
 
+#ifdef CONFIG_ARM_AMBA_DMA 
+	
+# if defined(CONFIG_MACH_VERSATILE_AB) || defined
(CONFIG_ARCH_VERSATILE_PB)
+
+	/* Indicate no DMA channel in use */
+	aaci->playback.dma_chan = MAX_DMA_CHANNELS;
+ 	aaci->capture.dma_chan = MAX_DMA_CHANNELS;
+
+# endif
+
+#endif
+
 	/* Set MAINCR to allow slot 1 and 2 data IO */
 	aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN |
 		       MAINCR_SL2RXEN | MAINCR_SL2TXEN;
@@ -831,6 +1097,8 @@ static unsigned int __devinit aaci_size_
 	 * Re-initialise the AACI after the FIFO depth test, to
 	 * ensure that the FIFOs are empty.  Unfortunately, merely
 	 * disabling the channel doesn't clear the FIFO.
+	 * PrimeCell PL041 TRM:: "The FIFOs are flushed when the AACIfEN bit

+	 * in the AACIMAINCR is deasserted"
 	 */
 	writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
 	writel(aaci->maincr, aaci->base + AACI_MAINCR);
@@ -860,6 +1128,8 @@ static int __devinit aaci_probe(struct a
 		goto out;
 	}
 
+	aaci->fifo_irq_attached = 0;
+
 	aaci->base = ioremap(dev->res.start, SZ_4K);
 	if (!aaci->base) {
 		ret = -ENOMEM;
@@ -868,6 +1138,7 @@ static int __devinit aaci_probe(struct a
 
 	/*
 	 * Playback uses AACI channel 0
+	 * - known in the PrimeCell PL041 TRM as channel 1
 	 */
 	aaci->playback.base = aaci->base + AACI_CSCH1;
 	aaci->playback.fifo = aaci->base + AACI_DR1;
diff -purN arm_amba_pl080err/sound/arm/aaci.h arm_amba_aaci/sound/arm/aaci.h
--- arm_amba_pl080err/sound/arm/aaci.h	2006-10-17 13:29:53.000000000 +0100
+++ arm_amba_aaci/sound/arm/aaci.h	2006-10-18 08:35:36.000000000 +0100
@@ -12,7 +12,7 @@
 
 /*
  * Control and status register offsets
- *  P39.
+ * - see Programmer's Model in PrimeCell PL041 TRM - ARM DDI10137
  */
 #define AACI_CSCH1	0x000
 #define AACI_CSCH2	0x014
@@ -209,6 +209,10 @@ struct aaci_runtime {
 	u32			cr;
 	struct snd_pcm_substream	*substream;
 
+#ifdef CONFIG_ARM_AMBA_DMA 
+	dmach_t 		dma_chan;
+#endif
+
 	/*
 	 * PIO support
 	 */
@@ -223,8 +227,9 @@ struct aaci_runtime {
 struct aaci {
 	struct amba_device	*dev;
 	struct snd_card		*card;
-	void			__iomem *base;
+	void			__iomem *base;	/* AACI register base */
 	unsigned int		fifosize;
+	unsigned int		fifo_irq_attached;
 
 	/* AC'97 */
 	struct mutex		ac97_sem;
@@ -242,5 +247,16 @@ struct aaci {
 #define ACSTREAM_FRONT		0
 #define ACSTREAM_SURROUND	1
 #define ACSTREAM_LFE		2
+#define TIME_TO_RESPOND		42	/* 42us = ~2 frames at
48kHz. */
+					/* This should give the AC'97 codec
*/
+					/* sufficient time to respond */ 
+#ifdef CONFIG_ARM_AMBA_DMA 
+/* 
+ * DMA only possible on audio channel 1 
+ * - the first of four - see TRM
+ */
+# define AACI_INDEX_DMACHAN	0
+# define AACI_DMACHAN		1
+#endif
 
 #endif


-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux