mirror of
https://github.com/adulau/aha.git
synced 2024-12-29 12:16:20 +00:00
29a4f2d31c
After a reboot on an ARM1176 which amounts to a softreset, it has been noted that the ALSA driver does not get registered and the probe fails with the error "aaci-pl041 fpga:04: ac97 read back fail". In the process of reading from a register the SL1TxBusy bit is set indicating that the transceiver is busy and remains so until the default timeout occurs. Set the Power down register 0x26 to an arbitrary value as specified in the PL041 manual (page: 3-18) so that AACISL1TX/AACISL2TX registers take their default state. Signed-off-by: Philby John <pjohn@in.mvista.com> Signed-off-by: Takashi Iwai <tiwai@suse.de>
1208 lines
27 KiB
C
1208 lines
27 KiB
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/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_PM
|
|
|
|
static 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 int
|
|
aaci_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 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 *aaci = substream->private_data;
|
|
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 = CR_FEN | CR_COMPACT | CR_SZ16;
|
|
aacirun->cr |= channels_to_txmask[channels];
|
|
|
|
aacirun->fifosz = aaci->fifosize * 4;
|
|
if (aacirun->cr & CR_COMPACT)
|
|
aacirun->fifosz >>= 1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void aaci_pcm_playback_stop(struct aaci_runtime *aacirun)
|
|
{
|
|
u32 ie;
|
|
|
|
ie = readl(aacirun->base + AACI_IE);
|
|
ie &= ~(IE_URIE|IE_TXIE);
|
|
writel(ie, aacirun->base + AACI_IE);
|
|
aacirun->cr &= ~CR_EN;
|
|
aaci_chan_wait_ready(aacirun);
|
|
writel(aacirun->cr, aacirun->base + AACI_TXCR);
|
|
}
|
|
|
|
static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
|
|
{
|
|
u32 ie;
|
|
|
|
aaci_chan_wait_ready(aacirun);
|
|
aacirun->cr |= CR_EN;
|
|
|
|
ie = readl(aacirun->base + AACI_IE);
|
|
ie |= IE_URIE | IE_TXIE;
|
|
writel(ie, aacirun->base + AACI_IE);
|
|
writel(aacirun->cr, aacirun->base + AACI_TXCR);
|
|
}
|
|
|
|
static int aaci_pcm_playback_trigger(struct snd_pcm_substream *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_playback_start(aacirun);
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
aaci_pcm_playback_start(aacirun);
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
aaci_pcm_playback_stop(aacirun);
|
|
break;
|
|
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
aaci_pcm_playback_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 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_free = aaci_pcm_hw_free,
|
|
.prepare = aaci_pcm_prepare,
|
|
.trigger = aaci_pcm_playback_trigger,
|
|
.pointer = aaci_pcm_pointer,
|
|
.mmap = aaci_pcm_mmap,
|
|
};
|
|
|
|
static int aaci_pcm_capture_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *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(struct snd_pcm_substream *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(struct snd_pcm_substream *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 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_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.
|
|
*/
|
|
#ifdef CONFIG_PM
|
|
static int aaci_do_suspend(struct snd_card *card, unsigned int state)
|
|
{
|
|
struct aaci *aaci = card->private_data;
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
|
|
snd_pcm_suspend_all(aaci->pcm);
|
|
return 0;
|
|
}
|
|
|
|
static int aaci_do_resume(struct snd_card *card, unsigned int state)
|
|
{
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
|
return 0;
|
|
}
|
|
|
|
static int aaci_suspend(struct amba_device *dev, pm_message_t state)
|
|
{
|
|
struct snd_card *card = amba_get_drvdata(dev);
|
|
return card ? aaci_do_suspend(card) : 0;
|
|
}
|
|
|
|
static int aaci_resume(struct amba_device *dev)
|
|
{
|
|
struct snd_card *card = amba_get_drvdata(dev);
|
|
return card ? aaci_do_resume(card) : 0;
|
|
}
|
|
#else
|
|
#define aaci_do_suspend NULL
|
|
#define aaci_do_resume NULL
|
|
#define aaci_suspend NULL
|
|
#define aaci_resume NULL
|
|
#endif
|
|
|
|
|
|
static struct ac97_pcm ac97_defs[] __devinitdata = {
|
|
[0] = { /* Front PCM */
|
|
.exclusive = 1,
|
|
.r = {
|
|
[0] = {
|
|
.slots = (1 << AC97_SLOT_PCM_LEFT) |
|
|
(1 << AC97_SLOT_PCM_RIGHT) |
|
|
(1 << AC97_SLOT_PCM_CENTER) |
|
|
(1 << AC97_SLOT_PCM_SLEFT) |
|
|
(1 << AC97_SLOT_PCM_SRIGHT) |
|
|
(1 << AC97_SLOT_LFE),
|
|
},
|
|
},
|
|
},
|
|
[1] = { /* PCM in */
|
|
.stream = 1,
|
|
.exclusive = 1,
|
|
.r = {
|
|
[0] = {
|
|
.slots = (1 << AC97_SLOT_PCM_LEFT) |
|
|
(1 << AC97_SLOT_PCM_RIGHT),
|
|
},
|
|
},
|
|
},
|
|
[2] = { /* Mic in */
|
|
.stream = 1,
|
|
.exclusive = 1,
|
|
.r = {
|
|
[0] = {
|
|
.slots = (1 << AC97_SLOT_MIC),
|
|
},
|
|
},
|
|
}
|
|
};
|
|
|
|
static struct snd_ac97_bus_ops aaci_bus_ops = {
|
|
.write = aaci_ac97_write,
|
|
.read = aaci_ac97_read,
|
|
};
|
|
|
|
static int __devinit aaci_probe_ac97(struct aaci *aaci)
|
|
{
|
|
struct snd_ac97_template ac97_template;
|
|
struct snd_ac97_bus *ac97_bus;
|
|
struct snd_ac97 *ac97;
|
|
int ret;
|
|
|
|
writel(0, aaci->base + AC97_POWERDOWN);
|
|
/*
|
|
* Assert AACIRESET for 2us
|
|
*/
|
|
writel(0, aaci->base + AACI_RESET);
|
|
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);
|
|
|
|
ret = snd_ac97_bus(aaci->card, 0, &aaci_bus_ops, aaci, &ac97_bus);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ac97_bus->clock = 48000;
|
|
aaci->ac97_bus = ac97_bus;
|
|
|
|
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
|
|
ac97_template.private_data = aaci;
|
|
ac97_template.num = 0;
|
|
ac97_template.scaps = AC97_SCAP_SKIP_MODEM;
|
|
|
|
ret = snd_ac97_mixer(ac97_bus, &ac97_template, &ac97);
|
|
if (ret)
|
|
goto out;
|
|
aaci->ac97 = ac97;
|
|
|
|
/*
|
|
* Disable AC97 PC Beep input on audio codecs.
|
|
*/
|
|
if (ac97_is_audio(ac97))
|
|
snd_ac97_write_cache(ac97, AC97_PC_BEEP, 0x801e);
|
|
|
|
ret = snd_ac97_pcm_assign(ac97_bus, ARRAY_SIZE(ac97_defs), ac97_defs);
|
|
if (ret)
|
|
goto out;
|
|
|
|
aaci->playback.pcm = &ac97_bus->pcms[0];
|
|
aaci->capture.pcm = &ac97_bus->pcms[1];
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void aaci_free_card(struct snd_card *card)
|
|
{
|
|
struct aaci *aaci = card->private_data;
|
|
if (aaci->base)
|
|
iounmap(aaci->base);
|
|
}
|
|
|
|
static struct aaci * __devinit aaci_init_card(struct amba_device *dev)
|
|
{
|
|
struct aaci *aaci;
|
|
struct snd_card *card;
|
|
int err;
|
|
|
|
err = snd_card_create(SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
|
THIS_MODULE, sizeof(struct aaci), &card);
|
|
if (err < 0)
|
|
return NULL;
|
|
|
|
card->private_free = aaci_free_card;
|
|
|
|
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]);
|
|
|
|
aaci = card->private_data;
|
|
mutex_init(&aaci->ac97_sem);
|
|
spin_lock_init(&aaci->lock);
|
|
aaci->card = card;
|
|
aaci->dev = dev;
|
|
|
|
/* Set MAINCR to allow slot 1 and 2 data IO */
|
|
aaci->maincr = MAINCR_IE | MAINCR_SL1RXEN | MAINCR_SL1TXEN |
|
|
MAINCR_SL2RXEN | MAINCR_SL2TXEN;
|
|
|
|
return aaci;
|
|
}
|
|
|
|
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, 1, &pcm);
|
|
if (ret == 0) {
|
|
aaci->pcm = pcm;
|
|
pcm->private_data = aaci;
|
|
pcm->info_flags = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
static unsigned int __devinit aaci_size_fifo(struct aaci *aaci)
|
|
{
|
|
struct aaci_runtime *aacirun = &aaci->playback;
|
|
int i;
|
|
|
|
writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);
|
|
|
|
for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
|
|
writel(0, aacirun->fifo);
|
|
|
|
writel(0, aacirun->base + AACI_TXCR);
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
writel(aaci->maincr & ~MAINCR_IE, aaci->base + AACI_MAINCR);
|
|
writel(aaci->maincr, aaci->base + AACI_MAINCR);
|
|
|
|
/*
|
|
* If we hit 4096, we failed. Go back to the specified
|
|
* fifo depth.
|
|
*/
|
|
if (i == 4096)
|
|
i = 8;
|
|
|
|
return i;
|
|
}
|
|
|
|
static int __devinit aaci_probe(struct amba_device *dev, struct amba_id *id)
|
|
{
|
|
struct aaci *aaci;
|
|
int ret, i;
|
|
|
|
ret = amba_request_regions(dev, NULL);
|
|
if (ret)
|
|
return ret;
|
|
|
|
aaci = aaci_init_card(dev);
|
|
if (!aaci) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
aaci->base = ioremap(dev->res.start, resource_size(&dev->res));
|
|
if (!aaci->base) {
|
|
ret = -ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Playback uses AACI channel 0
|
|
*/
|
|
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;
|
|
|
|
writel(0, base + AACI_IE);
|
|
writel(0, base + AACI_TXCR);
|
|
writel(0, base + AACI_RXCR);
|
|
}
|
|
|
|
writel(0x1fff, aaci->base + AACI_INTCLR);
|
|
writel(aaci->maincr, aaci->base + AACI_MAINCR);
|
|
|
|
ret = aaci_probe_ac97(aaci);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/*
|
|
* Size the FIFOs (must be multiple of 16).
|
|
*/
|
|
aaci->fifosize = aaci_size_fifo(aaci);
|
|
if (aaci->fifosize & 15) {
|
|
printk(KERN_WARNING "AACI: fifosize = %d not supported\n",
|
|
aaci->fifosize);
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
ret = aaci_init_pcm(aaci);
|
|
if (ret)
|
|
goto out;
|
|
|
|
snd_card_set_dev(aaci->card, &dev->dev);
|
|
|
|
ret = snd_card_register(aaci->card);
|
|
if (ret == 0) {
|
|
dev_info(&dev->dev, "%s, fifo %d\n", aaci->card->longname,
|
|
aaci->fifosize);
|
|
amba_set_drvdata(dev, aaci->card);
|
|
return ret;
|
|
}
|
|
|
|
out:
|
|
if (aaci)
|
|
snd_card_free(aaci->card);
|
|
amba_release_regions(dev);
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit aaci_remove(struct amba_device *dev)
|
|
{
|
|
struct snd_card *card = amba_get_drvdata(dev);
|
|
|
|
amba_set_drvdata(dev, NULL);
|
|
|
|
if (card) {
|
|
struct aaci *aaci = card->private_data;
|
|
writel(0, aaci->base + AACI_MAINCR);
|
|
|
|
snd_card_free(card);
|
|
amba_release_regions(dev);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct amba_id aaci_ids[] = {
|
|
{
|
|
.id = 0x00041041,
|
|
.mask = 0x000fffff,
|
|
},
|
|
{ 0, 0 },
|
|
};
|
|
|
|
static struct amba_driver aaci_driver = {
|
|
.drv = {
|
|
.name = DRIVER_NAME,
|
|
},
|
|
.probe = aaci_probe,
|
|
.remove = __devexit_p(aaci_remove),
|
|
.suspend = aaci_suspend,
|
|
.resume = aaci_resume,
|
|
.id_table = aaci_ids,
|
|
};
|
|
|
|
static int __init aaci_init(void)
|
|
{
|
|
return amba_driver_register(&aaci_driver);
|
|
}
|
|
|
|
static void __exit aaci_exit(void)
|
|
{
|
|
amba_driver_unregister(&aaci_driver);
|
|
}
|
|
|
|
module_init(aaci_init);
|
|
module_exit(aaci_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver");
|