📄 aaci.c
字号:
/* * linux/sound/arm/aaci.c - ARM PrimeCell AACI PL041 driver * * Copyright (C) 2003 Deep Blue Solutions Ltd, All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * Documentation: ARM DDI 0173B */#include <linux/module.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/ioport.h>#include <linux/device.h>#include <linux/spinlock.h>#include <linux/interrupt.h>#include <linux/err.h>#include <linux/amba/bus.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/sizes.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/ac97_codec.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include "aaci.h"#include "devdma.h"#define DRIVER_NAME "aaci-pl041"/* * PM support is not complete. Turn it off. */#undef CONFIG_PMstatic void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97){ u32 v, maincr = aaci->maincr | MAINCR_SCRA(ac97->num); /* * Ensure that the slot 1/2 RX registers are empty. */ v = readl(aaci->base + AACI_SLFR); if (v & SLFR_2RXV) readl(aaci->base + AACI_SL2RX); if (v & SLFR_1RXV) readl(aaci->base + AACI_SL1RX); writel(maincr, aaci->base + AACI_MAINCR);}/* * P29: * The recommended use of programming the external codec through slot 1 * and slot 2 data is to use the channels during setup routines and the * slot register at any other time. The data written into slot 1, slot 2 * and slot 12 registers is transmitted only when their corresponding * 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){ struct aaci *aaci = ac97->private_data; u32 v; int timeout = 5000; if (ac97->num >= 4) return; mutex_lock(&aaci->ac97_sem); aaci_ac97_select_codec(aaci, ac97); /* * P54: You must ensure that AACI_SL2TX is always written * to, if required, before data is written to AACI_SL1TX. */ writel(val << 4, aaci->base + AACI_SL2TX); writel(reg << 12, aaci->base + AACI_SL1TX); /* * Wait for the transmission of both slots to complete. */ do { v = readl(aaci->base + AACI_SLFR); } 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);}/* * Read an AC'97 register. */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; mutex_lock(&aaci->ac97_sem); aaci_ac97_select_codec(aaci, ac97); /* * Write the register address to slot 1. */ writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX); /* * Wait for the transmission to complete. */ do { v = readl(aaci->base + AACI_SLFR); } 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 * to respond. (42us = ~2 frames at 48kHz.) */ udelay(42); /* * 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)) && timeout--); 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;}static inline void aaci_chan_wait_ready(struct aaci_runtime *aacirun){ u32 val; int timeout = 5000; do { val = readl(aacirun->base + AACI_SR); } while (val & (SR_TXB|SR_RXB) && timeout--);}/* * Interrupt support. */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???\n"); 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); } if (mask & ISR_TXINTR) { struct aaci_runtime *aacirun = &aaci->playback; void *ptr; if (!aacirun->substream || !aacirun->start) { dev_warn(&aaci->dev->dev, "TX interrupt???\n"); 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_TXHE)) break; if (!(val & SR_TXFE)) len >>= 1; aacirun->bytes -= len; /* writing 16 bytes at a time */ for ( ; len > 0; len -= 16) { asm( "ldmia %0!, {r0, r1, r2, r3}\n\t" "stmia %1, {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; }}static irqreturn_t aaci_irq(int irq, void *devid){ struct aaci *aaci = devid; u32 mask; int i; spin_lock(&aaci->lock); mask = readl(aaci->base + AACI_ALLINTS); if (mask) { u32 m = mask; for (i = 0; i < 4; i++, m >>= 7) { if (m & 0x7f) { aaci_fifo_irq(aaci, i, m); } } } spin_unlock(&aaci->lock); return mask ? IRQ_HANDLED : IRQ_NONE;}/* * ALSA support. */struct aaci_stream { unsigned char codec_idx; unsigned char rate_idx;};static struct aaci_stream aaci_streams[] = { [ACSTREAM_FRONT] = { .codec_idx = 0, .rate_idx = AC97_RATES_FRONT_DAC, }, [ACSTREAM_SURROUND] = { .codec_idx = 0, .rate_idx = AC97_RATES_SURR_DAC, }, [ACSTREAM_LFE] = { .codec_idx = 0, .rate_idx = AC97_RATES_LFE_DAC, },};static inline unsigned int aaci_rate_mask(struct aaci *aaci, int streamid){ struct aaci_stream *s = aaci_streams + streamid; return aaci->ac97_bus->codec[s->codec_idx]->rates[s->rate_idx];}static unsigned int rate_list[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000};/* * Double-rate rule: we can support double rate iff channels == 2 * (unimplemented) */static intaaci_rule_rate_by_channels(struct snd_pcm_hw_params *p, struct snd_pcm_hw_rule *rule){ struct aaci *aaci = rule->private; unsigned int rate_mask = SNDRV_PCM_RATE_8000_48000|SNDRV_PCM_RATE_5512; struct snd_interval *c = hw_param_interval(p, SNDRV_PCM_HW_PARAM_CHANNELS); switch (c->max) { case 6: rate_mask &= aaci_rate_mask(aaci, ACSTREAM_LFE); case 4: rate_mask &= aaci_rate_mask(aaci, ACSTREAM_SURROUND); case 2: rate_mask &= aaci_rate_mask(aaci, ACSTREAM_FRONT); } return snd_interval_list(hw_param_interval(p, rule->var), ARRAY_SIZE(rate_list), rate_list, rate_mask);}static struct snd_pcm_hardware aaci_hw_info = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_RESUME, /* * ALSA doesn't support 18-bit or 20-bit packed into 32-bit * words. It also doesn't support 12-bit at all. */ .formats = SNDRV_PCM_FMTBIT_S16_LE, /* should this be continuous or knot? */ .rates = SNDRV_PCM_RATE_CONTINUOUS, .rate_max = 48000, .rate_min = 4000, .channels_min = 2, .channels_max = 6, .buffer_bytes_max = 64 * 1024, .period_bytes_min = 256, .period_bytes_max = PAGE_SIZE, .periods_min = 4, .periods_max = PAGE_SIZE / 16,};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; aacirun->substream = substream; runtime->private_data = aacirun; runtime->hw = aaci_hw_info; /* * 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. */ runtime->hw.fifo_size = aaci->fifosize * 2; /* * Add rule describing hardware rate dependency * on the number of channels. */ ret = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, 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;}/* * Common ALSA stuff */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 & CR_EN); aacirun->substream = NULL; free_irq(aaci->dev->irq[0], aaci); return 0;}static int aaci_pcm_hw_free(struct snd_pcm_substream *substream){ struct aaci_runtime *aacirun = substream->runtime->private_data; /* * This must not be called with the device enabled. */ WARN_ON(aacirun->cr & CR_EN); if (aacirun->pcm_open) snd_ac97_pcm_close(aacirun->pcm); aacirun->pcm_open = 0; /* * Clear out the DMA and any allocated buffers. */ devdma_hw_free(NULL, substream); return 0;}static int aaci_pcm_hw_params(struct snd_pcm_substream *substream, struct aaci_runtime *aacirun, struct snd_pcm_hw_params *params){ int err; aaci_pcm_hw_free(substream); err = devdma_hw_alloc(NULL, substream, params_buffer_bytes(params)); if (err < 0) goto out; 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; aacirun->pcm_open = 1; out: return err;}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->start = (void *)runtime->dma_area; aacirun->end = aacirun->start + runtime->dma_bytes; aacirun->ptr = aacirun->start; aacirun->period = aacirun->bytes = frames_to_bytes(runtime, runtime->period_size); return 0;}static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream){ struct snd_pcm_runtime *runtime = substream->runtime; struct aaci_runtime *aacirun = runtime->private_data; ssize_t bytes = aacirun->ptr - aacirun->start; return bytes_to_frames(runtime, bytes);}static int aaci_pcm_mmap(struct snd_pcm_substream *substream, struct vm_area_struct *vma){ return devdma_mmap(NULL, substream, vma);}/* * 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 intaaci_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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -