2010-06-22 02:46:31 +04:00
/*
* Copyright ( C ) 2009 - 2010 , Lars - Peter Clausen < lars @ metafoo . de >
*
* 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 .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
2011-12-06 11:19:00 +04:00
# include <linux/io.h>
2012-10-27 19:15:08 +04:00
# include <linux/regmap.h>
2010-06-22 02:46:31 +04:00
# include <linux/delay.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/initval.h>
# include <sound/soc.h>
2012-10-27 19:15:06 +04:00
# include <sound/tlv.h>
2010-06-22 02:46:31 +04:00
# define JZ4740_REG_CODEC_1 0x0
2012-10-27 19:15:08 +04:00
# define JZ4740_REG_CODEC_2 0x4
2010-06-22 02:46:31 +04:00
# define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
# define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
# define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
# define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
# define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
# define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
# define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
# define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
# define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
# define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
# define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
# define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
# define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
# define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
# define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
# define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
# define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
# define JZ4740_CODEC_1_SUSPEND BIT(1)
# define JZ4740_CODEC_1_RESET BIT(0)
# define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
# define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
# define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
# define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
# define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
# define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
# define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
# define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
# define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
# define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
# define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
# define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
# define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
# define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
# define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
# define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
2012-10-27 19:15:08 +04:00
static const struct reg_default jz4740_codec_reg_defaults [ ] = {
{ JZ4740_REG_CODEC_1 , 0x021b2302 } ,
{ JZ4740_REG_CODEC_2 , 0x00170803 } ,
2010-06-22 02:46:31 +04:00
} ;
struct jz4740_codec {
2012-10-27 19:15:08 +04:00
struct regmap * regmap ;
2010-06-22 02:46:31 +04:00
} ;
2012-10-27 19:15:06 +04:00
static const unsigned int jz4740_mic_tlv [ ] = {
TLV_DB_RANGE_HEAD ( 2 ) ,
0 , 2 , TLV_DB_SCALE_ITEM ( 0 , 600 , 0 ) ,
3 , 3 , TLV_DB_SCALE_ITEM ( 2000 , 0 , 0 ) ,
} ;
static const DECLARE_TLV_DB_SCALE ( jz4740_out_tlv , 0 , 200 , 0 ) ;
static const DECLARE_TLV_DB_SCALE ( jz4740_in_tlv , - 3450 , 150 , 0 ) ;
2010-06-22 02:46:31 +04:00
static const struct snd_kcontrol_new jz4740_codec_controls [ ] = {
2012-10-27 19:15:06 +04:00
SOC_SINGLE_TLV ( " Master Playback Volume " , JZ4740_REG_CODEC_2 ,
JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET , 3 , 0 ,
jz4740_out_tlv ) ,
SOC_SINGLE_TLV ( " Master Capture Volume " , JZ4740_REG_CODEC_2 ,
JZ4740_CODEC_2_INPUT_VOLUME_OFFSET , 31 , 0 ,
jz4740_in_tlv ) ,
2010-06-22 02:46:31 +04:00
SOC_SINGLE ( " Master Playback Switch " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET , 1 , 1 ) ,
2012-10-27 19:15:06 +04:00
SOC_SINGLE_TLV ( " Mic Capture Volume " , JZ4740_REG_CODEC_2 ,
JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET , 3 , 0 ,
jz4740_mic_tlv ) ,
2010-06-22 02:46:31 +04:00
} ;
static const struct snd_kcontrol_new jz4740_codec_output_controls [ ] = {
SOC_DAPM_SINGLE ( " Bypass Switch " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_SW1_ENABLE_OFFSET , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " DAC Switch " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_SW2_ENABLE_OFFSET , 1 , 0 ) ,
} ;
static const struct snd_kcontrol_new jz4740_codec_input_controls [ ] = {
SOC_DAPM_SINGLE ( " Line Capture Switch " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_LINE_ENABLE_OFFSET , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Mic Capture Switch " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_MIC_ENABLE_OFFSET , 1 , 0 ) ,
} ;
static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets [ ] = {
SND_SOC_DAPM_ADC ( " ADC " , " Capture " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_ADC_ENABLE_OFFSET , 0 ) ,
SND_SOC_DAPM_DAC ( " DAC " , " Playback " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_DAC_ENABLE_OFFSET , 0 ) ,
SND_SOC_DAPM_MIXER ( " Output Mixer " , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET , 1 ,
jz4740_codec_output_controls ,
ARRAY_SIZE ( jz4740_codec_output_controls ) ) ,
SND_SOC_DAPM_MIXER_NAMED_CTL ( " Input Mixer " , SND_SOC_NOPM , 0 , 0 ,
jz4740_codec_input_controls ,
ARRAY_SIZE ( jz4740_codec_input_controls ) ) ,
SND_SOC_DAPM_MIXER ( " Line Input " , SND_SOC_NOPM , 0 , 0 , NULL , 0 ) ,
SND_SOC_DAPM_OUTPUT ( " LOUT " ) ,
SND_SOC_DAPM_OUTPUT ( " ROUT " ) ,
SND_SOC_DAPM_INPUT ( " MIC " ) ,
SND_SOC_DAPM_INPUT ( " LIN " ) ,
SND_SOC_DAPM_INPUT ( " RIN " ) ,
} ;
static const struct snd_soc_dapm_route jz4740_codec_dapm_routes [ ] = {
{ " Line Input " , NULL , " LIN " } ,
{ " Line Input " , NULL , " RIN " } ,
{ " Input Mixer " , " Line Capture Switch " , " Line Input " } ,
{ " Input Mixer " , " Mic Capture Switch " , " MIC " } ,
{ " ADC " , NULL , " Input Mixer " } ,
{ " Output Mixer " , " Bypass Switch " , " Input Mixer " } ,
{ " Output Mixer " , " DAC Switch " , " DAC " } ,
{ " LOUT " , NULL , " Output Mixer " } ,
{ " ROUT " , NULL , " Output Mixer " } ,
} ;
static int jz4740_codec_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params , struct snd_soc_dai * dai )
{
2012-10-27 19:15:08 +04:00
struct jz4740_codec * jz4740_codec = snd_soc_codec_get_drvdata ( dai - > codec ) ;
2010-06-22 02:46:31 +04:00
uint32_t val ;
switch ( params_rate ( params ) ) {
case 8000 :
val = 0 ;
break ;
case 11025 :
val = 1 ;
break ;
case 12000 :
val = 2 ;
break ;
case 16000 :
val = 3 ;
break ;
case 22050 :
val = 4 ;
break ;
case 24000 :
val = 5 ;
break ;
case 32000 :
val = 6 ;
break ;
case 44100 :
val = 7 ;
break ;
case 48000 :
val = 8 ;
break ;
default :
return - EINVAL ;
}
val < < = JZ4740_CODEC_2_SAMPLE_RATE_OFFSET ;
2012-10-27 19:15:08 +04:00
regmap_update_bits ( jz4740_codec - > regmap , JZ4740_REG_CODEC_2 ,
2010-06-22 02:46:31 +04:00
JZ4740_CODEC_2_SAMPLE_RATE_MASK , val ) ;
return 0 ;
}
2011-11-23 14:40:40 +04:00
static const struct snd_soc_dai_ops jz4740_codec_dai_ops = {
2010-06-22 02:46:31 +04:00
. hw_params = jz4740_codec_hw_params ,
} ;
2010-03-17 23:15:21 +03:00
static struct snd_soc_dai_driver jz4740_codec_dai = {
. name = " jz4740-hifi " ,
2010-06-22 02:46:31 +04:00
. playback = {
. stream_name = " Playback " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 ,
} ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 2 ,
. channels_max = 2 ,
. rates = SNDRV_PCM_RATE_8000_48000 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8 ,
} ,
. ops = & jz4740_codec_dai_ops ,
. symmetric_rates = 1 ,
} ;
2012-10-27 19:15:08 +04:00
static void jz4740_codec_wakeup ( struct regmap * regmap )
2010-06-22 02:46:31 +04:00
{
2012-10-27 19:15:08 +04:00
regmap_update_bits ( regmap , JZ4740_REG_CODEC_1 ,
2010-06-22 02:46:31 +04:00
JZ4740_CODEC_1_RESET , JZ4740_CODEC_1_RESET ) ;
udelay ( 2 ) ;
2012-10-27 19:15:08 +04:00
regmap_update_bits ( regmap , JZ4740_REG_CODEC_1 ,
2010-06-22 02:46:31 +04:00
JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET , 0 ) ;
2012-10-27 19:15:08 +04:00
regcache_sync ( regmap ) ;
2010-06-22 02:46:31 +04:00
}
static int jz4740_codec_set_bias_level ( struct snd_soc_codec * codec ,
enum snd_soc_bias_level level )
{
2012-10-27 19:15:08 +04:00
struct jz4740_codec * jz4740_codec = snd_soc_codec_get_drvdata ( codec ) ;
struct regmap * regmap = jz4740_codec - > regmap ;
2010-06-22 02:46:31 +04:00
unsigned int mask ;
unsigned int value ;
switch ( level ) {
case SND_SOC_BIAS_ON :
break ;
case SND_SOC_BIAS_PREPARE :
mask = JZ4740_CODEC_1_VREF_DISABLE |
JZ4740_CODEC_1_VREF_AMP_DISABLE |
JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M ;
value = 0 ;
2012-10-27 19:15:08 +04:00
regmap_update_bits ( regmap , JZ4740_REG_CODEC_1 , mask , value ) ;
2010-06-22 02:46:31 +04:00
break ;
case SND_SOC_BIAS_STANDBY :
/* The only way to clear the suspend flag is to reset the codec */
2010-11-05 16:53:46 +03:00
if ( codec - > dapm . bias_level = = SND_SOC_BIAS_OFF )
2012-10-27 19:15:08 +04:00
jz4740_codec_wakeup ( regmap ) ;
2010-06-22 02:46:31 +04:00
mask = JZ4740_CODEC_1_VREF_DISABLE |
JZ4740_CODEC_1_VREF_AMP_DISABLE |
JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M ;
value = JZ4740_CODEC_1_VREF_DISABLE |
JZ4740_CODEC_1_VREF_AMP_DISABLE |
JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M ;
2012-10-27 19:15:08 +04:00
regmap_update_bits ( regmap , JZ4740_REG_CODEC_1 , mask , value ) ;
2010-06-22 02:46:31 +04:00
break ;
case SND_SOC_BIAS_OFF :
mask = JZ4740_CODEC_1_SUSPEND ;
value = JZ4740_CODEC_1_SUSPEND ;
2012-10-27 19:15:08 +04:00
regmap_update_bits ( regmap , JZ4740_REG_CODEC_1 , mask , value ) ;
regcache_mark_dirty ( regmap ) ;
2010-06-22 02:46:31 +04:00
break ;
default :
break ;
}
2010-11-05 16:53:46 +03:00
codec - > dapm . bias_level = level ;
2010-06-22 02:46:31 +04:00
return 0 ;
}
2010-03-17 23:15:21 +03:00
static int jz4740_codec_dev_probe ( struct snd_soc_codec * codec )
2010-06-22 02:46:31 +04:00
{
2012-10-27 19:15:08 +04:00
struct jz4740_codec * jz4740_codec = snd_soc_codec_get_drvdata ( codec ) ;
regmap_update_bits ( jz4740_codec - > regmap , JZ4740_REG_CODEC_1 ,
2010-03-17 23:15:21 +03:00
JZ4740_CODEC_1_SW2_ENABLE , JZ4740_CODEC_1_SW2_ENABLE ) ;
2010-06-22 02:46:31 +04:00
2010-03-17 23:15:21 +03:00
jz4740_codec_set_bias_level ( codec , SND_SOC_BIAS_STANDBY ) ;
2010-06-22 02:46:31 +04:00
return 0 ;
}
2010-03-17 23:15:21 +03:00
static int jz4740_codec_dev_remove ( struct snd_soc_codec * codec )
2010-06-22 02:46:31 +04:00
{
2010-03-17 23:15:21 +03:00
jz4740_codec_set_bias_level ( codec , SND_SOC_BIAS_OFF ) ;
2010-06-22 02:46:31 +04:00
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
2011-12-02 13:18:28 +04:00
static int jz4740_codec_suspend ( struct snd_soc_codec * codec )
2010-06-22 02:46:31 +04:00
{
return jz4740_codec_set_bias_level ( codec , SND_SOC_BIAS_OFF ) ;
}
2010-03-17 23:15:21 +03:00
static int jz4740_codec_resume ( struct snd_soc_codec * codec )
2010-06-22 02:46:31 +04:00
{
return jz4740_codec_set_bias_level ( codec , SND_SOC_BIAS_STANDBY ) ;
}
# else
# define jz4740_codec_suspend NULL
# define jz4740_codec_resume NULL
# endif
2010-03-17 23:15:21 +03:00
static struct snd_soc_codec_driver soc_codec_dev_jz4740_codec = {
2010-06-22 02:46:31 +04:00
. probe = jz4740_codec_dev_probe ,
. remove = jz4740_codec_dev_remove ,
. suspend = jz4740_codec_suspend ,
. resume = jz4740_codec_resume ,
2010-03-17 23:15:21 +03:00
. set_bias_level = jz4740_codec_set_bias_level ,
2011-04-12 21:33:29 +04:00
. controls = jz4740_codec_controls ,
. num_controls = ARRAY_SIZE ( jz4740_codec_controls ) ,
. dapm_widgets = jz4740_codec_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( jz4740_codec_dapm_widgets ) ,
. dapm_routes = jz4740_codec_dapm_routes ,
. num_dapm_routes = ARRAY_SIZE ( jz4740_codec_dapm_routes ) ,
2010-06-22 02:46:31 +04:00
} ;
2012-10-27 19:15:08 +04:00
static const struct regmap_config jz4740_codec_regmap_config = {
. reg_bits = 32 ,
. reg_stride = 4 ,
. val_bits = 32 ,
. max_register = JZ4740_REG_CODEC_2 ,
. reg_defaults = jz4740_codec_reg_defaults ,
. num_reg_defaults = ARRAY_SIZE ( jz4740_codec_reg_defaults ) ,
. cache_type = REGCACHE_RBTREE ,
} ;
2010-06-22 02:46:31 +04:00
static int __devinit jz4740_codec_probe ( struct platform_device * pdev )
{
int ret ;
struct jz4740_codec * jz4740_codec ;
struct resource * mem ;
2012-10-27 19:15:08 +04:00
void __iomem * base ;
2010-06-22 02:46:31 +04:00
2011-12-26 16:55:01 +04:00
jz4740_codec = devm_kzalloc ( & pdev - > dev , sizeof ( * jz4740_codec ) ,
GFP_KERNEL ) ;
2010-06-22 02:46:31 +04:00
if ( ! jz4740_codec )
return - ENOMEM ;
mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
2012-10-27 19:15:08 +04:00
base = devm_request_and_ioremap ( & pdev - > dev , mem ) ;
if ( ! base )
2012-10-27 19:15:07 +04:00
return - EBUSY ;
2010-06-22 02:46:31 +04:00
2012-10-27 19:15:08 +04:00
jz4740_codec - > regmap = devm_regmap_init_mmio ( & pdev - > dev , base ,
& jz4740_codec_regmap_config ) ;
if ( IS_ERR ( jz4740_codec - > regmap ) )
return PTR_ERR ( jz4740_codec - > regmap ) ;
2010-06-22 02:46:31 +04:00
platform_set_drvdata ( pdev , jz4740_codec ) ;
2010-03-17 23:15:21 +03:00
ret = snd_soc_register_codec ( & pdev - > dev ,
& soc_codec_dev_jz4740_codec , & jz4740_codec_dai , 1 ) ;
2012-10-27 19:15:07 +04:00
if ( ret )
2010-06-22 02:46:31 +04:00
dev_err ( & pdev - > dev , " Failed to register codec \n " ) ;
return ret ;
}
static int __devexit jz4740_codec_remove ( struct platform_device * pdev )
{
2010-03-17 23:15:21 +03:00
snd_soc_unregister_codec ( & pdev - > dev ) ;
2010-06-22 02:46:31 +04:00
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
static struct platform_driver jz4740_codec_driver = {
. probe = jz4740_codec_probe ,
. remove = __devexit_p ( jz4740_codec_remove ) ,
. driver = {
. name = " jz4740-codec " ,
. owner = THIS_MODULE ,
} ,
} ;
2011-11-24 02:52:08 +04:00
module_platform_driver ( jz4740_codec_driver ) ;
2010-06-22 02:46:31 +04:00
MODULE_DESCRIPTION ( " JZ4740 SoC internal codec driver " ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:jz4740-codec " ) ;