2007-05-14 11:02:51 +02:00
/*
* s3c2443 - ac97 . c - - ALSA Soc Audio Layer
*
* ( c ) 2007 Wolfson Microelectronics PLC .
* Graeme Gregory graeme . gregory @ wolfsonmicro . com or linux @ wolfsonmicro . com
*
* Copyright ( C ) 2005 , Sean Choi < sh428 . choi @ samsung . com >
* 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 .
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
2008-04-30 17:19:07 +02:00
# include <linux/io.h>
2007-05-14 11:02:51 +02:00
# include <linux/wait.h>
# include <linux/delay.h>
2009-05-13 22:52:24 +01:00
# include <linux/gpio.h>
2007-05-14 11:02:51 +02:00
# include <linux/clk.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/ac97_codec.h>
# include <sound/initval.h>
# include <sound/soc.h>
2008-08-05 16:14:15 +01:00
# include <mach/hardware.h>
2008-10-30 10:14:36 +00:00
# include <plat/regs-ac97.h>
2008-08-05 16:14:15 +01:00
# include <mach/regs-gpio.h>
# include <mach/regs-clock.h>
2009-03-04 00:49:28 +00:00
# include <plat/audio.h>
2007-05-14 11:02:51 +02:00
# include <asm/dma.h>
2008-08-05 16:14:15 +01:00
# include <mach/dma.h>
2007-05-14 11:02:51 +02:00
# include "s3c24xx-pcm.h"
# include "s3c24xx-ac97.h"
struct s3c24xx_ac97_info {
void __iomem * regs ;
struct clk * ac97_clk ;
} ;
static struct s3c24xx_ac97_info s3c24xx_ac97 ;
2008-04-30 17:19:07 +02:00
static DECLARE_COMPLETION ( ac97_completion ) ;
2007-05-14 11:02:51 +02:00
static u32 codec_ready ;
2009-08-07 22:42:37 +02:00
static DEFINE_MUTEX ( ac97_mutex ) ;
2007-05-14 11:02:51 +02:00
static unsigned short s3c2443_ac97_read ( struct snd_ac97 * ac97 ,
unsigned short reg )
{
u32 ac_glbctrl ;
u32 ac_codec_cmd ;
u32 stat , addr , data ;
2009-08-07 22:42:37 +02:00
mutex_lock ( & ac97_mutex ) ;
2007-05-14 11:02:51 +02:00
codec_ready = S3C_AC97_GLBSTAT_CODECREADY ;
ac_codec_cmd = readl ( s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR ( reg ) ;
writel ( ac_codec_cmd , s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
udelay ( 50 ) ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
wait_for_completion ( & ac97_completion ) ;
stat = readl ( s3c24xx_ac97 . regs + S3C_AC97_STAT ) ;
addr = ( stat > > 16 ) & 0x7f ;
data = ( stat & 0xffff ) ;
if ( addr ! = reg )
printk ( KERN_ERR " s3c24xx-ac97: req addr = %02x, "
" rep addr = %02x \n " , reg , addr ) ;
2009-08-07 22:42:37 +02:00
mutex_unlock ( & ac97_mutex ) ;
2007-05-14 11:02:51 +02:00
return ( unsigned short ) data ;
}
static void s3c2443_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
u32 ac_glbctrl ;
u32 ac_codec_cmd ;
2009-08-07 22:42:37 +02:00
mutex_lock ( & ac97_mutex ) ;
2007-05-14 11:02:51 +02:00
codec_ready = S3C_AC97_GLBSTAT_CODECREADY ;
ac_codec_cmd = readl ( s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd = AC_CMD_ADDR ( reg ) | AC_CMD_DATA ( val ) ;
writel ( ac_codec_cmd , s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
udelay ( 50 ) ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
wait_for_completion ( & ac97_completion ) ;
ac_codec_cmd = readl ( s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd | = S3C_AC97_CODEC_CMD_READ ;
writel ( ac_codec_cmd , s3c24xx_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
2009-08-07 22:42:37 +02:00
mutex_unlock ( & ac97_mutex ) ;
2007-05-14 11:02:51 +02:00
}
static void s3c2443_ac97_warm_reset ( struct snd_ac97 * ac97 )
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_WARMRESET ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = 0 ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
}
static void s3c2443_ac97_cold_reset ( struct snd_ac97 * ac97 )
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = 0 ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_TRANSFERDATAENABLE ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_PCMOUTTM_DMA |
S3C_AC97_GLBCTRL_PCMINTM_DMA | S3C_AC97_GLBCTRL_MICINTM_DMA ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
}
static irqreturn_t s3c2443_ac97_irq ( int irq , void * dev_id )
{
int status ;
u32 ac_glbctrl ;
status = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBSTAT ) & codec_ready ;
if ( status ) {
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
complete ( & ac97_completion ) ;
}
return IRQ_HANDLED ;
}
struct snd_ac97_bus_ops soc_ac97_ops = {
. read = s3c2443_ac97_read ,
. write = s3c2443_ac97_write ,
. warm_reset = s3c2443_ac97_warm_reset ,
. reset = s3c2443_ac97_cold_reset ,
} ;
static struct s3c2410_dma_client s3c2443_dma_client_out = {
. name = " AC97 PCM Stereo out "
} ;
static struct s3c2410_dma_client s3c2443_dma_client_in = {
. name = " AC97 PCM Stereo in "
} ;
static struct s3c2410_dma_client s3c2443_dma_client_micin = {
. name = " AC97 Mic Mono in "
} ;
static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_out = {
. client = & s3c2443_dma_client_out ,
. channel = DMACH_PCM_OUT ,
. dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA ,
. dma_size = 4 ,
} ;
static struct s3c24xx_pcm_dma_params s3c2443_ac97_pcm_stereo_in = {
. client = & s3c2443_dma_client_in ,
. channel = DMACH_PCM_IN ,
. dma_addr = S3C2440_PA_AC97 + S3C_AC97_PCM_DATA ,
. dma_size = 4 ,
} ;
static struct s3c24xx_pcm_dma_params s3c2443_ac97_mic_mono_in = {
. client = & s3c2443_dma_client_micin ,
. channel = DMACH_MIC_IN ,
. dma_addr = S3C2440_PA_AC97 + S3C_AC97_MIC_DATA ,
. dma_size = 4 ,
} ;
2008-06-11 13:47:10 +01:00
static int s3c2443_ac97_probe ( struct platform_device * pdev ,
2008-07-07 16:08:24 +01:00
struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
int ret ;
u32 ac_glbctrl ;
s3c24xx_ac97 . regs = ioremap ( S3C2440_PA_AC97 , 0x100 ) ;
if ( s3c24xx_ac97 . regs = = NULL )
return - ENXIO ;
s3c24xx_ac97 . ac97_clk = clk_get ( & pdev - > dev , " ac97 " ) ;
if ( s3c24xx_ac97 . ac97_clk = = NULL ) {
printk ( KERN_ERR " s3c2443-ac97 failed to get ac97_clock \n " ) ;
iounmap ( s3c24xx_ac97 . regs ) ;
return - ENODEV ;
}
clk_enable ( s3c24xx_ac97 . ac97_clk ) ;
s3c2410_gpio_cfgpin ( S3C2410_GPE0 , S3C2443_GPE0_AC_nRESET ) ;
s3c2410_gpio_cfgpin ( S3C2410_GPE1 , S3C2443_GPE1_AC_SYNC ) ;
s3c2410_gpio_cfgpin ( S3C2410_GPE2 , S3C2443_GPE2_AC_BITCLK ) ;
s3c2410_gpio_cfgpin ( S3C2410_GPE3 , S3C2443_GPE3_AC_SDI ) ;
s3c2410_gpio_cfgpin ( S3C2410_GPE4 , S3C2443_GPE4_AC_SDO ) ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_COLDRESET ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = 0 ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_TRANSFERDATAENABLE ;
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
2007-10-26 14:53:47 +02:00
ret = request_irq ( IRQ_S3C244x_AC97 , s3c2443_ac97_irq ,
2007-05-14 11:02:51 +02:00
IRQF_DISABLED , " AC97 " , NULL ) ;
if ( ret < 0 ) {
printk ( KERN_ERR " s3c24xx-ac97: interrupt request failed. \n " ) ;
clk_disable ( s3c24xx_ac97 . ac97_clk ) ;
clk_put ( s3c24xx_ac97 . ac97_clk ) ;
iounmap ( s3c24xx_ac97 . regs ) ;
}
return ret ;
}
2008-06-11 13:47:10 +01:00
static void s3c2443_ac97_remove ( struct platform_device * pdev ,
2008-07-07 16:08:24 +01:00
struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
2007-10-26 14:53:47 +02:00
free_irq ( IRQ_S3C244x_AC97 , NULL ) ;
2007-05-14 11:02:51 +02:00
clk_disable ( s3c24xx_ac97 . ac97_clk ) ;
clk_put ( s3c24xx_ac97 . ac97_clk ) ;
iounmap ( s3c24xx_ac97 . regs ) ;
}
static int s3c2443_ac97_hw_params ( struct snd_pcm_substream * substream ,
2008-11-18 22:11:38 +00:00
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 16:08:24 +01:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2007-05-14 11:02:51 +02:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
cpu_dai - > dma_data = & s3c2443_ac97_pcm_stereo_out ;
else
cpu_dai - > dma_data = & s3c2443_ac97_pcm_stereo_in ;
return 0 ;
}
2008-12-01 20:03:54 +01:00
static int s3c2443_ac97_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
2008-04-30 17:19:07 +02:00
switch ( cmd ) {
2007-05-14 11:02:51 +02:00
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
ac_glbctrl | = S3C_AC97_GLBCTRL_PCMINTM_DMA ;
else
ac_glbctrl | = S3C_AC97_GLBCTRL_PCMOUTTM_DMA ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_PCMINTM_MASK ;
else
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_PCMOUTTM_MASK ;
break ;
}
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
return 0 ;
}
static int s3c2443_ac97_hw_mic_params ( struct snd_pcm_substream * substream ,
2008-11-18 22:11:38 +00:00
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 16:08:24 +01:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2007-05-14 11:02:51 +02:00
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
return - ENODEV ;
else
cpu_dai - > dma_data = & s3c2443_ac97_mic_mono_in ;
return 0 ;
}
static int s3c2443_ac97_mic_trigger ( struct snd_pcm_substream * substream ,
2008-11-18 22:11:38 +00:00
int cmd , struct snd_soc_dai * dai )
2007-05-14 11:02:51 +02:00
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
2008-04-30 17:19:07 +02:00
switch ( cmd ) {
2007-05-14 11:02:51 +02:00
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
ac_glbctrl | = S3C_AC97_GLBCTRL_PCMINTM_DMA ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_PCMINTM_MASK ;
}
writel ( ac_glbctrl , s3c24xx_ac97 . regs + S3C_AC97_GLBCTRL ) ;
return 0 ;
}
# define s3c2443_AC97_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 )
2009-03-03 09:41:00 +08:00
static struct snd_soc_dai_ops s3c2443_ac97_dai_ops = {
. hw_params = s3c2443_ac97_hw_params ,
. trigger = s3c2443_ac97_trigger ,
} ;
2009-03-11 16:51:31 +00:00
static struct snd_soc_dai_ops s3c2443_ac97_mic_dai_ops = {
. hw_params = s3c2443_ac97_hw_mic_params ,
. trigger = s3c2443_ac97_mic_trigger ,
} ;
2008-07-07 16:08:24 +01:00
struct snd_soc_dai s3c2443_ac97_dai [ ] = {
2007-05-14 11:02:51 +02:00
{
. name = " s3c2443-ac97 " ,
. id = 0 ,
2008-11-24 18:01:05 +00:00
. ac97_control = 1 ,
2007-05-14 11:02:51 +02:00
. probe = s3c2443_ac97_probe ,
. remove = s3c2443_ac97_remove ,
. playback = {
. stream_name = " AC97 Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = s3c2443_AC97_RATES ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
. capture = {
. stream_name = " AC97 Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = s3c2443_AC97_RATES ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
2009-03-03 09:41:00 +08:00
. ops = & s3c2443_ac97_dai_ops ,
2007-05-14 11:02:51 +02:00
} ,
{
. name = " pxa2xx-ac97-mic " ,
. id = 1 ,
2008-11-24 18:01:05 +00:00
. ac97_control = 1 ,
2007-05-14 11:02:51 +02:00
. capture = {
. stream_name = " AC97 Mic Capture " ,
. channels_min = 1 ,
. channels_max = 1 ,
. rates = s3c2443_AC97_RATES ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
2009-03-11 16:51:31 +00:00
. ops = & s3c2443_ac97_mic_dai_ops ,
2007-05-14 11:02:51 +02:00
} ,
} ;
EXPORT_SYMBOL_GPL ( s3c2443_ac97_dai ) ;
EXPORT_SYMBOL_GPL ( soc_ac97_ops ) ;
2008-12-10 07:47:22 +01:00
static int __init s3c2443_ac97_init ( void )
2008-12-03 19:26:35 +00:00
{
2008-12-11 10:28:18 +01:00
return snd_soc_register_dais ( s3c2443_ac97_dai ,
ARRAY_SIZE ( s3c2443_ac97_dai ) ) ;
2008-12-03 19:26:35 +00:00
}
module_init ( s3c2443_ac97_init ) ;
static void __exit s3c2443_ac97_exit ( void )
{
2008-12-11 10:28:18 +01:00
snd_soc_unregister_dais ( s3c2443_ac97_dai ,
ARRAY_SIZE ( s3c2443_ac97_dai ) ) ;
2008-12-03 19:26:35 +00:00
}
module_exit ( s3c2443_ac97_exit ) ;
2007-05-14 11:02:51 +02:00
MODULE_AUTHOR ( " Graeme Gregory " ) ;
MODULE_DESCRIPTION ( " AC97 driver for the Samsung s3c2443 chip " ) ;
MODULE_LICENSE ( " GPL " ) ;