2005-05-12 20:04:59 +08:00
|
|
|
/*
|
|
|
|
* 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>
|
2006-01-07 21:52:45 +08:00
|
|
|
#include <linux/amba/bus.h>
|
2009-11-23 16:44:10 +08:00
|
|
|
#include <linux/io.h>
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
#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"
|
|
|
|
|
|
|
|
#define DRIVER_NAME "aaci-pl041"
|
|
|
|
|
2011-01-13 07:42:57 +08:00
|
|
|
#define FRAME_PERIOD_US 21
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
/*
|
|
|
|
* PM support is not complete. Turn it off.
|
|
|
|
*/
|
|
|
|
#undef CONFIG_PM
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static void aaci_ac97_select_codec(struct aaci *aaci, struct snd_ac97 *ac97)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
|
2011-02-05 18:41:55 +08:00
|
|
|
if (maincr != readl(aaci->base + AACI_MAINCR)) {
|
|
|
|
writel(maincr, aaci->base + AACI_MAINCR);
|
|
|
|
readl(aaci->base + AACI_MAINCR);
|
|
|
|
udelay(1);
|
|
|
|
}
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2007-02-07 12:46:47 +08:00
|
|
|
static void aaci_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
|
|
|
|
unsigned short val)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = ac97->private_data;
|
2011-01-13 07:42:57 +08:00
|
|
|
int timeout;
|
2005-05-12 20:04:59 +08:00
|
|
|
u32 v;
|
|
|
|
|
|
|
|
if (ac97->num >= 4)
|
|
|
|
return;
|
|
|
|
|
2006-01-16 23:36:05 +08:00
|
|
|
mutex_lock(&aaci->ac97_sem);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2011-01-13 07:42:57 +08:00
|
|
|
/* Initially, wait one frame period */
|
|
|
|
udelay(FRAME_PERIOD_US);
|
|
|
|
|
|
|
|
/* And then wait an additional eight frame periods for it to be sent */
|
|
|
|
timeout = FRAME_PERIOD_US * 8;
|
2005-05-12 20:04:59 +08:00
|
|
|
do {
|
2011-01-13 07:42:57 +08:00
|
|
|
udelay(1);
|
2005-05-12 20:04:59 +08:00
|
|
|
v = readl(aaci->base + AACI_SLFR);
|
2009-02-08 22:22:25 +08:00
|
|
|
} while ((v & (SLFR_1TXB|SLFR_2TXB)) && --timeout);
|
2007-02-07 12:46:47 +08:00
|
|
|
|
2011-01-13 07:17:24 +08:00
|
|
|
if (v & (SLFR_1TXB|SLFR_2TXB))
|
2007-02-07 12:46:47 +08:00
|
|
|
dev_err(&aaci->dev->dev,
|
|
|
|
"timeout waiting for write to complete\n");
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2006-01-16 23:36:05 +08:00
|
|
|
mutex_unlock(&aaci->ac97_sem);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Read an AC'97 register.
|
|
|
|
*/
|
2005-11-17 22:10:16 +08:00
|
|
|
static unsigned short aaci_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = ac97->private_data;
|
2011-01-13 07:42:57 +08:00
|
|
|
int timeout, retries = 10;
|
2005-05-12 20:04:59 +08:00
|
|
|
u32 v;
|
|
|
|
|
|
|
|
if (ac97->num >= 4)
|
|
|
|
return ~0;
|
|
|
|
|
2006-01-16 23:36:05 +08:00
|
|
|
mutex_lock(&aaci->ac97_sem);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
aaci_ac97_select_codec(aaci, ac97);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Write the register address to slot 1.
|
|
|
|
*/
|
|
|
|
writel((reg << 12) | (1 << 19), aaci->base + AACI_SL1TX);
|
|
|
|
|
2011-01-13 07:42:57 +08:00
|
|
|
/* Initially, wait one frame period */
|
|
|
|
udelay(FRAME_PERIOD_US);
|
|
|
|
|
|
|
|
/* And then wait an additional eight frame periods for it to be sent */
|
|
|
|
timeout = FRAME_PERIOD_US * 8;
|
2005-05-12 20:04:59 +08:00
|
|
|
do {
|
2011-01-13 07:42:57 +08:00
|
|
|
udelay(1);
|
2005-05-12 20:04:59 +08:00
|
|
|
v = readl(aaci->base + AACI_SLFR);
|
2009-02-08 22:22:25 +08:00
|
|
|
} while ((v & SLFR_1TXB) && --timeout);
|
2007-02-07 12:46:47 +08:00
|
|
|
|
2011-01-13 07:17:24 +08:00
|
|
|
if (v & SLFR_1TXB) {
|
2007-02-07 12:46:47 +08:00
|
|
|
dev_err(&aaci->dev->dev, "timeout on slot 1 TX busy\n");
|
|
|
|
v = ~0;
|
|
|
|
goto out;
|
|
|
|
}
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-13 07:42:57 +08:00
|
|
|
/* Now wait for the response frame */
|
|
|
|
udelay(FRAME_PERIOD_US);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-13 07:42:57 +08:00
|
|
|
/* And then wait an additional eight frame periods for data */
|
|
|
|
timeout = FRAME_PERIOD_US * 8;
|
2005-05-12 20:04:59 +08:00
|
|
|
do {
|
2011-01-13 07:42:57 +08:00
|
|
|
udelay(1);
|
2005-05-12 20:04:59 +08:00
|
|
|
cond_resched();
|
|
|
|
v = readl(aaci->base + AACI_SLFR) & (SLFR_1RXV|SLFR_2RXV);
|
2009-02-08 22:22:25 +08:00
|
|
|
} while ((v != (SLFR_1RXV|SLFR_2RXV)) && --timeout);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-13 07:17:24 +08:00
|
|
|
if (v != (SLFR_1RXV|SLFR_2RXV)) {
|
2007-02-07 12:46:47 +08:00
|
|
|
dev_err(&aaci->dev->dev, "timeout on RX valid\n");
|
2005-05-12 20:04:59 +08:00
|
|
|
v = ~0;
|
2007-02-07 12:46:47 +08:00
|
|
|
goto out;
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
2007-02-07 12:46:47 +08:00
|
|
|
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:
|
2006-01-16 23:36:05 +08:00
|
|
|
mutex_unlock(&aaci->ac97_sem);
|
2005-05-12 20:04:59 +08:00
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
static inline void
|
|
|
|
aaci_chan_wait_ready(struct aaci_runtime *aacirun, unsigned long mask)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
u32 val;
|
|
|
|
int timeout = 5000;
|
|
|
|
|
|
|
|
do {
|
2011-01-13 07:42:57 +08:00
|
|
|
udelay(1);
|
2005-05-12 20:04:59 +08:00
|
|
|
val = readl(aacirun->base + AACI_SR);
|
2009-12-19 01:48:50 +08:00
|
|
|
} while (val & mask && timeout--);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interrupt support.
|
|
|
|
*/
|
2007-02-07 12:41:37 +08:00
|
|
|
static void aaci_fifo_irq(struct aaci *aaci, int channel, u32 mask)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2007-02-07 12:45:32 +08:00
|
|
|
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;
|
2011-01-13 16:47:35 +08:00
|
|
|
bool period_elapsed = false;
|
2007-02-07 12:45:32 +08:00
|
|
|
void *ptr;
|
|
|
|
|
|
|
|
if (!aacirun->substream || !aacirun->start) {
|
2007-10-18 18:06:30 +08:00
|
|
|
dev_warn(&aaci->dev->dev, "RX interrupt???\n");
|
2007-02-07 12:45:32 +08:00
|
|
|
writel(0, aacirun->base + AACI_IE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock(&aacirun->lock);
|
|
|
|
|
|
|
|
ptr = aacirun->ptr;
|
2007-02-07 12:45:32 +08:00
|
|
|
do {
|
2011-01-14 06:25:10 +08:00
|
|
|
unsigned int len = aacirun->fifo_bytes;
|
2007-02-07 12:45:32 +08:00
|
|
|
u32 val;
|
|
|
|
|
|
|
|
if (aacirun->bytes <= 0) {
|
|
|
|
aacirun->bytes += aacirun->period;
|
2011-01-13 16:47:35 +08:00
|
|
|
period_elapsed = true;
|
2007-02-07 12:45:32 +08:00
|
|
|
}
|
|
|
|
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);
|
2009-12-19 01:48:50 +08:00
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
aacirun->ptr = ptr;
|
2009-12-19 01:48:50 +08:00
|
|
|
|
|
|
|
spin_unlock(&aacirun->lock);
|
2011-01-13 16:47:35 +08:00
|
|
|
|
|
|
|
if (period_elapsed)
|
|
|
|
snd_pcm_period_elapsed(aacirun->substream);
|
2007-02-07 12:45:32 +08:00
|
|
|
}
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
if (mask & ISR_URINTR) {
|
2007-02-07 12:41:37 +08:00
|
|
|
dev_dbg(&aaci->dev->dev, "TX underrun on chan %d\n", channel);
|
|
|
|
writel(ICLR_TXUEC1 << channel, aaci->base + AACI_INTCLR);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mask & ISR_TXINTR) {
|
|
|
|
struct aaci_runtime *aacirun = &aaci->playback;
|
2011-01-13 16:47:35 +08:00
|
|
|
bool period_elapsed = false;
|
2005-05-12 20:04:59 +08:00
|
|
|
void *ptr;
|
|
|
|
|
|
|
|
if (!aacirun->substream || !aacirun->start) {
|
2007-10-18 18:06:30 +08:00
|
|
|
dev_warn(&aaci->dev->dev, "TX interrupt???\n");
|
2005-05-12 20:04:59 +08:00
|
|
|
writel(0, aacirun->base + AACI_IE);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock(&aacirun->lock);
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
ptr = aacirun->ptr;
|
|
|
|
do {
|
2011-01-14 06:25:10 +08:00
|
|
|
unsigned int len = aacirun->fifo_bytes;
|
2005-05-12 20:04:59 +08:00
|
|
|
u32 val;
|
|
|
|
|
|
|
|
if (aacirun->bytes <= 0) {
|
|
|
|
aacirun->bytes += aacirun->period;
|
2011-01-13 16:47:35 +08:00
|
|
|
period_elapsed = true;
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
2007-02-07 12:45:32 +08:00
|
|
|
if (!(aacirun->cr & CR_EN))
|
2005-05-12 20:04:59 +08:00
|
|
|
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;
|
2009-12-19 01:48:50 +08:00
|
|
|
|
|
|
|
spin_unlock(&aacirun->lock);
|
2011-01-13 16:47:35 +08:00
|
|
|
|
|
|
|
if (period_elapsed)
|
|
|
|
snd_pcm_period_elapsed(aacirun->substream);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
IRQ: Maintain regs pointer globally rather than passing to IRQ handlers
Maintain a per-CPU global "struct pt_regs *" variable which can be used instead
of passing regs around manually through all ~1800 interrupt handlers in the
Linux kernel.
The regs pointer is used in few places, but it potentially costs both stack
space and code to pass it around. On the FRV arch, removing the regs parameter
from all the genirq function results in a 20% speed up of the IRQ exit path
(ie: from leaving timer_interrupt() to leaving do_IRQ()).
Where appropriate, an arch may override the generic storage facility and do
something different with the variable. On FRV, for instance, the address is
maintained in GR28 at all times inside the kernel as part of general exception
handling.
Having looked over the code, it appears that the parameter may be handed down
through up to twenty or so layers of functions. Consider a USB character
device attached to a USB hub, attached to a USB controller that posts its
interrupts through a cascaded auxiliary interrupt controller. A character
device driver may want to pass regs to the sysrq handler through the input
layer which adds another few layers of parameter passing.
I've build this code with allyesconfig for x86_64 and i386. I've runtested the
main part of the code on FRV and i386, though I can't test most of the drivers.
I've also done partial conversion for powerpc and MIPS - these at least compile
with minimal configurations.
This will affect all archs. Mostly the changes should be relatively easy.
Take do_IRQ(), store the regs pointer at the beginning, saving the old one:
struct pt_regs *old_regs = set_irq_regs(regs);
And put the old one back at the end:
set_irq_regs(old_regs);
Don't pass regs through to generic_handle_irq() or __do_IRQ().
In timer_interrupt(), this sort of change will be necessary:
- update_process_times(user_mode(regs));
- profile_tick(CPU_PROFILING, regs);
+ update_process_times(user_mode(get_irq_regs()));
+ profile_tick(CPU_PROFILING);
I'd like to move update_process_times()'s use of get_irq_regs() into itself,
except that i386, alone of the archs, uses something other than user_mode().
Some notes on the interrupt handling in the drivers:
(*) input_dev() is now gone entirely. The regs pointer is no longer stored in
the input_dev struct.
(*) finish_unlinks() in drivers/usb/host/ohci-q.c needs checking. It does
something different depending on whether it's been supplied with a regs
pointer or not.
(*) Various IRQ handler function pointers have been moved to type
irq_handler_t.
Signed-Off-By: David Howells <dhowells@redhat.com>
(cherry picked from 1b16e7ac850969f38b375e511e3fa2f474a33867 commit)
2006-10-05 21:55:46 +08:00
|
|
|
static irqreturn_t aaci_irq(int irq, void *devid)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = devid;
|
|
|
|
u32 mask;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mask = readl(aaci->base + AACI_ALLINTS);
|
|
|
|
if (mask) {
|
|
|
|
u32 m = mask;
|
|
|
|
for (i = 0; i < 4; i++, m >>= 7) {
|
|
|
|
if (m & 0x7f) {
|
2007-02-07 12:41:37 +08:00
|
|
|
aaci_fifo_irq(aaci, i, m);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return mask ? IRQ_HANDLED : IRQ_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ALSA support.
|
|
|
|
*/
|
2017-08-17 17:15:49 +08:00
|
|
|
static const struct snd_pcm_hardware aaci_hw_info = {
|
2005-05-12 20:04:59 +08:00
|
|
|
.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,
|
|
|
|
|
2009-12-19 01:48:35 +08:00
|
|
|
/* rates are setup from the AC'97 codec */
|
2005-05-12 20:04:59 +08:00
|
|
|
.channels_min = 2,
|
2011-01-13 18:13:17 +08:00
|
|
|
.channels_max = 2,
|
2005-05-12 20:04:59 +08:00
|
|
|
.buffer_bytes_max = 64 * 1024,
|
|
|
|
.period_bytes_min = 256,
|
|
|
|
.period_bytes_max = PAGE_SIZE,
|
|
|
|
.periods_min = 4,
|
|
|
|
.periods_max = PAGE_SIZE / 16,
|
|
|
|
};
|
|
|
|
|
2011-01-13 18:13:17 +08:00
|
|
|
/*
|
|
|
|
* 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)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2011-01-13 18:13:17 +08:00
|
|
|
struct aaci *aaci = substream->private_data;
|
|
|
|
struct aaci_runtime *aacirun;
|
2011-01-25 23:52:33 +08:00
|
|
|
int ret = 0;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-13 18:13:17 +08:00
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
aacirun = &aaci->playback;
|
|
|
|
} else {
|
|
|
|
aacirun = &aaci->capture;
|
|
|
|
}
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
aacirun->substream = substream;
|
|
|
|
runtime->private_data = aacirun;
|
|
|
|
runtime->hw = aaci_hw_info;
|
2009-12-19 01:48:35 +08:00
|
|
|
runtime->hw.rates = aacirun->pcm->rates;
|
|
|
|
snd_pcm_limit_hw_rates(runtime);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-13 18:13:17 +08:00
|
|
|
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);
|
|
|
|
}
|
2009-12-19 01:48:45 +08:00
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
/*
|
2011-01-14 06:25:10 +08:00
|
|
|
* 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.
|
2005-05-12 20:04:59 +08:00
|
|
|
*/
|
2011-01-14 06:25:10 +08:00
|
|
|
runtime->hw.fifo_size = aaci->fifo_depth * 2;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-25 23:52:33 +08:00
|
|
|
mutex_lock(&aaci->irq_lock);
|
|
|
|
if (!aaci->users++) {
|
|
|
|
ret = request_irq(aaci->dev->irq[0], aaci_irq,
|
2011-09-22 16:59:20 +08:00
|
|
|
IRQF_SHARED, DRIVER_NAME, aaci);
|
2011-01-25 23:52:33 +08:00
|
|
|
if (ret != 0)
|
|
|
|
aaci->users--;
|
|
|
|
}
|
|
|
|
mutex_unlock(&aaci->irq_lock);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Common ALSA stuff
|
|
|
|
*/
|
2005-11-17 22:10:16 +08:00
|
|
|
static int aaci_pcm_close(struct snd_pcm_substream *substream)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = substream->private_data;
|
|
|
|
struct aaci_runtime *aacirun = substream->runtime->private_data;
|
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
WARN_ON(aacirun->cr & CR_EN);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
aacirun->substream = NULL;
|
2011-01-25 23:52:33 +08:00
|
|
|
|
|
|
|
mutex_lock(&aaci->irq_lock);
|
|
|
|
if (!--aaci->users)
|
|
|
|
free_irq(aaci->dev->irq[0], aaci);
|
|
|
|
mutex_unlock(&aaci->irq_lock);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static int aaci_pcm_hw_free(struct snd_pcm_substream *substream)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci_runtime *aacirun = substream->runtime->private_data;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This must not be called with the device enabled.
|
|
|
|
*/
|
2007-02-07 12:45:32 +08:00
|
|
|
WARN_ON(aacirun->cr & CR_EN);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
if (aacirun->pcm_open)
|
|
|
|
snd_ac97_pcm_close(aacirun->pcm);
|
|
|
|
aacirun->pcm_open = 0;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Clear out the DMA and any allocated buffers.
|
|
|
|
*/
|
2009-11-26 22:08:54 +08:00
|
|
|
snd_pcm_lib_free_pages(substream);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-01-27 00:59:39 +08:00
|
|
|
/* 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,
|
|
|
|
};
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static int aaci_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2011-01-27 00:59:39 +08:00
|
|
|
struct aaci_runtime *aacirun = substream->runtime->private_data;
|
|
|
|
unsigned int channels = params_channels(params);
|
|
|
|
unsigned int rate = params_rate(params);
|
|
|
|
int dbl = rate > 48000;
|
2005-05-12 20:04:59 +08:00
|
|
|
int err;
|
|
|
|
|
|
|
|
aaci_pcm_hw_free(substream);
|
2009-11-30 00:39:52 +08:00
|
|
|
if (aacirun->pcm_open) {
|
|
|
|
snd_ac97_pcm_close(aacirun->pcm);
|
|
|
|
aacirun->pcm_open = 0;
|
|
|
|
}
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2011-01-27 00:59:39 +08:00
|
|
|
/* channels is already limited to 2, 4, or 6 by aaci_rule_channels */
|
|
|
|
if (dbl && channels != 2)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2009-11-26 22:08:54 +08:00
|
|
|
err = snd_pcm_lib_malloc_pages(substream,
|
|
|
|
params_buffer_bytes(params));
|
2009-12-19 01:48:37 +08:00
|
|
|
if (err >= 0) {
|
2011-01-27 00:59:39 +08:00
|
|
|
struct aaci *aaci = substream->private_data;
|
2009-12-19 01:48:45 +08:00
|
|
|
|
2011-01-27 00:59:39 +08:00
|
|
|
err = snd_ac97_pcm_open(aacirun->pcm, rate, channels,
|
2009-12-19 01:48:45 +08:00
|
|
|
aacirun->pcm->r[dbl].slots);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2009-12-19 01:48:37 +08:00
|
|
|
aacirun->pcm_open = err == 0;
|
2009-12-19 01:48:40 +08:00
|
|
|
aacirun->cr = CR_FEN | CR_COMPACT | CR_SZ16;
|
2011-01-27 00:59:39 +08:00
|
|
|
aacirun->cr |= channels_to_slotmask[channels + dbl * 2];
|
2009-12-19 01:48:40 +08:00
|
|
|
|
2011-01-14 06:25:10 +08:00
|
|
|
/*
|
|
|
|
* 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;
|
2009-12-19 01:48:37 +08:00
|
|
|
}
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static int aaci_pcm_prepare(struct snd_pcm_substream *substream)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-05-12 20:04:59 +08:00
|
|
|
struct aaci_runtime *aacirun = runtime->private_data;
|
|
|
|
|
2011-01-13 08:34:08 +08:00
|
|
|
aacirun->period = snd_pcm_lib_period_bytes(substream);
|
2009-12-19 01:48:37 +08:00
|
|
|
aacirun->start = runtime->dma_area;
|
2009-11-23 16:44:10 +08:00
|
|
|
aacirun->end = aacirun->start + snd_pcm_lib_buffer_bytes(substream);
|
2005-05-12 20:04:59 +08:00
|
|
|
aacirun->ptr = aacirun->start;
|
2011-01-13 08:34:08 +08:00
|
|
|
aacirun->bytes = aacirun->period;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static snd_pcm_uframes_t aaci_pcm_pointer(struct snd_pcm_substream *substream)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
2005-05-12 20:04:59 +08:00
|
|
|
struct aaci_runtime *aacirun = runtime->private_data;
|
|
|
|
ssize_t bytes = aacirun->ptr - aacirun->start;
|
|
|
|
|
|
|
|
return bytes_to_frames(runtime, bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Playback specific ALSA stuff
|
|
|
|
*/
|
|
|
|
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);
|
2007-02-07 12:45:32 +08:00
|
|
|
aacirun->cr &= ~CR_EN;
|
2009-12-19 01:48:50 +08:00
|
|
|
aaci_chan_wait_ready(aacirun, SR_TXB);
|
2005-05-12 20:04:59 +08:00
|
|
|
writel(aacirun->cr, aacirun->base + AACI_TXCR);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aaci_pcm_playback_start(struct aaci_runtime *aacirun)
|
|
|
|
{
|
|
|
|
u32 ie;
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
aaci_chan_wait_ready(aacirun, SR_TXB);
|
2007-02-07 12:45:32 +08:00
|
|
|
aacirun->cr |= CR_EN;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
ie = readl(aacirun->base + AACI_IE);
|
|
|
|
ie |= IE_URIE | IE_TXIE;
|
|
|
|
writel(ie, aacirun->base + AACI_IE);
|
|
|
|
writel(aacirun->cr, aacirun->base + AACI_TXCR);
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static int aaci_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci_runtime *aacirun = substream->runtime->private_data;
|
|
|
|
unsigned long flags;
|
|
|
|
int ret = 0;
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock_irqsave(&aacirun->lock, flags);
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
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;
|
|
|
|
}
|
2009-12-19 01:48:50 +08:00
|
|
|
|
|
|
|
spin_unlock_irqrestore(&aacirun->lock, flags);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-08-18 15:45:10 +08:00
|
|
|
static const struct snd_pcm_ops aaci_playback_ops = {
|
2007-02-07 12:45:32 +08:00
|
|
|
.open = aaci_pcm_open,
|
2005-05-12 20:04:59 +08:00
|
|
|
.close = aaci_pcm_close,
|
|
|
|
.ioctl = snd_pcm_lib_ioctl,
|
2011-01-27 00:59:39 +08:00
|
|
|
.hw_params = aaci_pcm_hw_params,
|
2005-05-12 20:04:59 +08:00
|
|
|
.hw_free = aaci_pcm_hw_free,
|
|
|
|
.prepare = aaci_pcm_prepare,
|
|
|
|
.trigger = aaci_pcm_playback_trigger,
|
|
|
|
.pointer = aaci_pcm_pointer,
|
|
|
|
};
|
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
static void aaci_pcm_capture_stop(struct aaci_runtime *aacirun)
|
|
|
|
{
|
|
|
|
u32 ie;
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
aaci_chan_wait_ready(aacirun, SR_RXB);
|
2007-02-07 12:45:32 +08:00
|
|
|
|
|
|
|
ie = readl(aacirun->base + AACI_IE);
|
|
|
|
ie &= ~(IE_ORIE | IE_RXIE);
|
|
|
|
writel(ie, aacirun->base+AACI_IE);
|
|
|
|
|
|
|
|
aacirun->cr &= ~CR_EN;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
writel(aacirun->cr, aacirun->base + AACI_RXCR);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void aaci_pcm_capture_start(struct aaci_runtime *aacirun)
|
|
|
|
{
|
|
|
|
u32 ie;
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
aaci_chan_wait_ready(aacirun, SR_RXB);
|
2007-02-07 12:45:32 +08:00
|
|
|
|
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2007-02-20 23:44:23 +08:00
|
|
|
static int aaci_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
|
|
|
|
{
|
2007-02-07 12:45:32 +08:00
|
|
|
struct aaci_runtime *aacirun = substream->runtime->private_data;
|
|
|
|
unsigned long flags;
|
|
|
|
int ret = 0;
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock_irqsave(&aacirun->lock, flags);
|
2007-02-07 12:45:32 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_unlock_irqrestore(&aacirun->lock, flags);
|
2007-02-07 12:45:32 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-02-20 23:44:23 +08:00
|
|
|
static int aaci_pcm_capture_prepare(struct snd_pcm_substream *substream)
|
2007-02-07 12:45:32 +08:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-08-18 15:45:10 +08:00
|
|
|
static const struct snd_pcm_ops aaci_capture_ops = {
|
2007-02-07 12:45:32 +08:00
|
|
|
.open = aaci_pcm_open,
|
|
|
|
.close = aaci_pcm_close,
|
|
|
|
.ioctl = snd_pcm_lib_ioctl,
|
2011-01-27 00:59:39 +08:00
|
|
|
.hw_params = aaci_pcm_hw_params,
|
2007-02-07 12:45:32 +08:00
|
|
|
.hw_free = aaci_pcm_hw_free,
|
|
|
|
.prepare = aaci_pcm_capture_prepare,
|
|
|
|
.trigger = aaci_pcm_capture_trigger,
|
|
|
|
.pointer = aaci_pcm_pointer,
|
|
|
|
};
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Power Management.
|
|
|
|
*/
|
|
|
|
#ifdef CONFIG_PM
|
2013-12-03 18:04:26 +08:00
|
|
|
static int aaci_do_suspend(struct snd_card *card)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = card->private_data;
|
2005-11-18 00:19:25 +08:00
|
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
|
2005-05-12 20:04:59 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-12-03 18:04:26 +08:00
|
|
|
static int aaci_do_resume(struct snd_card *card)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-18 00:19:25 +08:00
|
|
|
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
|
2005-05-12 20:04:59 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-12-03 18:04:26 +08:00
|
|
|
static int aaci_suspend(struct device *dev)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2013-12-03 18:04:26 +08:00
|
|
|
struct snd_card *card = dev_get_drvdata(dev);
|
2005-05-12 20:04:59 +08:00
|
|
|
return card ? aaci_do_suspend(card) : 0;
|
|
|
|
}
|
|
|
|
|
2013-12-03 18:04:26 +08:00
|
|
|
static int aaci_resume(struct device *dev)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2013-12-03 18:04:26 +08:00
|
|
|
struct snd_card *card = dev_get_drvdata(dev);
|
2005-05-12 20:04:59 +08:00
|
|
|
return card ? aaci_do_resume(card) : 0;
|
|
|
|
}
|
2013-12-03 18:04:26 +08:00
|
|
|
|
|
|
|
static SIMPLE_DEV_PM_OPS(aaci_dev_pm_ops, aaci_suspend, aaci_resume);
|
|
|
|
#define AACI_DEV_PM_OPS (&aaci_dev_pm_ops)
|
2005-05-12 20:04:59 +08:00
|
|
|
#else
|
2013-12-03 18:04:26 +08:00
|
|
|
#define AACI_DEV_PM_OPS NULL
|
2005-05-12 20:04:59 +08:00
|
|
|
#endif
|
|
|
|
|
|
|
|
|
2017-08-23 20:09:25 +08:00
|
|
|
static const struct ac97_pcm ac97_defs[] = {
|
2007-02-07 12:45:32 +08:00
|
|
|
[0] = { /* Front PCM */
|
2005-05-12 20:04:59 +08:00
|
|
|
.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),
|
|
|
|
},
|
2009-12-19 01:48:45 +08:00
|
|
|
[1] = {
|
|
|
|
.slots = (1 << AC97_SLOT_PCM_LEFT) |
|
|
|
|
(1 << AC97_SLOT_PCM_RIGHT) |
|
|
|
|
(1 << AC97_SLOT_PCM_LEFT_0) |
|
|
|
|
(1 << AC97_SLOT_PCM_RIGHT_0),
|
|
|
|
},
|
2005-05-12 20:04:59 +08:00
|
|
|
},
|
|
|
|
},
|
|
|
|
[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),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static struct snd_ac97_bus_ops aaci_bus_ops = {
|
2005-05-12 20:04:59 +08:00
|
|
|
.write = aaci_ac97_write,
|
|
|
|
.read = aaci_ac97_read,
|
|
|
|
};
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static int aaci_probe_ac97(struct aaci *aaci)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_ac97_template ac97_template;
|
|
|
|
struct snd_ac97_bus *ac97_bus;
|
|
|
|
struct snd_ac97 *ac97;
|
2005-05-12 20:04:59 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.)
|
|
|
|
*/
|
2011-01-13 07:42:57 +08:00
|
|
|
udelay(FRAME_PERIOD_US * 2);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
memset(&ac97_template, 0, sizeof(struct snd_ac97_template));
|
2005-05-12 20:04:59 +08:00
|
|
|
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;
|
2007-02-07 12:45:32 +08:00
|
|
|
aaci->ac97 = ac97;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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];
|
2007-02-07 12:45:32 +08:00
|
|
|
aaci->capture.pcm = &ac97_bus->pcms[1];
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
out:
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:10:16 +08:00
|
|
|
static void aaci_free_card(struct snd_card *card)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci = card->private_data;
|
2015-01-04 05:55:54 +08:00
|
|
|
|
|
|
|
iounmap(aaci->base);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static struct aaci *aaci_init_card(struct amba_device *dev)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci;
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_card *card;
|
2008-12-28 23:45:02 +08:00
|
|
|
int err;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2014-01-29 21:25:18 +08:00
|
|
|
err = snd_card_new(&dev->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
|
|
|
|
THIS_MODULE, sizeof(struct aaci), &card);
|
2008-12-28 23:45:02 +08:00
|
|
|
if (err < 0)
|
2008-09-01 21:31:50 +08:00
|
|
|
return NULL;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
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),
|
2011-01-13 07:46:03 +08:00
|
|
|
"%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]);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
aaci = card->private_data;
|
2006-01-16 23:36:05 +08:00
|
|
|
mutex_init(&aaci->ac97_sem);
|
2011-01-25 23:52:33 +08:00
|
|
|
mutex_init(&aaci->irq_lock);
|
2005-05-12 20:04:59 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static int aaci_init_pcm(struct aaci *aaci)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_pcm *pcm;
|
2005-05-12 20:04:59 +08:00
|
|
|
int ret;
|
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
ret = snd_pcm_new(aaci->card, "AACI AC'97", 0, 1, 1, &pcm);
|
2005-05-12 20:04:59 +08:00
|
|
|
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);
|
2007-02-07 12:45:32 +08:00
|
|
|
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &aaci_capture_ops);
|
2009-11-26 22:08:54 +08:00
|
|
|
snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
|
2019-02-04 21:26:27 +08:00
|
|
|
aaci->card->dev,
|
|
|
|
0, 64 * 1024);
|
2005-05-12 20:04:59 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static unsigned int aaci_size_fifo(struct aaci *aaci)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2007-02-07 12:45:32 +08:00
|
|
|
struct aaci_runtime *aacirun = &aaci->playback;
|
2005-05-12 20:04:59 +08:00
|
|
|
int i;
|
|
|
|
|
2011-01-14 06:25:10 +08:00
|
|
|
/*
|
|
|
|
* Enable the channel, but don't assign it to any slots, so
|
|
|
|
* it won't empty onto the AC'97 link.
|
|
|
|
*/
|
2007-02-07 12:45:32 +08:00
|
|
|
writel(CR_FEN | CR_SZ16 | CR_EN, aacirun->base + AACI_TXCR);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
for (i = 0; !(readl(aacirun->base + AACI_SR) & SR_TXFF) && i < 4096; i++)
|
|
|
|
writel(0, aacirun->fifo);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
writel(0, aacirun->base + AACI_TXCR);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2011-02-05 18:41:55 +08:00
|
|
|
readl(aaci->base + AACI_MAINCR);
|
|
|
|
udelay(1);
|
2005-05-12 20:04:59 +08:00
|
|
|
writel(aaci->maincr, aaci->base + AACI_MAINCR);
|
|
|
|
|
|
|
|
/*
|
2011-01-14 06:25:10 +08:00
|
|
|
* If we hit 4096 entries, we failed. Go back to the specified
|
2005-05-12 20:04:59 +08:00
|
|
|
* fifo depth.
|
|
|
|
*/
|
|
|
|
if (i == 4096)
|
|
|
|
i = 8;
|
|
|
|
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static int aaci_probe(struct amba_device *dev,
|
|
|
|
const struct amba_id *id)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
|
|
|
struct aaci *aaci;
|
|
|
|
int ret, i;
|
|
|
|
|
|
|
|
ret = amba_request_regions(dev, NULL);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
aaci = aaci_init_card(dev);
|
2008-09-01 21:31:50 +08:00
|
|
|
if (!aaci) {
|
|
|
|
ret = -ENOMEM;
|
2005-05-12 20:04:59 +08:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2009-06-08 06:27:31 +08:00
|
|
|
aaci->base = ioremap(dev->res.start, resource_size(&dev->res));
|
2005-05-12 20:04:59 +08:00
|
|
|
if (!aaci->base) {
|
|
|
|
ret = -ENOMEM;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Playback uses AACI channel 0
|
|
|
|
*/
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock_init(&aaci->playback.lock);
|
2005-05-12 20:04:59 +08:00
|
|
|
aaci->playback.base = aaci->base + AACI_CSCH1;
|
|
|
|
aaci->playback.fifo = aaci->base + AACI_DR1;
|
|
|
|
|
2007-02-07 12:45:32 +08:00
|
|
|
/*
|
|
|
|
* Capture uses AACI channel 0
|
|
|
|
*/
|
2009-12-19 01:48:50 +08:00
|
|
|
spin_lock_init(&aaci->capture.lock);
|
2007-02-07 12:45:32 +08:00
|
|
|
aaci->capture.base = aaci->base + AACI_CSCH1;
|
|
|
|
aaci->capture.fifo = aaci->base + AACI_DR1;
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
for (i = 0; i < 4; i++) {
|
2005-09-06 09:06:57 +08:00
|
|
|
void __iomem *base = aaci->base + i * 0x14;
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
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);
|
2010-03-27 00:07:51 +08:00
|
|
|
/*
|
|
|
|
* Fix: ac97 read back fail errors by reading
|
|
|
|
* from any arbitrary aaci register.
|
|
|
|
*/
|
|
|
|
readl(aaci->base + AACI_CSCH1);
|
2006-02-02 03:25:58 +08:00
|
|
|
ret = aaci_probe_ac97(aaci);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
/*
|
2006-02-02 03:25:58 +08:00
|
|
|
* Size the FIFOs (must be multiple of 16).
|
2011-01-14 06:25:10 +08:00
|
|
|
* This is the number of entries in the FIFO.
|
2005-05-12 20:04:59 +08:00
|
|
|
*/
|
2011-01-14 06:25:10 +08:00
|
|
|
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);
|
2006-02-02 03:25:58 +08:00
|
|
|
ret = -ENODEV;
|
2005-05-12 20:04:59 +08:00
|
|
|
goto out;
|
2006-02-02 03:25:58 +08:00
|
|
|
}
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
ret = aaci_init_pcm(aaci);
|
|
|
|
if (ret)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = snd_card_register(aaci->card);
|
|
|
|
if (ret == 0) {
|
2011-01-14 06:25:10 +08:00
|
|
|
dev_info(&dev->dev, "%s\n", aaci->card->longname);
|
|
|
|
dev_info(&dev->dev, "FIFO %u entries\n", aaci->fifo_depth);
|
2005-05-12 20:04:59 +08:00
|
|
|
amba_set_drvdata(dev, aaci->card);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
if (aaci)
|
|
|
|
snd_card_free(aaci->card);
|
|
|
|
amba_release_regions(dev);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-12-07 01:35:11 +08:00
|
|
|
static int aaci_remove(struct amba_device *dev)
|
2005-05-12 20:04:59 +08:00
|
|
|
{
|
2005-11-17 22:10:16 +08:00
|
|
|
struct snd_card *card = amba_get_drvdata(dev);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
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 },
|
|
|
|
};
|
|
|
|
|
2011-10-05 22:50:36 +08:00
|
|
|
MODULE_DEVICE_TABLE(amba, aaci_ids);
|
|
|
|
|
2005-05-12 20:04:59 +08:00
|
|
|
static struct amba_driver aaci_driver = {
|
|
|
|
.drv = {
|
|
|
|
.name = DRIVER_NAME,
|
2013-12-03 18:04:26 +08:00
|
|
|
.pm = AACI_DEV_PM_OPS,
|
2005-05-12 20:04:59 +08:00
|
|
|
},
|
|
|
|
.probe = aaci_probe,
|
2012-12-07 01:35:11 +08:00
|
|
|
.remove = aaci_remove,
|
2005-05-12 20:04:59 +08:00
|
|
|
.id_table = aaci_ids,
|
|
|
|
};
|
|
|
|
|
2012-03-15 17:40:38 +08:00
|
|
|
module_amba_driver(aaci_driver);
|
2005-05-12 20:04:59 +08:00
|
|
|
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("ARM PrimeCell PL041 Advanced Audio CODEC Interface driver");
|