2019-02-07 16:31:40 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// JZ4740 CODEC driver
//
// Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
2010-06-22 02:46:31 +04:00
# 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
} ;
2015-08-02 18:19:40 +03:00
static const DECLARE_TLV_DB_RANGE ( jz4740_mic_tlv ,
2012-10-27 19:15:06 +04:00
0 , 2 , TLV_DB_SCALE_ITEM ( 0 , 600 , 0 ) ,
2015-08-02 18:19:40 +03:00
3 , 3 , TLV_DB_SCALE_ITEM ( 2000 , 0 , 0 )
) ;
2012-10-27 19:15:06 +04:00
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 )
{
2018-01-29 07:43:36 +03:00
struct jz4740_codec * jz4740_codec = snd_soc_component_get_drvdata ( dai - > component ) ;
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 ,
2021-01-15 07:55:20 +03:00
. symmetric_rate = 1 ,
2010-06-22 02:46:31 +04:00
} ;
2012-10-27 19:15:08 +04:00
static void jz4740_codec_wakeup ( struct regmap * regmap )
2010-06-22 02:46:31 +04:00
{
2020-12-07 15:53:33 +03:00
regmap_set_bits ( regmap , JZ4740_REG_CODEC_1 , JZ4740_CODEC_1_RESET ) ;
2010-06-22 02:46:31 +04:00
udelay ( 2 ) ;
2020-12-07 15:53:33 +03:00
regmap_clear_bits ( regmap , JZ4740_REG_CODEC_1 ,
JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET ) ;
2010-06-22 02:46:31 +04:00
2012-10-27 19:15:08 +04:00
regcache_sync ( regmap ) ;
2010-06-22 02:46:31 +04:00
}
2018-01-29 07:43:36 +03:00
static int jz4740_codec_set_bias_level ( struct snd_soc_component * component ,
2010-06-22 02:46:31 +04:00
enum snd_soc_bias_level level )
{
2018-01-29 07:43:36 +03:00
struct jz4740_codec * jz4740_codec = snd_soc_component_get_drvdata ( component ) ;
2012-10-27 19:15:08 +04:00
struct regmap * regmap = jz4740_codec - > regmap ;
2010-06-22 02:46:31 +04:00
unsigned int mask ;
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 ;
2020-12-07 15:53:33 +03:00
regmap_clear_bits ( regmap , JZ4740_REG_CODEC_1 , mask ) ;
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 */
2018-01-29 07:43:36 +03:00
if ( snd_soc_component_get_bias_level ( component ) = = 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 ;
2020-12-07 15:53:33 +03:00
regmap_set_bits ( regmap , JZ4740_REG_CODEC_1 , mask ) ;
2010-06-22 02:46:31 +04:00
break ;
case SND_SOC_BIAS_OFF :
mask = JZ4740_CODEC_1_SUSPEND ;
2020-12-07 15:53:33 +03:00
regmap_set_bits ( regmap , JZ4740_REG_CODEC_1 , mask ) ;
2012-10-27 19:15:08 +04:00
regcache_mark_dirty ( regmap ) ;
2010-06-22 02:46:31 +04:00
break ;
default :
break ;
}
return 0 ;
}
2018-01-29 07:43:36 +03:00
static int jz4740_codec_dev_probe ( struct snd_soc_component * component )
2010-06-22 02:46:31 +04:00
{
2018-01-29 07:43:36 +03:00
struct jz4740_codec * jz4740_codec = snd_soc_component_get_drvdata ( component ) ;
2012-10-27 19:15:08 +04:00
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
return 0 ;
}
2018-01-29 07:43:36 +03:00
static const struct snd_soc_component_driver soc_codec_dev_jz4740_codec = {
. probe = jz4740_codec_dev_probe ,
. set_bias_level = jz4740_codec_set_bias_level ,
. 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 ) ,
. suspend_bias_off = 1 ,
. idle_bias_on = 1 ,
. use_pmdown_time = 1 ,
. endianness = 1 ,
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 ) ,
2023-07-12 14:14:21 +03:00
. cache_type = REGCACHE_MAPLE ,
2012-10-27 19:15:08 +04:00
} ;
2012-12-07 18:26:37 +04:00
static int jz4740_codec_probe ( struct platform_device * pdev )
2010-06-22 02:46:31 +04:00
{
int ret ;
struct jz4740_codec * jz4740_codec ;
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 ;
2019-07-27 18:07:23 +03:00
base = devm_platform_ioremap_resource ( pdev , 0 ) ;
2013-01-21 14:09:26 +04:00
if ( IS_ERR ( base ) )
return PTR_ERR ( base ) ;
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 ) ;
2018-01-29 07:43:36 +03:00
ret = devm_snd_soc_register_component ( & pdev - > dev ,
2010-03-17 23:15:21 +03:00
& 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 ;
}
2019-02-07 16:31:41 +03:00
static const struct of_device_id jz4740_codec_of_matches [ ] = {
{ . compatible = " ingenic,jz4740-codec " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , jz4740_codec_of_matches ) ;
2010-06-22 02:46:31 +04:00
static struct platform_driver jz4740_codec_driver = {
. probe = jz4740_codec_probe ,
. driver = {
. name = " jz4740-codec " ,
2020-05-23 15:54:55 +03:00
. of_match_table = jz4740_codec_of_matches ,
2010-06-22 02:46:31 +04:00
} ,
} ;
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 " ) ;