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

From: Peter Pearse
Date: Mon Oct 30 2006 - 07:05:54 EST


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 <peter.pearse@xxxxxxx>

---

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 majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/