2005-05-12 16:04:59 +04: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 0173 B
*/
# 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 <asm/io.h>
# include <asm/irq.h>
2005-10-28 17:05:16 +04:00
# include <asm/sizes.h>
2005-05-12 16:04:59 +04:00
# include <asm/hardware/amba.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_PM
2005-11-17 17:10:16 +03:00
static void aaci_ac97_select_codec ( struct aaci * aaci , struct snd_ac97 * ac97 )
2005-05-12 16:04:59 +04: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 ) ;
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 .
*/
2005-11-17 17:10:16 +03:00
static void aaci_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg , unsigned short val )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = ac97 - > private_data ;
u32 v ;
if ( ac97 - > num > = 4 )
return ;
down ( & 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 ) ) ;
up ( & aaci - > ac97_sem ) ;
}
/*
* Read an AC ' 97 register .
*/
2005-11-17 17:10:16 +03:00
static unsigned short aaci_ac97_read ( struct snd_ac97 * ac97 , unsigned short reg )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = ac97 - > private_data ;
u32 v ;
if ( ac97 - > num > = 4 )
return ~ 0 ;
down ( & 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 ) ;
/*
* Give the AC ' 97 codec more than enough time
* to respond . ( 42u s = ~ 2 frames at 48 kHz . )
*/
udelay ( 42 ) ;
/*
* Wait for slot 2 to indicate data .
*/
do {
cond_resched ( ) ;
v = readl ( aaci - > base + AACI_SLFR ) & ( SLFR_1RXV | SLFR_2RXV ) ;
} while ( v ! = ( SLFR_1RXV | SLFR_2RXV ) ) ;
v = readl ( aaci - > base + AACI_SL1RX ) > > 12 ;
if ( v = = reg ) {
v = readl ( aaci - > base + AACI_SL2RX ) > > 4 ;
} else {
dev_err ( & aaci - > dev - > dev ,
" wrong ac97 register read back (%x != %x) \n " ,
v , reg ) ;
v = ~ 0 ;
}
up ( & 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 , u32 mask )
{
if ( mask & ISR_URINTR ) {
writel ( ICLR_TXUEC1 , 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??? " ) ;
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 & TXCR_TXEN ) )
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 pt_regs * regs )
{
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 , 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
2005-11-17 17:10:16 +03:00
aaci_rule_rate_by_channels ( struct snd_pcm_hw_params * p , struct snd_pcm_hw_rule * rule )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = rule - > private ;
unsigned int rate_mask = SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_5512 ;
2005-11-17 17:10:16 +03:00
struct snd_interval * c = hw_param_interval ( p , SNDRV_PCM_HW_PARAM_CHANNELS ) ;
2005-05-12 16:04:59 +04:00
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 ) ;
}
2005-11-17 17:10:16 +03:00
static struct snd_pcm_hardware aaci_hw_info = {
2005-05-12 16:04:59 +04: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 ,
/* 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 ,
} ;
2005-11-17 17:10:16 +03:00
static int aaci_pcm_open ( struct aaci * aaci , struct snd_pcm_substream * substream ,
2005-05-12 16:04:59 +04:00
struct aaci_runtime * aacirun )
{
2005-11-17 17:10:16 +03:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2005-05-12 16:04:59 +04:00
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 , SA_SHIRQ | SA_INTERRUPT ,
DRIVER_NAME , aaci ) ;
if ( ret )
goto out ;
return 0 ;
out :
return ret ;
}
/*
* Common ALSA stuff
*/
2005-11-17 17:10:16 +03:00
static int aaci_pcm_close ( struct snd_pcm_substream * substream )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = substream - > private_data ;
struct aaci_runtime * aacirun = substream - > runtime - > private_data ;
WARN_ON ( aacirun - > cr & TXCR_TXEN ) ;
aacirun - > substream = NULL ;
free_irq ( aaci - > dev - > irq [ 0 ] , aaci ) ;
return 0 ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_hw_free ( struct snd_pcm_substream * substream )
2005-05-12 16:04:59 +04:00
{
struct aaci_runtime * aacirun = substream - > runtime - > private_data ;
/*
* This must not be called with the device enabled .
*/
WARN_ON ( aacirun - > cr & TXCR_TXEN ) ;
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 ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_hw_params ( struct snd_pcm_substream * substream ,
2005-05-12 16:04:59 +04:00
struct aaci_runtime * aacirun ,
2005-11-17 17:10:16 +03:00
struct snd_pcm_hw_params * params )
2005-05-12 16:04:59 +04:00
{
int err ;
aaci_pcm_hw_free ( substream ) ;
err = devdma_hw_alloc ( NULL , substream ,
params_buffer_bytes ( params ) ) ;
if ( err < 0 )
goto out ;
err = snd_ac97_pcm_open ( aacirun - > pcm , params_rate ( params ) ,
params_channels ( params ) ,
aacirun - > pcm - > r [ 0 ] . slots ) ;
if ( err )
goto out ;
aacirun - > pcm_open = 1 ;
out :
return err ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_prepare ( struct snd_pcm_substream * substream )
2005-05-12 16:04:59 +04:00
{
2005-11-17 17:10:16 +03:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2005-05-12 16:04:59 +04:00
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 ;
}
2005-11-17 17:10:16 +03:00
static snd_pcm_uframes_t aaci_pcm_pointer ( struct snd_pcm_substream * substream )
2005-05-12 16:04:59 +04:00
{
2005-11-17 17:10:16 +03:00
struct snd_pcm_runtime * runtime = substream - > runtime ;
2005-05-12 16:04:59 +04:00
struct aaci_runtime * aacirun = runtime - > private_data ;
ssize_t bytes = aacirun - > ptr - aacirun - > start ;
return bytes_to_frames ( runtime , bytes ) ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_mmap ( struct snd_pcm_substream * substream , struct vm_area_struct * vma )
2005-05-12 16:04:59 +04:00
{
return devdma_mmap ( NULL , substream , vma ) ;
}
/*
* Playback specific ALSA stuff
*/
static const u32 channels_to_txmask [ ] = {
[ 2 ] = TXCR_TX3 | TXCR_TX4 ,
[ 4 ] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8 ,
[ 6 ] = TXCR_TX3 | TXCR_TX4 | TXCR_TX7 | TXCR_TX8 | TXCR_TX6 | TXCR_TX9 ,
} ;
/*
* 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
2005-11-17 17:10:16 +03:00
aaci_rule_channels ( struct snd_pcm_hw_params * p , struct snd_pcm_hw_rule * rule )
2005-05-12 16:04:59 +04:00
{
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 ) ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_playback_open ( struct snd_pcm_substream * substream )
2005-05-12 16:04:59 +04:00
{
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 ;
return aaci_pcm_open ( aaci , substream , & aaci - > playback ) ;
}
2005-11-17 17:10:16 +03:00
static int aaci_pcm_playback_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
2005-05-12 16:04:59 +04:00
{
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 = TXCR_FEN | TXCR_COMPACT | TXCR_TSZ16 ;
aacirun - > cr | = channels_to_txmask [ channels ] ;
aacirun - > fifosz = aaci - > fifosize * 4 ;
if ( aacirun - > cr & TXCR_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 & = ~ TXCR_TXEN ;
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 | = TXCR_TXEN ;
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 17:10:16 +03:00
static int aaci_pcm_playback_trigger ( struct snd_pcm_substream * substream , int cmd )
2005-05-12 16:04:59 +04:00
{
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 ;
}
2005-11-17 17:10:16 +03:00
static struct snd_pcm_ops aaci_playback_ops = {
2005-05-12 16:04:59 +04:00
. open = aaci_pcm_playback_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 ,
} ;
/*
* Power Management .
*/
# ifdef CONFIG_PM
2005-11-17 17:10:16 +03:00
static int aaci_do_suspend ( struct snd_card * card , unsigned int state )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = card - > private_data ;
if ( aaci - > card - > power_state ! = SNDRV_CTL_POWER_D3cold ) {
snd_pcm_suspend_all ( aaci - > pcm ) ;
snd_power_change_state ( aaci - > card , SNDRV_CTL_POWER_D3cold ) ;
}
return 0 ;
}
2005-11-17 17:10:16 +03:00
static int aaci_do_resume ( struct snd_card * card , unsigned int state )
2005-05-12 16:04:59 +04:00
{
struct aaci * aaci = card - > private_data ;
if ( aaci - > card - > power_state ! = SNDRV_CTL_POWER_D0 ) {
snd_power_change_state ( aaci - > card , SNDRV_CTL_POWER_D0 ) ;
}
return 0 ;
}
2005-09-17 06:27:53 +04:00
static int aaci_suspend ( struct amba_device * dev , pm_message_t state )
2005-05-12 16:04:59 +04:00
{
2005-11-17 17:10:16 +03:00
struct snd_card * card = amba_get_drvdata ( dev ) ;
2005-05-12 16:04:59 +04:00
return card ? aaci_do_suspend ( card ) : 0 ;
}
static int aaci_resume ( struct amba_device * dev )
{
2005-11-17 17:10:16 +03:00
struct snd_card * card = amba_get_drvdata ( dev ) ;
2005-05-12 16:04:59 +04:00
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 ) ,
} ,
} ,
}
} ;
2005-11-17 17:10:16 +03:00
static struct snd_ac97_bus_ops aaci_bus_ops = {
2005-05-12 16:04:59 +04:00
. write = aaci_ac97_write ,
. read = aaci_ac97_read ,
} ;
static int __devinit aaci_probe_ac97 ( struct aaci * aaci )
{
2005-11-17 17:10:16 +03:00
struct snd_ac97_template ac97_template ;
struct snd_ac97_bus * ac97_bus ;
struct snd_ac97 * ac97 ;
2005-05-12 16:04:59 +04:00
int ret ;
/*
* Assert AACIRESET for 2u s
*/
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 . ( 42u s = ~ 2 frames at 48 kHz . )
*/
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 ;
2005-11-17 17:10:16 +03:00
memset ( & ac97_template , 0 , sizeof ( struct snd_ac97_template ) ) ;
2005-05-12 16:04:59 +04: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 ;
/*
* 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 ] ;
out :
return ret ;
}
2005-11-17 17:10:16 +03:00
static void aaci_free_card ( struct snd_card * card )
2005-05-12 16:04:59 +04:00
{
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 ;
2005-11-17 17:10:16 +03:00
struct snd_card * card ;
2005-05-12 16:04:59 +04:00
card = snd_card_new ( SNDRV_DEFAULT_IDX1 , SNDRV_DEFAULT_STR1 ,
THIS_MODULE , sizeof ( struct aaci ) ) ;
if ( card = = NULL )
return ERR_PTR ( - ENOMEM ) ;
card - > private_free = aaci_free_card ;
snd_card_set_pm_callback ( card , aaci_do_suspend , aaci_do_resume , NULL ) ;
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%08lx, irq %d " ,
card - > shortname , dev - > res . start , dev - > irq [ 0 ] ) ;
aaci = card - > private_data ;
init_MUTEX ( & 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 )
{
2005-11-17 17:10:16 +03:00
struct snd_pcm * pcm ;
2005-05-12 16:04:59 +04:00
int ret ;
ret = snd_pcm_new ( aaci - > card , " AACI AC'97 " , 0 , 1 , 0 , & 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 ) ;
}
return ret ;
}
static unsigned int __devinit aaci_size_fifo ( struct aaci * aaci )
{
2005-09-06 05:06:57 +04:00
void __iomem * base = aaci - > base + AACI_CSCH1 ;
2005-05-12 16:04:59 +04:00
int i ;
writel ( TXCR_FEN | TXCR_TSZ16 | TXCR_TXEN , base + AACI_TXCR ) ;
for ( i = 0 ; ! ( readl ( base + AACI_SR ) & SR_TXFF ) & & i < 4096 ; i + + )
writel ( 0 , aaci - > base + AACI_DR1 ) ;
writel ( 0 , 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 , void * id )
{
struct aaci * aaci ;
int ret , i ;
ret = amba_request_regions ( dev , NULL ) ;
if ( ret )
return ret ;
aaci = aaci_init_card ( dev ) ;
if ( IS_ERR ( aaci ) ) {
ret = PTR_ERR ( aaci ) ;
goto out ;
}
aaci - > base = ioremap ( dev - > res . start , SZ_4K ) ;
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 ;
for ( i = 0 ; i < 4 ; i + + ) {
2005-09-06 05:06:57 +04:00
void __iomem * base = aaci - > base + i * 0x14 ;
2005-05-12 16:04:59 +04: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 ) ;
/*
* Size the FIFOs .
*/
aaci - > fifosize = aaci_size_fifo ( aaci ) ;
ret = aaci_probe_ac97 ( aaci ) ;
if ( ret )
goto out ;
ret = aaci_init_pcm ( aaci ) ;
if ( ret )
goto out ;
2005-09-05 18:56:40 +04:00
snd_card_set_dev ( aaci - > card , & dev - > dev ) ;
2005-05-12 16:04:59 +04:00
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 )
{
2005-11-17 17:10:16 +03:00
struct snd_card * card = amba_get_drvdata ( dev ) ;
2005-05-12 16:04:59 +04:00
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 " ) ;