2010-11-22 09:37:25 +03:00
/* sound/soc/samsung/ac97.c
2010-01-27 08:59:08 +03:00
*
* ALSA SoC Audio Layer - S3C AC97 Controller driver
* Evolved from s3c2443 - ac97 . c
*
* Copyright ( c ) 2010 Samsung Electronics Co . Ltd
2012-02-25 14:54:36 +04:00
* Author : Jaswinder Singh < jassisinghbrar @ gmail . com >
2010-01-27 08:59:08 +03:00
* Credits : Graeme Gregory , Sean Choi
*
* 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/io.h>
# include <linux/delay.h>
# include <linux/clk.h>
2011-07-15 20:38:28 +04:00
# include <linux/module.h>
2010-01-27 08:59:08 +03:00
# include <sound/soc.h>
2013-04-11 21:08:42 +04:00
# include "regs-ac97.h"
2012-08-24 17:22:12 +04:00
# include <linux/platform_data/asoc-s3c.h>
2010-01-27 08:59:08 +03:00
2010-11-22 09:35:57 +03:00
# include "dma.h"
2010-01-27 08:59:08 +03:00
# define AC_CMD_ADDR(x) (x << 16)
# define AC_CMD_DATA(x) (x & 0xffff)
2011-01-07 07:46:52 +03:00
# define S3C_AC97_DAI_PCM 0
# define S3C_AC97_DAI_MIC 1
2010-01-27 08:59:08 +03:00
struct s3c_ac97_info {
struct clk * ac97_clk ;
void __iomem * regs ;
struct mutex lock ;
struct completion done ;
} ;
static struct s3c_ac97_info s3c_ac97 ;
static struct s3c_dma_params s3c_ac97_pcm_out = {
. dma_size = 4 ,
} ;
static struct s3c_dma_params s3c_ac97_pcm_in = {
. dma_size = 4 ,
} ;
static struct s3c_dma_params s3c_ac97_mic_in = {
. dma_size = 4 ,
} ;
static void s3c_ac97_activate ( struct snd_ac97 * ac97 )
{
u32 ac_glbctrl , stat ;
stat = readl ( s3c_ac97 . regs + S3C_AC97_GLBSTAT ) & 0x7 ;
if ( stat = = S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE )
return ; /* Return if already active */
2013-11-15 02:32:02 +04:00
reinit_completion ( & s3c_ac97 . done ) ;
2010-01-27 08:59:08 +03:00
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl = S3C_AC97_GLBCTRL_ACLINKON ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_TRANSFERDATAENABLE ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
if ( ! wait_for_completion_timeout ( & s3c_ac97 . done , HZ ) )
2010-09-23 19:48:54 +04:00
pr_err ( " AC97: Unable to activate! " ) ;
2010-01-27 08:59:08 +03:00
}
static unsigned short s3c_ac97_read ( struct snd_ac97 * ac97 ,
unsigned short reg )
{
u32 ac_glbctrl , ac_codec_cmd ;
u32 stat , addr , data ;
mutex_lock ( & s3c_ac97 . lock ) ;
s3c_ac97_activate ( ac97 ) ;
2013-11-15 02:32:02 +04:00
reinit_completion ( & s3c_ac97 . done ) ;
2010-01-27 08:59:08 +03:00
ac_codec_cmd = readl ( s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd = S3C_AC97_CODEC_CMD_READ | AC_CMD_ADDR ( reg ) ;
writel ( ac_codec_cmd , s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
udelay ( 50 ) ;
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
if ( ! wait_for_completion_timeout ( & s3c_ac97 . done , HZ ) )
2010-09-23 19:48:54 +04:00
pr_err ( " AC97: Unable to read! " ) ;
2010-01-27 08:59:08 +03:00
stat = readl ( s3c_ac97 . regs + S3C_AC97_STAT ) ;
addr = ( stat > > 16 ) & 0x7f ;
data = ( stat & 0xffff ) ;
if ( addr ! = reg )
2010-11-22 09:36:03 +03:00
pr_err ( " ac97: req addr = %02x, rep addr = %02x \n " ,
2010-09-23 19:48:54 +04:00
reg , addr ) ;
2010-01-27 08:59:08 +03:00
mutex_unlock ( & s3c_ac97 . lock ) ;
return ( unsigned short ) data ;
}
static void s3c_ac97_write ( struct snd_ac97 * ac97 , unsigned short reg ,
unsigned short val )
{
u32 ac_glbctrl , ac_codec_cmd ;
mutex_lock ( & s3c_ac97 . lock ) ;
s3c_ac97_activate ( ac97 ) ;
2013-11-15 02:32:02 +04:00
reinit_completion ( & s3c_ac97 . done ) ;
2010-01-27 08:59:08 +03:00
ac_codec_cmd = readl ( s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd = AC_CMD_ADDR ( reg ) | AC_CMD_DATA ( val ) ;
writel ( ac_codec_cmd , s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
udelay ( 50 ) ;
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
if ( ! wait_for_completion_timeout ( & s3c_ac97 . done , HZ ) )
2010-09-23 19:48:54 +04:00
pr_err ( " AC97: Unable to write! " ) ;
2010-01-27 08:59:08 +03:00
ac_codec_cmd = readl ( s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
ac_codec_cmd | = S3C_AC97_CODEC_CMD_READ ;
writel ( ac_codec_cmd , s3c_ac97 . regs + S3C_AC97_CODEC_CMD ) ;
mutex_unlock ( & s3c_ac97 . lock ) ;
}
static void s3c_ac97_cold_reset ( struct snd_ac97 * ac97 )
{
2010-09-23 20:41:46 +04:00
pr_debug ( " AC97: Cold reset \n " ) ;
2010-01-27 08:59:08 +03:00
writel ( S3C_AC97_GLBCTRL_COLDRESET ,
s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
writel ( 0 , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
}
static void s3c_ac97_warm_reset ( struct snd_ac97 * ac97 )
{
u32 stat ;
stat = readl ( s3c_ac97 . regs + S3C_AC97_GLBSTAT ) & 0x7 ;
if ( stat = = S3C_AC97_GLBSTAT_MAINSTATE_ACTIVE )
return ; /* Return if already active */
2010-09-23 20:41:46 +04:00
pr_debug ( " AC97: Warm reset \n " ) ;
2010-01-27 08:59:08 +03:00
writel ( S3C_AC97_GLBCTRL_WARMRESET , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
writel ( 0 , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
msleep ( 1 ) ;
s3c_ac97_activate ( ac97 ) ;
}
static irqreturn_t s3c_ac97_irq ( int irq , void * dev_id )
{
u32 ac_glbctrl , ac_glbstat ;
ac_glbstat = readl ( s3c_ac97 . regs + S3C_AC97_GLBSTAT ) ;
if ( ac_glbstat & S3C_AC97_GLBSTAT_CODECREADY ) {
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_CODECREADYIE ;
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
complete ( & s3c_ac97 . done ) ;
}
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl | = ( 1 < < 30 ) ; /* Clear interrupt */
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
return IRQ_HANDLED ;
}
2013-06-26 15:45:59 +04:00
static struct snd_ac97_bus_ops s3c_ac97_ops = {
2010-01-27 08:59:08 +03:00
. read = s3c_ac97_read ,
. write = s3c_ac97_write ,
. warm_reset = s3c_ac97_warm_reset ,
. reset = s3c_ac97_cold_reset ,
} ;
static int s3c_ac97_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_CAPTURE )
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_PCMINTM_MASK ;
else
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_PCMOUTTM_MASK ;
switch ( cmd ) {
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 :
break ;
}
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
return 0 ;
}
static int s3c_ac97_mic_trigger ( struct snd_pcm_substream * substream ,
int cmd , struct snd_soc_dai * dai )
{
u32 ac_glbctrl ;
ac_glbctrl = readl ( s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
ac_glbctrl & = ~ S3C_AC97_GLBCTRL_MICINTM_MASK ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
ac_glbctrl | = S3C_AC97_GLBCTRL_MICINTM_DMA ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
break ;
}
writel ( ac_glbctrl , s3c_ac97 . regs + S3C_AC97_GLBCTRL ) ;
return 0 ;
}
2011-11-23 14:40:40 +04:00
static const struct snd_soc_dai_ops s3c_ac97_dai_ops = {
2010-01-27 08:59:08 +03:00
. trigger = s3c_ac97_trigger ,
} ;
2011-11-23 14:40:40 +04:00
static const struct snd_soc_dai_ops s3c_ac97_mic_dai_ops = {
2010-01-27 08:59:08 +03:00
. trigger = s3c_ac97_mic_trigger ,
} ;
2013-10-19 18:23:15 +04:00
static int s3c_ac97_dai_probe ( struct snd_soc_dai * dai )
{
samsung_asoc_init_dma_data ( dai , & s3c_ac97_pcm_out , & s3c_ac97_pcm_in ) ;
return 0 ;
}
static int s3c_ac97_mic_dai_probe ( struct snd_soc_dai * dai )
{
samsung_asoc_init_dma_data ( dai , NULL , & s3c_ac97_mic_in ) ;
return 0 ;
}
2010-03-17 23:15:21 +03:00
static struct snd_soc_dai_driver s3c_ac97_dai [ ] = {
2010-01-27 08:59:08 +03:00
[ S3C_AC97_DAI_PCM ] = {
2010-11-22 09:36:03 +03:00
. name = " samsung-ac97 " ,
2014-11-11 00:41:52 +03:00
. bus_control = true ,
2010-01-27 08:59:08 +03:00
. playback = {
. stream_name = " AC97 Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
. capture = {
. stream_name = " AC97 Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
2013-10-19 18:23:15 +04:00
. probe = s3c_ac97_dai_probe ,
2010-01-27 08:59:08 +03:00
. ops = & s3c_ac97_dai_ops ,
} ,
[ S3C_AC97_DAI_MIC ] = {
2010-11-22 09:36:03 +03:00
. name = " samsung-ac97-mic " ,
2014-11-11 00:41:52 +03:00
. bus_control = true ,
2010-01-27 08:59:08 +03:00
. capture = {
. stream_name = " AC97 Mic Capture " ,
. channels_min = 1 ,
. channels_max = 1 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE , } ,
2013-10-19 18:23:15 +04:00
. probe = s3c_ac97_mic_dai_probe ,
2010-01-27 08:59:08 +03:00
. ops = & s3c_ac97_mic_dai_ops ,
} ,
} ;
2013-03-21 14:35:33 +04:00
static const struct snd_soc_component_driver s3c_ac97_component = {
. name = " s3c-ac97 " ,
} ;
2012-12-07 18:26:15 +04:00
static int s3c_ac97_probe ( struct platform_device * pdev )
2010-01-27 08:59:08 +03:00
{
struct resource * mem_res , * dmatx_res , * dmarx_res , * dmamic_res , * irq_res ;
struct s3c_audio_pdata * ac97_pdata ;
int ret ;
ac97_pdata = pdev - > dev . platform_data ;
if ( ! ac97_pdata | | ! ac97_pdata - > cfg_gpio ) {
dev_err ( & pdev - > dev , " cfg_gpio callback not provided! \n " ) ;
return - EINVAL ;
}
/* Check for availability of necessary resource */
dmatx_res = platform_get_resource ( pdev , IORESOURCE_DMA , 0 ) ;
if ( ! dmatx_res ) {
dev_err ( & pdev - > dev , " Unable to get AC97-TX dma resource \n " ) ;
return - ENXIO ;
}
dmarx_res = platform_get_resource ( pdev , IORESOURCE_DMA , 1 ) ;
if ( ! dmarx_res ) {
dev_err ( & pdev - > dev , " Unable to get AC97-RX dma resource \n " ) ;
return - ENXIO ;
}
dmamic_res = platform_get_resource ( pdev , IORESOURCE_DMA , 2 ) ;
if ( ! dmamic_res ) {
dev_err ( & pdev - > dev , " Unable to get AC97-MIC dma resource \n " ) ;
return - ENXIO ;
}
irq_res = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( ! irq_res ) {
dev_err ( & pdev - > dev , " AC97 IRQ not provided! \n " ) ;
return - ENXIO ;
}
2013-08-14 13:11:16 +04:00
mem_res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2013-06-26 13:34:37 +04:00
s3c_ac97 . regs = devm_ioremap_resource ( & pdev - > dev , mem_res ) ;
if ( IS_ERR ( s3c_ac97 . regs ) )
return PTR_ERR ( s3c_ac97 . regs ) ;
2010-01-27 08:59:08 +03:00
s3c_ac97_pcm_out . channel = dmatx_res - > start ;
s3c_ac97_pcm_out . dma_addr = mem_res - > start + S3C_AC97_PCM_DATA ;
s3c_ac97_pcm_in . channel = dmarx_res - > start ;
s3c_ac97_pcm_in . dma_addr = mem_res - > start + S3C_AC97_PCM_DATA ;
s3c_ac97_mic_in . channel = dmamic_res - > start ;
s3c_ac97_mic_in . dma_addr = mem_res - > start + S3C_AC97_MIC_DATA ;
init_completion ( & s3c_ac97 . done ) ;
mutex_init ( & s3c_ac97 . lock ) ;
2013-06-26 13:29:02 +04:00
s3c_ac97 . ac97_clk = devm_clk_get ( & pdev - > dev , " ac97 " ) ;
2010-01-27 08:59:08 +03:00
if ( IS_ERR ( s3c_ac97 . ac97_clk ) ) {
2010-11-22 09:36:03 +03:00
dev_err ( & pdev - > dev , " ac97 failed to get ac97_clock \n " ) ;
2010-01-27 08:59:08 +03:00
ret = - ENODEV ;
goto err2 ;
}
2012-10-03 03:46:01 +04:00
clk_prepare_enable ( s3c_ac97 . ac97_clk ) ;
2010-01-27 08:59:08 +03:00
if ( ac97_pdata - > cfg_gpio ( pdev ) ) {
dev_err ( & pdev - > dev , " Unable to configure gpio \n " ) ;
ret = - EINVAL ;
goto err3 ;
}
ret = request_irq ( irq_res - > start , s3c_ac97_irq ,
2011-09-22 12:59:20 +04:00
0 , " AC97 " , NULL ) ;
2010-01-27 08:59:08 +03:00
if ( ret < 0 ) {
2010-11-22 09:36:03 +03:00
dev_err ( & pdev - > dev , " ac97: interrupt request failed. \n " ) ;
2010-01-27 08:59:08 +03:00
goto err4 ;
}
2013-06-26 15:45:59 +04:00
ret = snd_soc_set_ac97_ops ( & s3c_ac97_ops ) ;
if ( ret ! = 0 ) {
dev_err ( & pdev - > dev , " Failed to set AC'97 ops: %d \n " , ret ) ;
goto err4 ;
}
2014-05-21 07:22:19 +04:00
ret = devm_snd_soc_register_component ( & pdev - > dev , & s3c_ac97_component ,
2013-03-21 14:35:33 +04:00
s3c_ac97_dai , ARRAY_SIZE ( s3c_ac97_dai ) ) ;
2010-01-27 08:59:08 +03:00
if ( ret )
goto err5 ;
2013-08-20 01:59:05 +04:00
ret = samsung_asoc_dma_platform_register ( & pdev - > dev ) ;
2012-12-07 12:29:21 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " failed to get register DMA: %d \n " , ret ) ;
2014-05-21 07:22:19 +04:00
goto err5 ;
2012-12-07 12:29:21 +04:00
}
2010-01-27 08:59:08 +03:00
2012-12-07 12:29:21 +04:00
return 0 ;
2010-01-27 08:59:08 +03:00
err5 :
free_irq ( irq_res - > start , NULL ) ;
err4 :
err3 :
2012-10-03 03:46:01 +04:00
clk_disable_unprepare ( s3c_ac97 . ac97_clk ) ;
2010-01-27 08:59:08 +03:00
err2 :
2013-06-26 15:45:59 +04:00
snd_soc_set_ac97_ops ( NULL ) ;
2010-01-27 08:59:08 +03:00
return ret ;
}
2012-12-07 18:26:15 +04:00
static int s3c_ac97_remove ( struct platform_device * pdev )
2010-01-27 08:59:08 +03:00
{
2013-06-26 13:34:37 +04:00
struct resource * irq_res ;
2010-01-27 08:59:08 +03:00
irq_res = platform_get_resource ( pdev , IORESOURCE_IRQ , 0 ) ;
if ( irq_res )
free_irq ( irq_res - > start , NULL ) ;
2012-10-03 03:46:01 +04:00
clk_disable_unprepare ( s3c_ac97 . ac97_clk ) ;
2013-06-26 15:45:59 +04:00
snd_soc_set_ac97_ops ( NULL ) ;
2010-01-27 08:59:08 +03:00
return 0 ;
}
static struct platform_driver s3c_ac97_driver = {
. probe = s3c_ac97_probe ,
2012-12-07 18:26:15 +04:00
. remove = s3c_ac97_remove ,
2010-01-27 08:59:08 +03:00
. driver = {
2010-11-22 09:36:00 +03:00
. name = " samsung-ac97 " ,
2010-01-27 08:59:08 +03:00
} ,
} ;
2011-11-23 19:20:13 +04:00
module_platform_driver ( s3c_ac97_driver ) ;
2010-01-27 08:59:08 +03:00
2012-02-25 14:54:36 +04:00
MODULE_AUTHOR ( " Jaswinder Singh, <jassisinghbrar@gmail.com> " ) ;
2010-01-27 08:59:08 +03:00
MODULE_DESCRIPTION ( " AC97 driver for the Samsung SoC " ) ;
MODULE_LICENSE ( " GPL " ) ;
2010-11-22 09:36:00 +03:00
MODULE_ALIAS ( " platform:samsung-ac97 " ) ;