2010-05-18 13:41:46 +08:00
/*
* Copyright ( c ) 2009 - 2010 Nuvoton technology corporation .
*
* Wan ZongShun < mcuos . com @ gmail . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; version 2 of the License .
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/device.h>
# include <linux/delay.h>
# include <linux/mutex.h>
# include <linux/suspend.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/initval.h>
# include <sound/soc.h>
# include <linux/clk.h>
# include <mach/mfp.h>
2010-06-02 14:05:14 +08:00
# include "nuc900-audio.h"
2010-05-18 13:41:46 +08:00
static DEFINE_MUTEX ( ac97_mutex ) ;
struct nuc900_audio * nuc900_ac97_data ;
static int nuc900_checkready ( void )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
if ( ! ( AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACIS0 ) & CODEC_READY ) )
return - EPERM ;
return 0 ;
}
/* AC97 controller reads codec register */
static unsigned short nuc900_ac97_read ( struct snd_ac97 * ac97 ,
unsigned short reg )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
unsigned long timeout = 0x10000 , val ;
mutex_lock ( & ac97_mutex ) ;
val = nuc900_checkready ( ) ;
2010-11-29 17:43:39 +08:00
if ( val ) {
2010-05-18 13:41:46 +08:00
dev_err ( nuc900_audio - > dev , " AC97 codec is not ready \n " ) ;
goto out ;
}
/* set the R_WB bit and write register index */
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS1 , R_WB | reg ) ;
/* set the valid frame bit and valid slots */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
val | = ( VALID_FRAME | SLOT1_VALID ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , val ) ;
udelay ( 100 ) ;
/* polling the AC_R_FINISH */
2010-06-02 14:02:33 +08:00
while ( ! ( AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACCON ) & AC_R_FINISH )
& & timeout - - )
2010-05-18 13:41:46 +08:00
mdelay ( 1 ) ;
if ( ! timeout ) {
dev_err ( nuc900_audio - > dev , " AC97 read register time out ! \n " ) ;
val = - EPERM ;
goto out ;
}
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
val & = ~ SLOT1_VALID ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , val ) ;
if ( AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACIS1 ) > > 2 ! = reg ) {
dev_err ( nuc900_audio - > dev ,
" R_INDEX of REG_ACTL_ACIS1 not match! \n " ) ;
}
udelay ( 100 ) ;
val = ( AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACIS2 ) & 0xFFFF ) ;
out :
mutex_unlock ( & ac97_mutex ) ;
return val ;
}
/* AC97 controller writes to codec register */
static void nuc900_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
unsigned long tmp , timeout = 0x10000 ;
mutex_lock ( & ac97_mutex ) ;
tmp = nuc900_checkready ( ) ;
2010-11-29 17:43:39 +08:00
if ( tmp )
2010-05-18 13:41:46 +08:00
dev_err ( nuc900_audio - > dev , " AC97 codec is not ready \n " ) ;
/* clear the R_WB bit and write register index */
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS1 , reg ) ;
/* write register value */
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS2 , val ) ;
/* set the valid frame bit and valid slots */
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
tmp | = SLOT1_VALID | SLOT2_VALID | VALID_FRAME ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , tmp ) ;
udelay ( 100 ) ;
/* polling the AC_W_FINISH */
2010-06-02 14:02:33 +08:00
while ( ( AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACCON ) & AC_W_FINISH )
& & timeout - - )
2010-05-18 13:41:46 +08:00
mdelay ( 1 ) ;
if ( ! timeout )
dev_err ( nuc900_audio - > dev , " AC97 write register time out ! \n " ) ;
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
tmp & = ~ ( SLOT1_VALID | SLOT2_VALID ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , tmp ) ;
mutex_unlock ( & ac97_mutex ) ;
}
static void nuc900_ac97_warm_reset ( struct snd_ac97 * ac97 )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
unsigned long val ;
mutex_lock ( & ac97_mutex ) ;
/* warm reset AC 97 */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACCON ) ;
val | = AC_W_RES ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACCON , val ) ;
2010-06-02 13:57:01 +08:00
udelay ( 100 ) ;
2010-05-18 13:41:46 +08:00
val = nuc900_checkready ( ) ;
2010-11-29 17:43:39 +08:00
if ( val )
2010-05-18 13:41:46 +08:00
dev_err ( nuc900_audio - > dev , " AC97 codec is not ready \n " ) ;
mutex_unlock ( & ac97_mutex ) ;
}
static void nuc900_ac97_cold_reset ( struct snd_ac97 * ac97 )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
unsigned long val ;
mutex_lock ( & ac97_mutex ) ;
/* reset Audio Controller */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
val | = ACTL_RESET_BIT ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
val & = ( ~ ACTL_RESET_BIT ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
/* reset AC-link interface */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
val | = AC_RESET ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
val & = ~ AC_RESET ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
/* cold reset AC 97 */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACCON ) ;
val | = AC_C_RES ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACCON , val ) ;
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACCON ) ;
val & = ( ~ AC_C_RES ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACCON , val ) ;
2010-06-02 13:57:01 +08:00
udelay ( 100 ) ;
2010-05-18 13:41:46 +08:00
mutex_unlock ( & ac97_mutex ) ;
}
/* AC97 controller operations */
struct snd_ac97_bus_ops soc_ac97_ops = {
. read = nuc900_ac97_read ,
. write = nuc900_ac97_write ,
. reset = nuc900_ac97_cold_reset ,
. warm_reset = nuc900_ac97_warm_reset ,
}
EXPORT_SYMBOL_GPL ( soc_ac97_ops ) ;
static int nuc900_ac97_trigger ( struct snd_pcm_substream * substream ,
int cmd , struct snd_soc_dai * dai )
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
2010-06-02 13:54:25 +08:00
int ret ;
2010-05-18 13:41:46 +08:00
unsigned long val , tmp ;
ret = 0 ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
2010-06-02 13:54:25 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2010-05-18 13:41:46 +08:00
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
tmp | = ( SLOT3_VALID | SLOT4_VALID | VALID_FRAME ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , tmp ) ;
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_PSR ) ;
tmp | = ( P_DMA_END_IRQ | P_DMA_MIDDLE_IRQ ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_PSR , tmp ) ;
val | = AC_PLAY ;
} else {
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RSR ) ;
tmp | = ( R_DMA_END_IRQ | R_DMA_MIDDLE_IRQ ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RSR , tmp ) ;
val | = AC_RECORD ;
}
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_RESET ) ;
2010-06-02 13:54:25 +08:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) {
2010-05-18 13:41:46 +08:00
tmp = AUDIO_READ ( nuc900_audio - > mmio + ACTL_ACOS0 ) ;
tmp & = ~ ( SLOT3_VALID | SLOT4_VALID ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_ACOS0 , tmp ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_PSR , RESET_PRSR ) ;
val & = ~ AC_PLAY ;
} else {
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RSR , RESET_PRSR ) ;
val & = ~ AC_RECORD ;
}
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_RESET , val ) ;
break ;
default :
ret = - EINVAL ;
}
return ret ;
}
2010-11-29 17:40:05 +08:00
static int nuc900_ac97_probe ( struct snd_soc_dai * dai )
2010-05-18 13:41:46 +08:00
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
unsigned long val ;
mutex_lock ( & ac97_mutex ) ;
/* enable unit clock */
clk_enable ( nuc900_audio - > clk ) ;
/* enable audio controller and AC-link interface */
val = AUDIO_READ ( nuc900_audio - > mmio + ACTL_CON ) ;
val | = ( IIS_AC_PIN_SEL | ACLINK_EN ) ;
AUDIO_WRITE ( nuc900_audio - > mmio + ACTL_CON , val ) ;
mutex_unlock ( & ac97_mutex ) ;
return 0 ;
}
2010-11-29 17:40:05 +08:00
static int nuc900_ac97_remove ( struct snd_soc_dai * dai )
2010-05-18 13:41:46 +08:00
{
struct nuc900_audio * nuc900_audio = nuc900_ac97_data ;
clk_disable ( nuc900_audio - > clk ) ;
2010-11-29 17:40:05 +08:00
return 0 ;
2010-05-18 13:41:46 +08:00
}
2011-11-23 11:40:40 +01:00
static const struct snd_soc_dai_ops nuc900_ac97_dai_ops = {
2010-05-18 13:41:46 +08:00
. trigger = nuc900_ac97_trigger ,
} ;
2010-03-17 20:15:21 +00:00
static struct snd_soc_dai_driver nuc900_ac97_dai = {
2010-05-18 13:41:46 +08:00
. probe = nuc900_ac97_probe ,
. remove = nuc900_ac97_remove ,
. ac97_control = 1 ,
. playback = {
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
. channels_min = 1 ,
. channels_max = 2 ,
} ,
. capture = {
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE ,
. channels_min = 1 ,
. channels_max = 2 ,
} ,
. ops = & nuc900_ac97_dai_ops ,
2010-11-29 17:39:10 +08:00
} ;
2010-05-18 13:41:46 +08:00
static int __devinit nuc900_ac97_drvprobe ( struct platform_device * pdev )
{
struct nuc900_audio * nuc900_audio ;
int ret ;
if ( nuc900_ac97_data )
return - EBUSY ;
nuc900_audio = kzalloc ( sizeof ( struct nuc900_audio ) , GFP_KERNEL ) ;
if ( ! nuc900_audio )
return - ENOMEM ;
spin_lock_init ( & nuc900_audio - > lock ) ;
nuc900_audio - > res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! nuc900_audio - > res ) {
ret = - ENODEV ;
goto out0 ;
}
if ( ! request_mem_region ( nuc900_audio - > res - > start ,
resource_size ( nuc900_audio - > res ) , pdev - > name ) ) {
ret = - EBUSY ;
goto out0 ;
}
nuc900_audio - > mmio = ioremap ( nuc900_audio - > res - > start ,
resource_size ( nuc900_audio - > res ) ) ;
if ( ! nuc900_audio - > mmio ) {
ret = - ENOMEM ;
goto out1 ;
}
nuc900_audio - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( nuc900_audio - > clk ) ) {
ret = PTR_ERR ( nuc900_audio - > clk ) ;
goto out2 ;
}
nuc900_audio - > irq_num = platform_get_irq ( pdev , 0 ) ;
if ( ! nuc900_audio - > irq_num ) {
ret = - EBUSY ;
2011-06-01 19:10:05 +02:00
goto out3 ;
2010-05-18 13:41:46 +08:00
}
nuc900_ac97_data = nuc900_audio ;
2010-03-17 20:15:21 +00:00
ret = snd_soc_register_dai ( & pdev - > dev , & nuc900_ac97_dai ) ;
2010-05-18 13:41:46 +08:00
if ( ret )
goto out3 ;
2011-11-25 00:23:28 +01:00
/* enbale ac97 multifunction pin */
2012-01-09 10:54:59 +01:00
mfp_set_groupg ( nuc900_audio - > dev , NULL ) ;
2010-05-18 13:41:46 +08:00
return 0 ;
out3 :
clk_put ( nuc900_audio - > clk ) ;
out2 :
iounmap ( nuc900_audio - > mmio ) ;
out1 :
release_mem_region ( nuc900_audio - > res - > start ,
resource_size ( nuc900_audio - > res ) ) ;
out0 :
kfree ( nuc900_audio ) ;
return ret ;
}
static int __devexit nuc900_ac97_drvremove ( struct platform_device * pdev )
{
2010-03-17 20:15:21 +00:00
snd_soc_unregister_dai ( & pdev - > dev ) ;
2010-05-18 13:41:46 +08:00
clk_put ( nuc900_ac97_data - > clk ) ;
iounmap ( nuc900_ac97_data - > mmio ) ;
release_mem_region ( nuc900_ac97_data - > res - > start ,
resource_size ( nuc900_ac97_data - > res ) ) ;
2010-11-24 16:44:23 +08:00
kfree ( nuc900_ac97_data ) ;
2010-05-18 13:41:46 +08:00
nuc900_ac97_data = NULL ;
return 0 ;
}
static struct platform_driver nuc900_ac97_driver = {
. driver = {
2010-03-17 20:15:21 +00:00
. name = " nuc900-ac97 " ,
2010-05-18 13:41:46 +08:00
. owner = THIS_MODULE ,
} ,
. probe = nuc900_ac97_drvprobe ,
. remove = __devexit_p ( nuc900_ac97_drvremove ) ,
} ;
2011-11-24 10:45:32 +08:00
module_platform_driver ( nuc900_ac97_driver ) ;
2010-05-18 13:41:46 +08:00
MODULE_AUTHOR ( " Wan ZongShun <mcuos.com@gmail.com> " ) ;
MODULE_DESCRIPTION ( " NUC900 AC97 SoC driver! " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:nuc900-ac97 " ) ;