diff options
author | Kevin Hilman <khilman@mvista.com> | 2007-02-07 05:45:32 +0100 |
---|---|---|
committer | Russell King <rmk+kernel@arm.linux.org.uk> | 2007-02-18 10:59:49 +0000 |
commit | 41762b8ca9e16c7443d8348ec53daddbe940cdcc (patch) | |
tree | bec07a1eac2a998d332c2f3870f85467d6550993 /sound/arm/aaci.c | |
parent | 62578cbfaa50df06b3bb6e4231adc3b911a3d4b4 (diff) |
[ARM] 4139/1: AACI record support
Add PCM audio capture support for AACI audio on Versatile platform.
Signed-off-by: Kevin Hilman <khilman@mvista.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
Diffstat (limited to 'sound/arm/aaci.c')
-rw-r--r-- | sound/arm/aaci.c | 258 |
1 files changed, 232 insertions, 26 deletions
diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index b85df793cdc..a032aee82ad 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -166,6 +166,65 @@ static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun) */ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) { + if (mask & ISR_ORINTR) { + dev_warn(&aaci->dev->dev, "RX overrun on chan %d\n", channel); + writel(ICLR_RXOEC1 << channel, aaci->base + AACI_INTCLR); + } + + if (mask & ISR_RXTOINTR) { + dev_warn(&aaci->dev->dev, "RX timeout on chan %d\n", channel); + writel(ICLR_RXTOFEC1 << channel, aaci->base + AACI_INTCLR); + } + + if (mask & ISR_RXINTR) { + struct aaci_runtime *aacirun = &aaci->capture; + void *ptr; + + if (!aacirun->substream || !aacirun->start) { + dev_warn(&aaci->dev->dev, "RX interrupt???"); + writel(0, aacirun->base + AACI_IE); + return; + } + ptr = aacirun->ptr; + + do { + unsigned int len = aacirun->fifosz; + u32 val; + + if (aacirun->bytes <= 0) { + aacirun->bytes += aacirun->period; + aacirun->ptr = ptr; + spin_unlock(&aaci->lock); + snd_pcm_period_elapsed(aacirun->substream); + spin_lock(&aaci->lock); + } + if (!(aacirun->cr & CR_EN)) + break; + + val = readl(aacirun->base + AACI_SR); + if (!(val & SR_RXHF)) + break; + if (!(val & SR_RXFF)) + len >>= 1; + + aacirun->bytes -= len; + + /* reading 16 bytes at a time */ + for( ; len > 0; len -= 16) { + asm( + "ldmia %1, {r0, r1, r2, r3}\n\t" + "stmia %0!, {r0, r1, r2, r3}" + : "+r" (ptr) + : "r" (aacirun->fifo) + : "r0", "r1", "r2", "r3", "cc"); + + if (ptr >= aacirun->end) + ptr = aacirun->start; + } + } while(1); + aacirun->ptr = ptr; + } + if (mask & ISR_URINTR) { dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel); writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR); @@ -193,7 +252,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) snd_pcm_period_elapsed(aacirun->substream); spin_lock(&aaci->lock); } - if (!(aacirun->cr & TXCR_TXEN)) + if (!(aacirun->cr & CR_EN)) break; val = readl(aacirun->base + AACI_SR); @@ -331,8 +390,9 @@ 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) +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; @@ -381,7 +441,7 @@ static int aaci_pcm_close(struct snd_pcm_substream *substream) struct aaci *aaci = substream->private_data; struct aaci_runtime *aacirun = substream->runtime->private_data; - WARN_ON(aacirun->cr & TXCR_TXEN); + WARN_ON(aacirun->cr & CR_EN); aacirun->substream = NULL; free_irq(aaci->dev->irq[0], aaci); @@ -396,7 +456,7 @@ static int aaci_pcm_hw_free(struct snd_pcm_substream *substream) /* * This must not be called with the device enabled. */ - WARN_ON(aacirun->cr & TXCR_TXEN); + WARN_ON(aacirun->cr & CR_EN); if (aacirun->pcm_open) snd_ac97_pcm_close(aacirun->pcm); @@ -423,9 +483,15 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto out; - err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params), - params_channels(params), - aacirun->pcm->r[0].slots); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params), + params_channels(params), + aacirun->pcm->r[0].slots); + else + err = snd_ac97_pcm_open(aacirun->pcm, params_rate(params), + params_channels(params), + aacirun->pcm->r[1].slots); + if (err) goto out; @@ -468,9 +534,9 @@ static int aaci_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_str * Playback specific ALSA stuff */ static const u32 channels_to_txmask[] = { - [2] = TXCR_TX3 | TXCR_TX4, - [4] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8, - [6] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8 | TXCR_TX6 | TXCR_TX9, + [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, }; /* @@ -505,7 +571,7 @@ aaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule) chan_mask); } -static int aaci_pcm_playback_open(struct snd_pcm_substream *substream) +static int aaci_pcm_open(struct snd_pcm_substream *substream) { struct aaci *aaci = substream->private_data; int ret; @@ -520,7 +586,12 @@ static int aaci_pcm_playback_open(struct snd_pcm_substream *substream) if (ret) return ret; - return aaci_pcm_open(aaci, substream, &aaci->playback); + 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, @@ -541,11 +612,11 @@ static int aaci_pcm_playback_hw_params(struct snd_pcm_substream *substream, * FIXME: double rate slots? */ if (ret >= 0) { - aacirun->cr = TXCR_FEN | TXCR_COMPACT | TXCR_TSZ16; + aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16; aacirun->cr |= channels_to_txmask[channels]; aacirun->fifosz = aaci->fifosize * 4; - if (aacirun->cr & TXCR_COMPACT) + if (aacirun->cr & CR_COMPACT) aacirun->fifosz >>= 1; } return ret; @@ -558,7 +629,7 @@ static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun) ie = readl(aacirun->base + AACI_IE); ie &= ~(IE_URIE|IE_TXIE); writel(ie, aacirun->base + AACI_IE); - aacirun->cr &= ~TXCR_TXEN; + aacirun->cr &= ~CR_EN; aaci_chan_wait_ready(aacirun); writel(aacirun->cr, aacirun->base + AACI_TXCR); } @@ -568,7 +639,7 @@ static void aaci_pcm_playback_start(struct aaci_runtime *aacirun) u32 ie; aaci_chan_wait_ready(aacirun); - aacirun->cr |= TXCR_TXEN; + aacirun->cr |= CR_EN; ie = readl(aacirun->base + AACI_IE); ie |= IE_URIE | IE_TXIE; @@ -616,7 +687,7 @@ static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cm } static struct snd_pcm_ops aaci_playback_ops = { - .open = aaci_pcm_playback_open, + .open = aaci_pcm_open, .close = aaci_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = aaci_pcm_playback_hw_params, @@ -627,7 +698,133 @@ static struct snd_pcm_ops aaci_playback_ops = { .mmap = aaci_pcm_mmap, }; +static int aaci_pcm_capture_hw_params(snd_pcm_substream_t *substream, + snd_pcm_hw_params_t *params) +{ + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun = substream->runtime->private_data; + int ret; + + ret = aaci_pcm_hw_params(substream, aacirun, params); + + if (ret >= 0) { + aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16; + + /* Line in record: slot 3 and 4 */ + aacirun->cr |= CR_SL3 | CR_SL4; + + aacirun->fifosz = aaci->fifosize * 4; + + if (aacirun->cr & CR_COMPACT) + aacirun->fifosz >>= 1; + } + return ret; +} + +static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun); + + ie = readl(aacirun->base + AACI_IE); + ie &= ~(IE_ORIE | IE_RXIE); + writel(ie, aacirun->base+AACI_IE); + + aacirun->cr &= ~CR_EN; + writel(aacirun->cr, aacirun->base + AACI_RXCR); +} + +static void aaci_pcm_capture_start(struct aaci_runtime *aacirun) +{ + u32 ie; + + aaci_chan_wait_ready(aacirun); + +#ifdef DEBUG + /* RX Timeout value: bits 28:17 in RXCR */ + aacirun->cr |= 0xf << 17; +#endif + + aacirun->cr |= CR_EN; + writel(aacirun->cr, aacirun->base + AACI_RXCR); + + ie = readl(aacirun->base + AACI_IE); + ie |= IE_ORIE |IE_RXIE; // overrun and rx interrupt -- half full + writel(ie, aacirun->base + AACI_IE); +} + +static int aaci_pcm_capture_trigger(snd_pcm_substream_t *substream, int cmd){ + + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&aaci->lock, flags); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + aaci_pcm_capture_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + aaci_pcm_capture_start(aacirun); + break; + + case SNDRV_PCM_TRIGGER_STOP: + aaci_pcm_capture_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + aaci_pcm_capture_stop(aacirun); + break; + + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + break; + + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + break; + + default: + ret = -EINVAL; + } + + spin_unlock_irqrestore(&aaci->lock, flags); + + return ret; +} + +static int aaci_pcm_capture_prepare(snd_pcm_substream_t *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct aaci *aaci = substream->private_data; + + aaci_pcm_prepare(substream); + + /* allow changing of sample rate */ + aaci_ac97_write(aaci->ac97, AC97_EXTENDED_STATUS, 0x0001); /* VRA */ + aaci_ac97_write(aaci->ac97, AC97_PCM_LR_ADC_RATE, runtime->rate); + aaci_ac97_write(aaci->ac97, AC97_PCM_MIC_ADC_RATE, runtime->rate); + + /* Record select: Mic: 0, Aux: 3, Line: 4 */ + aaci_ac97_write(aaci->ac97, AC97_REC_SEL, 0x0404); + + return 0; +} + +static snd_pcm_ops_t aaci_capture_ops = { + .open = aaci_pcm_open, + .close = aaci_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = aaci_pcm_capture_hw_params, + .hw_free = aaci_pcm_hw_free, + .prepare = aaci_pcm_capture_prepare, + .trigger = aaci_pcm_capture_trigger, + .pointer = aaci_pcm_pointer, + .mmap = aaci_pcm_mmap, +}; /* * Power Management. @@ -667,7 +864,7 @@ static int aaci_resume(struct amba_device *dev) static struct ac97_pcm ac97_defs[] __devinitdata = { - [0] = { /* Front PCM */ + [0] = { /* Front PCM */ .exclusive = 1, .r = { [0] = { @@ -741,6 +938,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci) ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97); if (ret) goto out; + aaci->ac97 = ac97; /* * Disable AC97 PC Beep input on audio codecs. @@ -753,6 +951,7 @@ static int __devinit aaci_probe_ac97(struct aaci *aaci) goto out; aaci->playback.pcm = &ac97_bus->pcms[0]; + aaci->capture.pcm = &ac97_bus->pcms[1]; out: return ret; @@ -802,7 +1001,7 @@ static int __devinit aaci_init_pcm(struct aaci *aaci) struct snd_pcm *pcm; int ret; - ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 0, &pcm); + ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm); if (ret == 0) { aaci->pcm = pcm; pcm->private_data = aaci; @@ -811,6 +1010,7 @@ static int __devinit aaci_init_pcm(struct aaci *aaci) strlcpy(pcm->name, DRIVER_NAME, sizeof(pcm->name)); snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &aaci_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops); } return ret; @@ -818,15 +1018,15 @@ static int __devinit aaci_init_pcm(struct aaci *aaci) static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) { - void __iomem *base = aaci->base + AACI_CSCH1; + struct aaci_runtime *aacirun = &aaci->playback; int i; - writel(TXCR_FEN | TXCR_TSZ16 | TXCR_TXEN, base + AACI_TXCR); + writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR); - for (i = 0; !(readl(base + AACI_SR) & SR_TXFF) && i < 4096; i++) - writel(0, aaci->base + AACI_DR1); + for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++) + writel(0, aacirun->fifo); - writel(0, base + AACI_TXCR); + writel(0, aacirun->base + AACI_TXCR); /* * Re-initialise the AACI after the FIFO depth test, to @@ -873,6 +1073,12 @@ static int __devinit aaci_probe(struct amba_device *dev, void *id) aaci->playback.base = aaci->base + AACI_CSCH1; aaci->playback.fifo = aaci->base + AACI_DR1; + /* + * Capture uses AACI channel 0 + */ + aaci->capture.base = aaci->base + AACI_CSCH1; + aaci->capture.fifo = aaci->base + AACI_DR1; + for (i = 0; i < 4; i++) { void __iomem *base = aaci->base + i * 0x14; @@ -908,7 +1114,7 @@ static int __devinit aaci_probe(struct amba_device *dev, void *id) ret = snd_card_register(aaci->card); if (ret == 0) { dev_info(&dev->dev, "%s, fifo %d\n", aaci->card->longname, - aaci->fifosize); + aaci->fifosize); amba_set_drvdata(dev, aaci->card); return ret; } |