lkml.org 
[lkml]   [2006]   [Oct]   [30]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC 6/7][PATCH] AMBA DMA: Patch to allow ARM PrimeCell Advanced Codec Interface to use AMBA DMA.
Date
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@arm.com>

---

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

\
 
 \ /
  Last update: 2006-10-30 13:09    [W:0.048 / U:0.068 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site