2019-10-10 10:42:33 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// Analog Devices ADAU7118 8 channel PDM-to-I2S/TDM Converter driver
//
// Copyright 2019 Analog Devices Inc.
# include <linux/bitfield.h>
# include <linux/module.h>
# include <linux/regmap.h>
# include <linux/regulator/consumer.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
# include "adau7118.h"
# define ADAU7118_DEC_RATIO_MASK GENMASK(1, 0)
# define ADAU7118_DEC_RATIO(x) FIELD_PREP(ADAU7118_DEC_RATIO_MASK, x)
# define ADAU7118_CLK_MAP_MASK GENMASK(7, 4)
# define ADAU7118_SLOT_WIDTH_MASK GENMASK(5, 4)
# define ADAU7118_SLOT_WIDTH(x) FIELD_PREP(ADAU7118_SLOT_WIDTH_MASK, x)
# define ADAU7118_TRISTATE_MASK BIT(6)
# define ADAU7118_TRISTATE(x) FIELD_PREP(ADAU7118_TRISTATE_MASK, x)
# define ADAU7118_DATA_FMT_MASK GENMASK(3, 1)
# define ADAU7118_DATA_FMT(x) FIELD_PREP(ADAU7118_DATA_FMT_MASK, x)
# define ADAU7118_SAI_MODE_MASK BIT(0)
# define ADAU7118_SAI_MODE(x) FIELD_PREP(ADAU7118_SAI_MODE_MASK, x)
# define ADAU7118_LRCLK_BCLK_POL_MASK GENMASK(1, 0)
# define ADAU7118_LRCLK_BCLK_POL(x) \
FIELD_PREP ( ADAU7118_LRCLK_BCLK_POL_MASK , x )
# define ADAU7118_SPT_SLOT_MASK GENMASK(7, 4)
# define ADAU7118_SPT_SLOT(x) FIELD_PREP(ADAU7118_SPT_SLOT_MASK, x)
# define ADAU7118_FULL_SOFT_R_MASK BIT(1)
# define ADAU7118_FULL_SOFT_R(x) FIELD_PREP(ADAU7118_FULL_SOFT_R_MASK, x)
struct adau7118_data {
struct regmap * map ;
struct device * dev ;
struct regulator * iovdd ;
struct regulator * dvdd ;
u32 slot_width ;
u32 slots ;
bool hw_mode ;
bool right_j ;
} ;
/* Input Enable */
static const struct snd_kcontrol_new adau7118_dapm_pdm_control [ 4 ] = {
SOC_DAPM_SINGLE ( " Capture Switch " , ADAU7118_REG_ENABLES , 0 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Capture Switch " , ADAU7118_REG_ENABLES , 1 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Capture Switch " , ADAU7118_REG_ENABLES , 2 , 1 , 0 ) ,
SOC_DAPM_SINGLE ( " Capture Switch " , ADAU7118_REG_ENABLES , 3 , 1 , 0 ) ,
} ;
static const struct snd_soc_dapm_widget adau7118_widgets_sw [ ] = {
/* Input Enable Switches */
SND_SOC_DAPM_SWITCH ( " PDM0 " , SND_SOC_NOPM , 0 , 0 ,
& adau7118_dapm_pdm_control [ 0 ] ) ,
SND_SOC_DAPM_SWITCH ( " PDM1 " , SND_SOC_NOPM , 0 , 0 ,
& adau7118_dapm_pdm_control [ 1 ] ) ,
SND_SOC_DAPM_SWITCH ( " PDM2 " , SND_SOC_NOPM , 0 , 0 ,
& adau7118_dapm_pdm_control [ 2 ] ) ,
SND_SOC_DAPM_SWITCH ( " PDM3 " , SND_SOC_NOPM , 0 , 0 ,
& adau7118_dapm_pdm_control [ 3 ] ) ,
/* PDM Clocks */
SND_SOC_DAPM_SUPPLY ( " PDM_CLK0 " , ADAU7118_REG_ENABLES , 4 , 0 , NULL , 0 ) ,
SND_SOC_DAPM_SUPPLY ( " PDM_CLK1 " , ADAU7118_REG_ENABLES , 5 , 0 , NULL , 0 ) ,
/* Output channels */
SND_SOC_DAPM_AIF_OUT ( " AIF1TX1 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 0 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX2 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 1 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX3 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 2 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX4 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 3 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX5 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 4 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX6 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 5 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX7 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 6 ) ,
0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX8 " , " Capture " , 0 , ADAU7118_REG_SPT_CX ( 7 ) ,
0 , 0 ) ,
} ;
static const struct snd_soc_dapm_route adau7118_routes_sw [ ] = {
{ " PDM0 " , " Capture Switch " , " PDM_DAT0 " } ,
{ " PDM1 " , " Capture Switch " , " PDM_DAT1 " } ,
{ " PDM2 " , " Capture Switch " , " PDM_DAT2 " } ,
{ " PDM3 " , " Capture Switch " , " PDM_DAT3 " } ,
{ " AIF1TX1 " , NULL , " PDM0 " } ,
{ " AIF1TX2 " , NULL , " PDM0 " } ,
{ " AIF1TX3 " , NULL , " PDM1 " } ,
{ " AIF1TX4 " , NULL , " PDM1 " } ,
{ " AIF1TX5 " , NULL , " PDM2 " } ,
{ " AIF1TX6 " , NULL , " PDM2 " } ,
{ " AIF1TX7 " , NULL , " PDM3 " } ,
{ " AIF1TX8 " , NULL , " PDM3 " } ,
{ " Capture " , NULL , " PDM_CLK0 " } ,
{ " Capture " , NULL , " PDM_CLK1 " } ,
} ;
static const struct snd_soc_dapm_widget adau7118_widgets_hw [ ] = {
SND_SOC_DAPM_AIF_OUT ( " AIF1TX " , " Capture " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
} ;
static const struct snd_soc_dapm_route adau7118_routes_hw [ ] = {
{ " AIF1TX " , NULL , " PDM_DAT0 " } ,
{ " AIF1TX " , NULL , " PDM_DAT1 " } ,
{ " AIF1TX " , NULL , " PDM_DAT2 " } ,
{ " AIF1TX " , NULL , " PDM_DAT3 " } ,
} ;
static const struct snd_soc_dapm_widget adau7118_widgets [ ] = {
SND_SOC_DAPM_INPUT ( " PDM_DAT0 " ) ,
SND_SOC_DAPM_INPUT ( " PDM_DAT1 " ) ,
SND_SOC_DAPM_INPUT ( " PDM_DAT2 " ) ,
SND_SOC_DAPM_INPUT ( " PDM_DAT3 " ) ,
} ;
static int adau7118_set_channel_map ( struct snd_soc_dai * dai ,
unsigned int tx_num , unsigned int * tx_slot ,
unsigned int rx_num , unsigned int * rx_slot )
{
struct adau7118_data * st =
snd_soc_component_get_drvdata ( dai - > component ) ;
int chan , ret ;
dev_dbg ( st - > dev , " Set channel map, %d " , tx_num ) ;
for ( chan = 0 ; chan < tx_num ; chan + + ) {
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CX ( chan ) ,
ADAU7118_SPT_SLOT_MASK ,
ADAU7118_SPT_SLOT ( tx_slot [ chan ] ) ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
static int adau7118_set_fmt ( struct snd_soc_dai * dai , unsigned int fmt )
{
struct adau7118_data * st =
snd_soc_component_get_drvdata ( dai - > component ) ;
int ret = 0 ;
u32 regval ;
dev_dbg ( st - > dev , " Set format, fmt:%d \n " , fmt ) ;
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_DATA_FMT_MASK ,
ADAU7118_DATA_FMT ( 0 ) ) ;
break ;
case SND_SOC_DAIFMT_LEFT_J :
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_DATA_FMT_MASK ,
ADAU7118_DATA_FMT ( 1 ) ) ;
break ;
case SND_SOC_DAIFMT_RIGHT_J :
st - > right_j = true ;
break ;
default :
dev_err ( st - > dev , " Invalid format %d " ,
fmt & SND_SOC_DAIFMT_FORMAT_MASK ) ;
return - EINVAL ;
}
if ( ret < 0 )
return ret ;
switch ( fmt & SND_SOC_DAIFMT_INV_MASK ) {
case SND_SOC_DAIFMT_NB_NF :
regval = ADAU7118_LRCLK_BCLK_POL ( 0 ) ;
break ;
case SND_SOC_DAIFMT_NB_IF :
regval = ADAU7118_LRCLK_BCLK_POL ( 2 ) ;
break ;
case SND_SOC_DAIFMT_IB_NF :
regval = ADAU7118_LRCLK_BCLK_POL ( 1 ) ;
break ;
case SND_SOC_DAIFMT_IB_IF :
regval = ADAU7118_LRCLK_BCLK_POL ( 3 ) ;
break ;
default :
dev_err ( st - > dev , " Invalid Inv mask %d " ,
fmt & SND_SOC_DAIFMT_INV_MASK ) ;
return - EINVAL ;
}
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL2 ,
ADAU7118_LRCLK_BCLK_POL_MASK ,
regval ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int adau7118_set_tristate ( struct snd_soc_dai * dai , int tristate )
{
struct adau7118_data * st =
snd_soc_component_get_drvdata ( dai - > component ) ;
int ret ;
dev_dbg ( st - > dev , " Set tristate, %d \n " , tristate ) ;
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_TRISTATE_MASK ,
ADAU7118_TRISTATE ( tristate ) ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
static int adau7118_set_tdm_slot ( struct snd_soc_dai * dai , unsigned int tx_mask ,
unsigned int rx_mask , int slots ,
int slot_width )
{
struct adau7118_data * st =
snd_soc_component_get_drvdata ( dai - > component ) ;
int ret = 0 ;
u32 regval ;
dev_dbg ( st - > dev , " Set tdm, slots:%d width:%d \n " , slots , slot_width ) ;
switch ( slot_width ) {
case 32 :
regval = ADAU7118_SLOT_WIDTH ( 0 ) ;
break ;
case 24 :
regval = ADAU7118_SLOT_WIDTH ( 2 ) ;
break ;
case 16 :
regval = ADAU7118_SLOT_WIDTH ( 1 ) ;
break ;
default :
dev_err ( st - > dev , " Invalid slot width:%d \n " , slot_width ) ;
return - EINVAL ;
}
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_SLOT_WIDTH_MASK , regval ) ;
if ( ret < 0 )
return ret ;
st - > slot_width = slot_width ;
st - > slots = slots ;
return 0 ;
}
static int adau7118_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
{
struct adau7118_data * st =
snd_soc_component_get_drvdata ( dai - > component ) ;
u32 data_width = params_width ( params ) , slots_width ;
int ret ;
u32 regval ;
if ( ! st - > slots ) {
/* set stereo mode */
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_SAI_MODE_MASK ,
ADAU7118_SAI_MODE ( 0 ) ) ;
if ( ret < 0 )
return ret ;
slots_width = 32 ;
} else {
slots_width = st - > slot_width ;
}
if ( data_width > slots_width ) {
dev_err ( st - > dev , " Invalid data_width:%d, slots_width:%d " ,
data_width , slots_width ) ;
return - EINVAL ;
}
if ( st - > right_j ) {
switch ( slots_width - data_width ) {
case 8 :
/* delay bclck by 8 */
regval = ADAU7118_DATA_FMT ( 2 ) ;
break ;
case 12 :
/* delay bclck by 12 */
regval = ADAU7118_DATA_FMT ( 3 ) ;
break ;
case 16 :
/* delay bclck by 16 */
regval = ADAU7118_DATA_FMT ( 4 ) ;
break ;
default :
dev_err ( st - > dev ,
" Cannot set right_j setting, slot_w:%d, data_w:%d \n " ,
slots_width , data_width ) ;
return - EINVAL ;
}
ret = snd_soc_component_update_bits ( dai - > component ,
ADAU7118_REG_SPT_CTRL1 ,
ADAU7118_DATA_FMT_MASK ,
regval ) ;
if ( ret < 0 )
return ret ;
}
return 0 ;
}
static int adau7118_set_bias_level ( struct snd_soc_component * component ,
enum snd_soc_bias_level level )
{
struct adau7118_data * st = snd_soc_component_get_drvdata ( component ) ;
int ret = 0 ;
dev_dbg ( st - > dev , " Set bias level %d \n " , level ) ;
switch ( level ) {
case SND_SOC_BIAS_ON :
case SND_SOC_BIAS_PREPARE :
break ;
case SND_SOC_BIAS_STANDBY :
if ( snd_soc_component_get_bias_level ( component ) = =
SND_SOC_BIAS_OFF ) {
/* power on */
ret = regulator_enable ( st - > iovdd ) ;
if ( ret )
return ret ;
/* there's no timing constraints before enabling dvdd */
ret = regulator_enable ( st - > dvdd ) ;
if ( ret ) {
regulator_disable ( st - > iovdd ) ;
return ret ;
}
if ( st - > hw_mode )
return 0 ;
regcache_cache_only ( st - > map , false ) ;
/* sync cache */
ret = snd_soc_component_cache_sync ( component ) ;
}
break ;
case SND_SOC_BIAS_OFF :
/* power off */
ret = regulator_disable ( st - > dvdd ) ;
if ( ret )
return ret ;
ret = regulator_disable ( st - > iovdd ) ;
if ( ret )
return ret ;
if ( st - > hw_mode )
return 0 ;
/* cache only */
regcache_mark_dirty ( st - > map ) ;
regcache_cache_only ( st - > map , true ) ;
break ;
}
return ret ;
}
static int adau7118_component_probe ( struct snd_soc_component * component )
{
struct adau7118_data * st = snd_soc_component_get_drvdata ( component ) ;
struct snd_soc_dapm_context * dapm =
snd_soc_component_get_dapm ( component ) ;
int ret = 0 ;
if ( st - > hw_mode ) {
ret = snd_soc_dapm_new_controls ( dapm , adau7118_widgets_hw ,
ARRAY_SIZE ( adau7118_widgets_hw ) ) ;
if ( ret )
return ret ;
ret = snd_soc_dapm_add_routes ( dapm , adau7118_routes_hw ,
ARRAY_SIZE ( adau7118_routes_hw ) ) ;
} else {
snd_soc_component_init_regmap ( component , st - > map ) ;
ret = snd_soc_dapm_new_controls ( dapm , adau7118_widgets_sw ,
ARRAY_SIZE ( adau7118_widgets_sw ) ) ;
if ( ret )
return ret ;
ret = snd_soc_dapm_add_routes ( dapm , adau7118_routes_sw ,
ARRAY_SIZE ( adau7118_routes_sw ) ) ;
}
return ret ;
}
static const struct snd_soc_dai_ops adau7118_ops = {
. hw_params = adau7118_hw_params ,
. set_channel_map = adau7118_set_channel_map ,
. set_fmt = adau7118_set_fmt ,
. set_tdm_slot = adau7118_set_tdm_slot ,
. set_tristate = adau7118_set_tristate ,
} ;
static struct snd_soc_dai_driver adau7118_dai = {
. name = " adau7118-hifi-capture " ,
. capture = {
. stream_name = " Capture " ,
. channels_min = 1 ,
. channels_max = 8 ,
. formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |
SNDRV_PCM_FMTBIT_S20_LE | SNDRV_PCM_FMTBIT_S24_LE |
SNDRV_PCM_FMTBIT_S24_3LE ,
. rates = SNDRV_PCM_RATE_CONTINUOUS ,
. rate_min = 4000 ,
. rate_max = 192000 ,
. sig_bits = 24 ,
} ,
} ;
static const struct snd_soc_component_driver adau7118_component_driver = {
. probe = adau7118_component_probe ,
. set_bias_level = adau7118_set_bias_level ,
. dapm_widgets = adau7118_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( adau7118_widgets ) ,
. use_pmdown_time = 1 ,
. endianness = 1 ,
. non_legacy_dai_naming = 1 ,
} ;
static void adau7118_regulator_disable ( void * data )
{
struct adau7118_data * st = data ;
int ret ;
/*
* If we fail to disable DVDD , don ' t bother in trying IOVDD . We
* actually don ' t want to be left in the situation where DVDD
* is enabled and IOVDD is disabled .
*/
ret = regulator_disable ( st - > dvdd ) ;
if ( ret )
return ;
regulator_disable ( st - > iovdd ) ;
}
static int adau7118_regulator_setup ( struct adau7118_data * st )
{
2019-10-21 17:08:16 +03:00
st - > iovdd = devm_regulator_get ( st - > dev , " iovdd " ) ;
2019-10-10 10:42:33 +03:00
if ( IS_ERR ( st - > iovdd ) ) {
dev_err ( st - > dev , " Could not get iovdd: %ld \n " ,
PTR_ERR ( st - > iovdd ) ) ;
return PTR_ERR ( st - > iovdd ) ;
}
2019-10-21 17:08:16 +03:00
st - > dvdd = devm_regulator_get ( st - > dev , " dvdd " ) ;
2019-10-10 10:42:33 +03:00
if ( IS_ERR ( st - > dvdd ) ) {
dev_err ( st - > dev , " Could not get dvdd: %ld \n " ,
PTR_ERR ( st - > dvdd ) ) ;
return PTR_ERR ( st - > dvdd ) ;
}
/* just assume the device is in reset */
if ( ! st - > hw_mode ) {
regcache_mark_dirty ( st - > map ) ;
regcache_cache_only ( st - > map , true ) ;
}
return devm_add_action_or_reset ( st - > dev , adau7118_regulator_disable ,
st ) ;
}
static int adau7118_parset_dt ( const struct adau7118_data * st )
{
int ret ;
u32 dec_ratio = 0 ;
/* 4 inputs */
u32 clk_map [ 4 ] , regval ;
if ( st - > hw_mode )
return 0 ;
ret = device_property_read_u32 ( st - > dev , " adi,decimation-ratio " ,
& dec_ratio ) ;
if ( ! ret ) {
switch ( dec_ratio ) {
case 64 :
regval = ADAU7118_DEC_RATIO ( 0 ) ;
break ;
case 32 :
regval = ADAU7118_DEC_RATIO ( 1 ) ;
break ;
case 16 :
regval = ADAU7118_DEC_RATIO ( 2 ) ;
break ;
default :
dev_err ( st - > dev , " Invalid dec ratio: %u " , dec_ratio ) ;
return - EINVAL ;
}
ret = regmap_update_bits ( st - > map ,
ADAU7118_REG_DEC_RATIO_CLK_MAP ,
ADAU7118_DEC_RATIO_MASK , regval ) ;
if ( ret )
return ret ;
}
ret = device_property_read_u32_array ( st - > dev , " adi,pdm-clk-map " ,
clk_map , ARRAY_SIZE ( clk_map ) ) ;
if ( ! ret ) {
int pdm ;
u32 _clk_map = 0 ;
for ( pdm = 0 ; pdm < ARRAY_SIZE ( clk_map ) ; pdm + + )
_clk_map | = ( clk_map [ pdm ] < < ( pdm + 4 ) ) ;
ret = regmap_update_bits ( st - > map ,
ADAU7118_REG_DEC_RATIO_CLK_MAP ,
ADAU7118_CLK_MAP_MASK , _clk_map ) ;
if ( ret )
return ret ;
}
return 0 ;
}
int adau7118_probe ( struct device * dev , struct regmap * map , bool hw_mode )
{
struct adau7118_data * st ;
int ret ;
st = devm_kzalloc ( dev , sizeof ( * st ) , GFP_KERNEL ) ;
if ( ! st )
return - ENOMEM ;
st - > dev = dev ;
st - > hw_mode = hw_mode ;
dev_set_drvdata ( dev , st ) ;
if ( ! hw_mode ) {
st - > map = map ;
adau7118_dai . ops = & adau7118_ops ;
/*
* Perform a full soft reset . This will set all register ' s
* with their reset values .
*/
ret = regmap_update_bits ( map , ADAU7118_REG_RESET ,
ADAU7118_FULL_SOFT_R_MASK ,
ADAU7118_FULL_SOFT_R ( 1 ) ) ;
if ( ret )
return ret ;
}
ret = adau7118_parset_dt ( st ) ;
if ( ret )
return ret ;
ret = adau7118_regulator_setup ( st ) ;
if ( ret )
return ret ;
return devm_snd_soc_register_component ( dev ,
& adau7118_component_driver ,
& adau7118_dai , 1 ) ;
}
EXPORT_SYMBOL_GPL ( adau7118_probe ) ;
MODULE_AUTHOR ( " Nuno Sa <nuno.sa@analog.com> " ) ;
MODULE_DESCRIPTION ( " ADAU7118 8 channel PDM-to-I2S/TDM Converter driver " ) ;
MODULE_LICENSE ( " GPL " ) ;