diff options
Diffstat (limited to 'sound/arm')
-rw-r--r-- | sound/arm/aaci.c | 285 | ||||
-rw-r--r-- | sound/arm/aaci.h | 9 |
2 files changed, 136 insertions, 158 deletions
diff --git a/sound/arm/aaci.c b/sound/arm/aaci.c index 7c1fc64..d0cead3 100644 --- a/sound/arm/aaci.c +++ b/sound/arm/aaci.c @@ -210,6 +210,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) if (mask & ISR_RXINTR) { struct aaci_runtime *aacirun = &aaci->capture; + bool period_elapsed = false; void *ptr; if (!aacirun->substream || !aacirun->start) { @@ -222,15 +223,12 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) ptr = aacirun->ptr; do { - unsigned int len = aacirun->fifosz; + unsigned int len = aacirun->fifo_bytes; u32 val; if (aacirun->bytes <= 0) { aacirun->bytes += aacirun->period; - aacirun->ptr = ptr; - spin_unlock(&aacirun->lock); - snd_pcm_period_elapsed(aacirun->substream); - spin_lock(&aacirun->lock); + period_elapsed = true; } if (!(aacirun->cr & CR_EN)) break; @@ -260,6 +258,9 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) aacirun->ptr = ptr; spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); } if (mask & ISR_URINTR) { @@ -269,6 +270,7 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) if (mask & ISR_TXINTR) { struct aaci_runtime *aacirun = &aaci->playback; + bool period_elapsed = false; void *ptr; if (!aacirun->substream || !aacirun->start) { @@ -281,15 +283,12 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) ptr = aacirun->ptr; do { - unsigned int len = aacirun->fifosz; + unsigned int len = aacirun->fifo_bytes; u32 val; if (aacirun->bytes <= 0) { aacirun->bytes += aacirun->period; - aacirun->ptr = ptr; - spin_unlock(&aacirun->lock); - snd_pcm_period_elapsed(aacirun->substream); - spin_lock(&aacirun->lock); + period_elapsed = true; } if (!(aacirun->cr & CR_EN)) break; @@ -319,6 +318,9 @@ static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask) aacirun->ptr = ptr; spin_unlock(&aacirun->lock); + + if (period_elapsed) + snd_pcm_period_elapsed(aacirun->substream); } } @@ -361,7 +363,7 @@ static struct snd_pcm_hardware aaci_hw_info = { /* rates are setup from the AC'97 codec */ .channels_min = 2, - .channels_max = 6, + .channels_max = 2, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 256, .period_bytes_max = PAGE_SIZE, @@ -369,12 +371,46 @@ 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) +/* + * We can support two and four channel audio. Unfortunately + * six channel audio requires a non-standard channel ordering: + * 2 -> FL(3), FR(4) + * 4 -> FL(3), FR(4), SL(7), SR(8) + * 6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required) + * FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual) + * This requires an ALSA configuration file to correct. + */ +static int aaci_rule_channels(struct snd_pcm_hw_params *p, + struct snd_pcm_hw_rule *rule) +{ + static unsigned int channel_list[] = { 2, 4, 6 }; + struct aaci *aaci = rule->private; + unsigned int mask = 1 << 0, slots; + + /* pcms[0] is the our 5.1 PCM instance. */ + slots = aaci->ac97_bus->pcms[0].r[0].slots; + if (slots & (1 << AC97_SLOT_PCM_SLEFT)) { + mask |= 1 << 1; + if (slots & (1 << AC97_SLOT_LFE)) + mask |= 1 << 2; + } + + return snd_interval_list(hw_param_interval(p, rule->var), + ARRAY_SIZE(channel_list), channel_list, mask); +} + +static int aaci_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; - int ret; + struct aaci *aaci = substream->private_data; + struct aaci_runtime *aacirun; + int ret = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + aacirun = &aaci->playback; + } else { + aacirun = &aaci->capture; + } aacirun->substream = substream; runtime->private_data = aacirun; @@ -382,27 +418,37 @@ static int __aaci_pcm_open(struct aaci *aaci, runtime->hw.rates = aacirun->pcm->rates; snd_pcm_limit_hw_rates(runtime); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && - aacirun->pcm->r[1].slots) - snd_ac97_pcm_double_rate_rules(runtime); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + runtime->hw.channels_max = 6; + + /* Add rule describing channel dependency. */ + ret = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + aaci_rule_channels, aaci, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (ret) + return ret; + + if (aacirun->pcm->r[1].slots) + snd_ac97_pcm_double_rate_rules(runtime); + } /* - * FIXME: ALSA specifies fifo_size in bytes. If we're in normal - * mode, each 32-bit word contains one sample. If we're in - * compact mode, each 32-bit word contains two samples, effectively - * halving the FIFO size. However, we don't know for sure which - * we'll be using at this point. We set this to the lower limit. + * ALSA wants the byte-size of the FIFOs. As we only support + * 16-bit samples, this is twice the FIFO depth irrespective + * of whether it's in compact mode or not. */ - runtime->hw.fifo_size = aaci->fifosize * 2; - - ret = request_irq(aaci->dev->irq[0], aaci_irq, IRQF_SHARED|IRQF_DISABLED, - DRIVER_NAME, aaci); - if (ret) - goto out; - - return 0; + runtime->hw.fifo_size = aaci->fifo_depth * 2; + + mutex_lock(&aaci->irq_lock); + if (!aaci->users++) { + ret = request_irq(aaci->dev->irq[0], aaci_irq, + IRQF_SHARED | IRQF_DISABLED, DRIVER_NAME, aaci); + if (ret != 0) + aaci->users--; + } + mutex_unlock(&aaci->irq_lock); - out: return ret; } @@ -418,7 +464,11 @@ static int aaci_pcm_close(struct snd_pcm_substream *substream) WARN_ON(aacirun->cr & CR_EN); aacirun->substream = NULL; - free_irq(aaci->dev->irq[0], aaci); + + mutex_lock(&aaci->irq_lock); + if (!--aaci->users) + free_irq(aaci->dev->irq[0], aaci); + mutex_unlock(&aaci->irq_lock); return 0; } @@ -444,12 +494,21 @@ static int aaci_pcm_hw_free(struct snd_pcm_substream *substream) return 0; } +/* Channel to slot mask */ +static const u32 channels_to_slotmask[] = { + [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, +}; + static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, - struct aaci_runtime *aacirun, struct snd_pcm_hw_params *params) { + struct aaci_runtime *aacirun = substream->runtime->private_data; + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + int dbl = rate > 48000; int err; - struct aaci *aaci = substream->private_data; aaci_pcm_hw_free(substream); if (aacirun->pcm_open) { @@ -457,22 +516,28 @@ static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, aacirun->pcm_open = 0; } + /* channels is already limited to 2, 4, or 6 by aaci_rule_channels */ + if (dbl && channels != 2) + return -EINVAL; + err = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); if (err >= 0) { - unsigned int rate = params_rate(params); - int dbl = rate > 48000; + struct aaci *aaci = substream->private_data; - err = snd_ac97_pcm_open(aacirun->pcm, rate, - params_channels(params), + err = snd_ac97_pcm_open(aacirun->pcm, rate, channels, aacirun->pcm->r[dbl].slots); aacirun->pcm_open = err == 0; aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16; - aacirun->fifosz = aaci->fifosize * 4; - - if (aacirun->cr & CR_COMPACT) - aacirun->fifosz >>= 1; + aacirun->cr |= channels_to_slotmask[channels + dbl * 2]; + + /* + * fifo_bytes is the number of bytes we transfer to/from + * the FIFO, including padding. So that's x4. As we're + * in compact mode, the FIFO is half the size. + */ + aacirun->fifo_bytes = aaci->fifo_depth * 4 / 2; } return err; @@ -483,11 +548,11 @@ static int aaci_pcm_prepare(struct snd_pcm_substream *substream) struct snd_pcm_runtime *runtime = substream->runtime; struct aaci_runtime *aacirun = runtime->private_data; + aacirun->period = snd_pcm_lib_period_bytes(substream); aacirun->start = runtime->dma_area; aacirun->end = aacirun->start + snd_pcm_lib_buffer_bytes(substream); aacirun->ptr = aacirun->start; - aacirun->period = - aacirun->bytes = frames_to_bytes(runtime, runtime->period_size); + aacirun->bytes = aacirun->period; return 0; } @@ -505,89 +570,6 @@ static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream) /* * Playback specific ALSA stuff */ -static const u32 channels_to_txmask[] = { - [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, -}; - -/* - * We can support two and four channel audio. Unfortunately - * six channel audio requires a non-standard channel ordering: - * 2 -> FL(3), FR(4) - * 4 -> FL(3), FR(4), SL(7), SR(8) - * 6 -> FL(3), FR(4), SL(7), SR(8), C(6), LFE(9) (required) - * FL(3), FR(4), C(6), SL(7), SR(8), LFE(9) (actual) - * This requires an ALSA configuration file to correct. - */ -static unsigned int channel_list[] = { 2, 4, 6 }; - -static int -aaci_rule_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule) -{ - struct aaci *aaci = rule->private; - unsigned int chan_mask = 1 << 0, slots; - - /* - * pcms[0] is the our 5.1 PCM instance. - */ - slots = aaci->ac97_bus->pcms[0].r[0].slots; - if (slots & (1 << AC97_SLOT_PCM_SLEFT)) { - chan_mask |= 1 << 1; - if (slots & (1 << AC97_SLOT_LFE)) - chan_mask |= 1 << 2; - } - - return snd_interval_list(hw_param_interval(p, rule->var), - ARRAY_SIZE(channel_list), channel_list, - chan_mask); -} - -static int aaci_pcm_open(struct snd_pcm_substream *substream) -{ - struct aaci *aaci = substream->private_data; - int ret; - - /* - * Add rule describing channel dependency. - */ - ret = snd_pcm_hw_rule_add(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_CHANNELS, - aaci_rule_channels, aaci, - SNDRV_PCM_HW_PARAM_CHANNELS, -1); - if (ret) - return ret; - - 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, - struct snd_pcm_hw_params *params) -{ - struct aaci_runtime *aacirun = substream->runtime->private_data; - unsigned int channels = params_channels(params); - int ret; - - WARN_ON(channels >= ARRAY_SIZE(channels_to_txmask) || - !channels_to_txmask[channels]); - - ret = aaci_pcm_hw_params(substream, aacirun, params); - - /* - * Enable FIFO, compact mode, 16 bits per sample. - * FIXME: double rate slots? - */ - if (ret >= 0) - aacirun->cr |= channels_to_txmask[channels]; - - return ret; -} - static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun) { u32 ie; @@ -657,27 +639,13 @@ static struct snd_pcm_ops aaci_playback_ops = { .open = aaci_pcm_open, .close = aaci_pcm_close, .ioctl = snd_pcm_lib_ioctl, - .hw_params = aaci_pcm_playback_hw_params, + .hw_params = aaci_pcm_hw_params, .hw_free = aaci_pcm_hw_free, .prepare = aaci_pcm_prepare, .trigger = aaci_pcm_playback_trigger, .pointer = aaci_pcm_pointer, }; -static int aaci_pcm_capture_hw_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *params) -{ - struct aaci_runtime *aacirun = substream->runtime->private_data; - int ret; - - ret = aaci_pcm_hw_params(substream, aacirun, params); - if (ret >= 0) - /* Line in record: slot 3 and 4 */ - aacirun->cr |= CR_SL3 | CR_SL4; - - return ret; -} - static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun) { u32 ie; @@ -774,7 +742,7 @@ static struct snd_pcm_ops aaci_capture_ops = { .open = aaci_pcm_open, .close = aaci_pcm_close, .ioctl = snd_pcm_lib_ioctl, - .hw_params = aaci_pcm_capture_hw_params, + .hw_params = aaci_pcm_hw_params, .hw_free = aaci_pcm_hw_free, .prepare = aaci_pcm_capture_prepare, .trigger = aaci_pcm_capture_trigger, @@ -941,12 +909,13 @@ static struct aaci * __devinit aaci_init_card(struct amba_device *dev) strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strlcpy(card->shortname, "ARM AC'97 Interface", sizeof(card->shortname)); snprintf(card->longname, sizeof(card->longname), - "%s at 0x%016llx, irq %d", - card->shortname, (unsigned long long)dev->res.start, - dev->irq[0]); + "%s PL%03x rev%u at 0x%08llx, irq %d", + card->shortname, amba_part(dev), amba_rev(dev), + (unsigned long long)dev->res.start, dev->irq[0]); aaci = card->private_data; mutex_init(&aaci->ac97_sem); + mutex_init(&aaci->irq_lock); aaci->card = card; aaci->dev = dev; @@ -984,6 +953,10 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) struct aaci_runtime *aacirun = &aaci->playback; int i; + /* + * Enable the channel, but don't assign it to any slots, so + * it won't empty onto the AC'97 link. + */ writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR); for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++) @@ -1002,7 +975,7 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) writel(aaci->maincr, aaci->base + AACI_MAINCR); /* - * If we hit 4096, we failed. Go back to the specified + * If we hit 4096 entries, we failed. Go back to the specified * fifo depth. */ if (i == 4096) @@ -1011,7 +984,8 @@ static unsigned int __devinit aaci_size_fifo(struct aaci *aaci) return i; } -static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id) +static int __devinit aaci_probe(struct amba_device *dev, + const struct amba_id *id) { struct aaci *aaci; int ret, i; @@ -1067,11 +1041,12 @@ static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id) /* * Size the FIFOs (must be multiple of 16). + * This is the number of entries in the FIFO. */ - aaci->fifosize = aaci_size_fifo(aaci); - if (aaci->fifosize & 15) { - printk(KERN_WARNING "AACI: fifosize = %d not supported\n", - aaci->fifosize); + aaci->fifo_depth = aaci_size_fifo(aaci); + if (aaci->fifo_depth & 15) { + printk(KERN_WARNING "AACI: FIFO depth %d not supported\n", + aaci->fifo_depth); ret = -ENODEV; goto out; } @@ -1084,8 +1059,8 @@ static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id) ret = snd_card_register(aaci->card); if (ret == 0) { - dev_info(&dev->dev, "%s, fifo %d\n", aaci->card->longname, - aaci->fifosize); + dev_info(&dev->dev, "%s\n", aaci->card->longname); + dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth); amba_set_drvdata(dev, aaci->card); return ret; } diff --git a/sound/arm/aaci.h b/sound/arm/aaci.h index 6a4a2ee..5791bd5 100644 --- a/sound/arm/aaci.h +++ b/sound/arm/aaci.h @@ -210,6 +210,8 @@ struct aaci_runtime { u32 cr; struct snd_pcm_substream *substream; + unsigned int period; /* byte size of a "period" */ + /* * PIO support */ @@ -217,15 +219,16 @@ struct aaci_runtime { void *end; void *ptr; int bytes; - unsigned int period; - unsigned int fifosz; + unsigned int fifo_bytes; }; struct aaci { struct amba_device *dev; struct snd_card *card; void __iomem *base; - unsigned int fifosize; + unsigned int fifo_depth; + unsigned int users; + struct mutex irq_lock; /* AC'97 */ struct mutex ac97_sem; |