// 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,
const unsigned int *tx_slot,
unsigned int rx_num,
const 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 ;
case SND_SOC_DAIFMT_DSP_A:
ret = snd_soc_component_update_bits(dai->component,
ADAU7118_REG_SPT_CTRL1,
ADAU7118_DATA_FMT_MASK,
ADAU7118_DATA_FMT(1 ));
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 ,
};
static int adau7118_regulator_setup(struct adau7118_data *st)
{
st->iovdd = devm_regulator_get(st->dev, "iovdd" );
if (IS_ERR(st->iovdd)) {
dev_err(st->dev, "Could not get iovdd: %ld\n" ,
PTR_ERR(st->iovdd));
return PTR_ERR(st->iovdd);
}
st->dvdd = devm_regulator_get(st->dev, "dvdd" );
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 0 ;
}
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" );
Messung V0.5 in Prozent C=97 H=99 G=97
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland