diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-19 13:18:39 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-19 13:18:39 -0800 |
commit | 59b8175c771040afcd4ad67022b0cc80c216b866 (patch) | |
tree | 4ef5935bee1e342716d49b9d4b99e3fa835526e6 /sound/arm/aaci.c | |
parent | 920841d8d1d61bc12b43f95a579a5374f6d98f81 (diff) | |
parent | 3b0eb4a195a124567cd0dd6f700f8388def542c6 (diff) |
Merge branch 'for-linus' of master.kernel.org:/home/rmk/linux-2.6-arm
* 'for-linus' of master.kernel.org:/home/rmk/linux-2.6-arm: (117 commits)
[ARM] 4058/2: iop32x: set ->broken_parity_status on n2100 onboard r8169 ports
[ARM] 4140/1: AACI stability add ac97 timeout and retries
[ARM] 4139/1: AACI record support
[ARM] 4138/1: AACI: multiple channel support for IRQ handling
[ARM] 4211/1: Provide a defconfig for ns9xxx
[ARM] 4210/1: base for new machine type "NetSilicon NS9360"
[ARM] 4222/1: S3C2443: Remove reference to missing S3C2443_PM
[ARM] 4221/1: S3C2443: DMA support
[ARM] 4220/1: S3C24XX: DMA system initialised from sysdev
[ARM] 4219/1: S3C2443: DMA source definitions
[ARM] 4218/1: S3C2412: fix CONFIG_CPU_S3C2412_ONLY wrt to S3C2443
[ARM] 4217/1: S3C24XX: remove the dma channel show at startup
[ARM] 4090/2: avoid clash between PXA and SA1111 defines
[ARM] 4216/1: add .gitignore entries for ARM specific files
[ARM] 4214/2: S3C2410: Add Armzone QT2410
[ARM] 4215/1: s3c2410 usb device: per-platform vbus_draw
[ARM] 4213/1: S3C2410 - Update definition of ADCTSC_XY_PST
[ARM] 4098/1: ARM: rtc_lock only used with rtc_cmos
[ARM] 4137/1: Add kexec support
[ARM] 4201/1: SMP barriers pair needed for the secondary boot process
...
Fix up conflict due to typedef removal in sound/arm/aaci.h
Diffstat (limited to 'sound/arm/aaci.c')
-rw-r--r-- | sound/arm/aaci.c | 315 |
1 files changed, 275 insertions, 40 deletions
diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index 53675cf4de4..5190d7acdb9 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -65,10 +65,12 @@ static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97) * SI1TxEn, SI2TxEn and SI12TxEn bits are set in the AACI_MAINCR * register. */ -static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) +static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) { struct aaci *aaci = ac97->private_data; u32 v; + int timeout = 5000; if (ac97->num >= 4) return; @@ -89,7 +91,11 @@ static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned */ do { v = readl(aaci->base + AACI_SLFR); - } while (v & (SLFR_1TXB|SLFR_2TXB)); + } while ((v & (SLFR_1TXB|SLFR_2TXB)) && timeout--); + + if (!timeout) + dev_err(&aaci->dev->dev, + "timeout waiting for write to complete\n"); mutex_unlock(&aaci->ac97_sem); } @@ -101,6 +107,8 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { struct aaci *aaci = ac97->private_data; u32 v; + int timeout = 5000; + int retries = 10; if (ac97->num >= 4) return ~0; @@ -119,7 +127,13 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) */ do { v = readl(aaci->base + AACI_SLFR); - } while (v & SLFR_1TXB); + } while ((v & SLFR_1TXB) && timeout--); + + if (!timeout) { + dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n"); + v = ~0; + goto out; + } /* * Give the AC'97 codec more than enough time @@ -130,21 +144,35 @@ static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg) /* * Wait for slot 2 to indicate data. */ + timeout = 5000; do { cond_resched(); v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV); - } while (v != (SLFR_1RXV|SLFR_2RXV)); + } while ((v != (SLFR_1RXV|SLFR_2RXV)) && timeout--); - v = readl(aaci->base + AACI_SL1RX) >> 12; - if (v == reg) { - v = readl(aaci->base + AACI_SL2RX) >> 4; - } else { - dev_err(&aaci->dev->dev, - "wrong ac97 register read back (%x != %x)\n", - v, reg); + if (!timeout) { + dev_err(&aaci->dev->dev, "timeout on RX valid\n"); v = ~0; + goto out; } + do { + v = readl(aaci->base + AACI_SL1RX) >> 12; + if (v == reg) { + v = readl(aaci->base + AACI_SL2RX) >> 4; + break; + } else if (--retries) { + dev_warn(&aaci->dev->dev, + "ac97 read back fail. retry\n"); + continue; + } else { + dev_warn(&aaci->dev->dev, + "wrong ac97 register read back (%x != %x)\n", + v, reg); + v = ~0; + } + } while (retries); + out: mutex_unlock(&aaci->ac97_sem); return v; } @@ -164,10 +192,70 @@ static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun) /* * Interrupt support. */ -static void aaci_fifo_irq(struct aaci *aaci, u32 mask) +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) { - writel(ICLR_TXUEC1, aaci->base + AACI_INTCLR); + dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel); + writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR); } if (mask & ISR_TXINTR) { @@ -192,7 +280,7 @@ static void aaci_fifo_irq(struct aaci *aaci, 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); @@ -233,7 +321,7 @@ static irqreturn_t aaci_irq(int irq, void *devid) u32 m = mask; for (i = 0; i < 4; i++, m >>= 7) { if (m & 0x7f) { - aaci_fifo_irq(aaci, m); + aaci_fifo_irq(aaci, i, m); } } } @@ -330,8 +418,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; @@ -380,7 +469,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); @@ -395,7 +484,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); @@ -422,9 +511,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; @@ -467,9 +562,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, }; /* @@ -504,7 +599,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; @@ -519,7 +614,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, @@ -540,11 +640,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; @@ -557,7 +657,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); } @@ -567,7 +667,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; @@ -615,7 +715,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, @@ -626,7 +726,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. @@ -666,7 +892,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] = { @@ -740,6 +966,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. @@ -752,6 +979,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; @@ -801,7 +1029,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; @@ -810,6 +1038,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; @@ -817,15 +1046,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 @@ -872,6 +1101,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; @@ -907,7 +1142,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; } |