// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for ADAU1381/ADAU1781 codec
*
* Copyright 2011-2013 Analog Devices Inc.
* Author: Lars-Peter Clausen <lars@metafoo.de>
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/tlv.h>
#include <linux/platform_data/adau17x1.h>
#include "adau17x1.h"
#include "adau1781.h"
#define ADAU1781_DMIC_BEEP_CTRL 0 x4008
#define ADAU1781_LEFT_PGA 0 x400e
#define ADAU1781_RIGHT_PGA 0 x400f
#define ADAU1781_LEFT_PLAYBACK_MIXER 0 x401c
#define ADAU1781_RIGHT_PLAYBACK_MIXER 0 x401e
#define ADAU1781_MONO_PLAYBACK_MIXER 0 x401f
#define ADAU1781_LEFT_LINEOUT 0 x4025
#define ADAU1781_RIGHT_LINEOUT 0 x4026
#define ADAU1781_SPEAKER 0 x4027
#define ADAU1781_BEEP_ZC 0 x4028
#define ADAU1781_DEJITTER 0 x4032
#define ADAU1781_DIG_PWDN0 0 x4080
#define ADAU1781_DIG_PWDN1 0 x4081
#define ADAU1781_INPUT_DIFFERNTIAL BIT(3 )
#define ADAU1381_FIRMWARE "adau1381.bin"
#define ADAU1781_FIRMWARE "adau1781.bin"
static const struct reg_default adau1781_reg_defaults[] = {
{ ADAU1781_DMIC_BEEP_CTRL, 0 x00 },
{ ADAU1781_LEFT_PGA, 0 xc7 },
{ ADAU1781_RIGHT_PGA, 0 xc7 },
{ ADAU1781_LEFT_PLAYBACK_MIXER, 0 x00 },
{ ADAU1781_RIGHT_PLAYBACK_MIXER, 0 x00 },
{ ADAU1781_MONO_PLAYBACK_MIXER, 0 x00 },
{ ADAU1781_LEFT_LINEOUT, 0 x00 },
{ ADAU1781_RIGHT_LINEOUT, 0 x00 },
{ ADAU1781_SPEAKER, 0 x00 },
{ ADAU1781_BEEP_ZC, 0 x19 },
{ ADAU1781_DEJITTER, 0 x60 },
{ ADAU1781_DIG_PWDN1, 0 x0c },
{ ADAU1781_DIG_PWDN1, 0 x00 },
{ ADAU17X1_CLOCK_CONTROL, 0 x00 },
{ ADAU17X1_PLL_CONTROL, 0 x00 },
{ ADAU17X1_REC_POWER_MGMT, 0 x00 },
{ ADAU17X1_MICBIAS, 0 x04 },
{ ADAU17X1_SERIAL_PORT0, 0 x00 },
{ ADAU17X1_SERIAL_PORT1, 0 x00 },
{ ADAU17X1_CONVERTER0, 0 x00 },
{ ADAU17X1_CONVERTER1, 0 x00 },
{ ADAU17X1_LEFT_INPUT_DIGITAL_VOL, 0 x00 },
{ ADAU17X1_RIGHT_INPUT_DIGITAL_VOL, 0 x00 },
{ ADAU17X1_ADC_CONTROL, 0 x00 },
{ ADAU17X1_PLAY_POWER_MGMT, 0 x00 },
{ ADAU17X1_DAC_CONTROL0, 0 x00 },
{ ADAU17X1_DAC_CONTROL1, 0 x00 },
{ ADAU17X1_DAC_CONTROL2, 0 x00 },
{ ADAU17X1_SERIAL_PORT_PAD, 0 x00 },
{ ADAU17X1_CONTROL_PORT_PAD0, 0 x00 },
{ ADAU17X1_CONTROL_PORT_PAD1, 0 x00 },
{ ADAU17X1_DSP_SAMPLING_RATE, 0 x01 },
{ ADAU17X1_SERIAL_INPUT_ROUTE, 0 x00 },
{ ADAU17X1_SERIAL_OUTPUT_ROUTE, 0 x00 },
{ ADAU17X1_DSP_ENABLE, 0 x00 },
{ ADAU17X1_DSP_RUN, 0 x00 },
{ ADAU17X1_SERIAL_SAMPLING_RATE, 0 x00 },
};
static const DECLARE_TLV_DB_SCALE(adau1781_speaker_tlv, 0 , 200 , 0 );
static const DECLARE_TLV_DB_RANGE(adau1781_pga_tlv,
0 , 1 , TLV_DB_SCALE_ITEM(0 , 600 , 0 ),
2 , 3 , TLV_DB_SCALE_ITEM(1000 , 400 , 0 ),
4 , 4 , TLV_DB_SCALE_ITEM(1700 , 0 , 0 ),
5 , 7 , TLV_DB_SCALE_ITEM(2000 , 600 , 0 )
);
static const DECLARE_TLV_DB_RANGE(adau1781_beep_tlv,
0 , 1 , TLV_DB_SCALE_ITEM(0 , 600 , 0 ),
2 , 3 , TLV_DB_SCALE_ITEM(1000 , 400 , 0 ),
4 , 4 , TLV_DB_SCALE_ITEM(-2300 , 0 , 0 ),
5 , 7 , TLV_DB_SCALE_ITEM(2000 , 600 , 0 )
);
static const DECLARE_TLV_DB_SCALE(adau1781_sidetone_tlv, -1800 , 300 , 1 );
static const char * const adau1781_speaker_bias_select_text[] = {
"Normal operation" , "Power saving" , "Enhanced performance" ,
};
static const char * const adau1781_bias_select_text[] = {
"Normal operation" , "Extreme power saving" , "Power saving" ,
"Enhanced performance" ,
};
static SOC_ENUM_SINGLE_DECL(adau1781_adc_bias_enum,
ADAU17X1_REC_POWER_MGMT, 3 , adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_speaker_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 6 , adau1781_speaker_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_dac_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 4 , adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_playback_bias_enum,
ADAU17X1_PLAY_POWER_MGMT, 2 , adau1781_bias_select_text);
static SOC_ENUM_SINGLE_DECL(adau1781_capture_bias_enum,
ADAU17X1_REC_POWER_MGMT, 1 , adau1781_bias_select_text);
static const struct snd_kcontrol_new adau1781_controls[] = {
SOC_SINGLE_TLV("Beep Capture Volume" , ADAU1781_DMIC_BEEP_CTRL, 0 , 7 , 0 ,
adau1781_beep_tlv),
SOC_DOUBLE_R_TLV("PGA Capture Volume" , ADAU1781_LEFT_PGA,
ADAU1781_RIGHT_PGA, 5 , 7 , 0 , adau1781_pga_tlv),
SOC_DOUBLE_R("PGA Capture Switch" , ADAU1781_LEFT_PGA,
ADAU1781_RIGHT_PGA, 1 , 1 , 0 ),
SOC_DOUBLE_R("Lineout Playback Switch" , ADAU1781_LEFT_LINEOUT,
ADAU1781_RIGHT_LINEOUT, 1 , 1 , 0 ),
SOC_SINGLE("Beep ZC Switch" , ADAU1781_BEEP_ZC, 0 , 1 , 0 ),
SOC_SINGLE("Mono Playback Switch" , ADAU1781_MONO_PLAYBACK_MIXER,
0 , 1 , 0 ),
SOC_SINGLE_TLV("Mono Playback Volume" , ADAU1781_SPEAKER, 6 , 3 , 0 ,
adau1781_speaker_tlv),
SOC_ENUM("ADC Bias" , adau1781_adc_bias_enum),
SOC_ENUM("DAC Bias" , adau1781_dac_bias_enum),
SOC_ENUM("Capture Bias" , adau1781_capture_bias_enum),
SOC_ENUM("Playback Bias" , adau1781_playback_bias_enum),
SOC_ENUM("Speaker Bias" , adau1781_speaker_bias_enum),
};
static const struct snd_kcontrol_new adau1781_beep_mixer_controls[] = {
SOC_DAPM_SINGLE("Beep Capture Switch" , ADAU1781_DMIC_BEEP_CTRL,
3 , 1 , 0 ),
};
static const struct snd_kcontrol_new adau1781_left_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Switch" ,
ADAU1781_LEFT_PLAYBACK_MIXER, 5 , 1 , 0 ),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume" ,
ADAU1781_LEFT_PLAYBACK_MIXER, 1 , 8 , 0 , adau1781_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1781_right_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Switch" ,
ADAU1781_RIGHT_PLAYBACK_MIXER, 6 , 1 , 0 ),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume" ,
ADAU1781_LEFT_PLAYBACK_MIXER, 1 , 8 , 0 , adau1781_sidetone_tlv),
};
static const struct snd_kcontrol_new adau1781_mono_mixer_controls[] = {
SOC_DAPM_SINGLE_AUTODISABLE("Left Switch" ,
ADAU1781_MONO_PLAYBACK_MIXER, 7 , 1 , 0 ),
SOC_DAPM_SINGLE_AUTODISABLE("Right Switch" ,
ADAU1781_MONO_PLAYBACK_MIXER, 6 , 1 , 0 ),
SOC_DAPM_SINGLE_TLV("Beep Playback Volume" ,
ADAU1781_MONO_PLAYBACK_MIXER, 2 , 8 , 0 , adau1781_sidetone_tlv),
};
static int adau1781_dejitter_fixup(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
struct adau *adau = snd_soc_component_get_drvdata(component);
/* After any power changes have been made the dejitter circuit
* has to be reinitialized. */
regmap_write(adau->regmap, ADAU1781_DEJITTER, 0 );
if (!adau->master)
regmap_write(adau->regmap, ADAU1781_DEJITTER, 5 );
return 0 ;
}
static const struct snd_soc_dapm_widget adau1781_dapm_widgets[] = {
SND_SOC_DAPM_PGA("Left PGA" , ADAU1781_LEFT_PGA, 0 , 0 , NULL, 0 ),
SND_SOC_DAPM_PGA("Right PGA" , ADAU1781_RIGHT_PGA, 0 , 0 , NULL, 0 ),
SND_SOC_DAPM_OUT_DRV("Speaker" , ADAU1781_SPEAKER, 0 , 0 , NULL, 0 ),
SOC_MIXER_NAMED_CTL_ARRAY("Beep Mixer" , ADAU17X1_MICBIAS, 4 , 0 ,
adau1781_beep_mixer_controls),
SOC_MIXER_ARRAY("Left Lineout Mixer" , SND_SOC_NOPM, 0 , 0 ,
adau1781_left_mixer_controls),
SOC_MIXER_ARRAY("Right Lineout Mixer" , SND_SOC_NOPM, 0 , 0 ,
adau1781_right_mixer_controls),
SOC_MIXER_ARRAY("Mono Mixer" , SND_SOC_NOPM, 0 , 0 ,
adau1781_mono_mixer_controls),
SND_SOC_DAPM_SUPPLY("Serial Input Routing" , ADAU1781_DIG_PWDN0,
2 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Serial Output Routing" , ADAU1781_DIG_PWDN0,
3 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Clock Domain Transfer" , ADAU1781_DIG_PWDN0,
5 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Serial Ports" , ADAU1781_DIG_PWDN0, 4 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("ADC Engine" , ADAU1781_DIG_PWDN0, 7 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("DAC Engine" , ADAU1781_DIG_PWDN1, 0 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Digital Mic" , ADAU1781_DIG_PWDN1, 1 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Sound Engine" , ADAU1781_DIG_PWDN0, 0 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY_S("SYSCLK" , 1 , ADAU1781_DIG_PWDN0, 1 , 0 , NULL, 0 ),
SND_SOC_DAPM_SUPPLY("Zero Crossing Detector" , ADAU1781_DIG_PWDN1, 2 , 0 ,
NULL, 0 ),
SND_SOC_DAPM_POST("Dejitter fixup" , adau1781_dejitter_fixup),
SND_SOC_DAPM_INPUT("BEEP" ),
SND_SOC_DAPM_OUTPUT("AOUTL" ),
SND_SOC_DAPM_OUTPUT("AOUTR" ),
SND_SOC_DAPM_OUTPUT("SP" ),
SND_SOC_DAPM_INPUT("LMIC" ),
SND_SOC_DAPM_INPUT("RMIC" ),
};
static const struct snd_soc_dapm_route adau1781_dapm_routes[] = {
{ "Left Lineout Mixer" , NULL, "Left Playback Enable" },
{ "Right Lineout Mixer" , NULL, "Right Playback Enable" },
{ "Left Lineout Mixer" , "Beep Playback Volume" , "Beep Mixer" },
{ "Left Lineout Mixer" , "Switch" , "Left DAC" },
{ "Right Lineout Mixer" , "Beep Playback Volume" , "Beep Mixer" },
{ "Right Lineout Mixer" , "Switch" , "Right DAC" },
{ "Mono Mixer" , "Beep Playback Volume" , "Beep Mixer" },
{ "Mono Mixer" , "Right Switch" , "Right DAC" },
{ "Mono Mixer" , "Left Switch" , "Left DAC" },
{ "Speaker" , NULL, "Mono Mixer" },
{ "Mono Mixer" , NULL, "SYSCLK" },
{ "Left Lineout Mixer" , NULL, "SYSCLK" },
{ "Left Lineout Mixer" , NULL, "SYSCLK" },
{ "Beep Mixer" , "Beep Capture Switch" , "BEEP" },
{ "Beep Mixer" , NULL, "Zero Crossing Detector" },
{ "Left DAC" , NULL, "DAC Engine" },
{ "Right DAC" , NULL, "DAC Engine" },
{ "Sound Engine" , NULL, "SYSCLK" },
{ "DSP" , NULL, "Sound Engine" },
{ "Left Decimator" , NULL, "ADC Engine" },
{ "Right Decimator" , NULL, "ADC Engine" },
{ "AIFCLK" , NULL, "SYSCLK" },
{ "Playback" , NULL, "Serial Input Routing" },
{ "Playback" , NULL, "Serial Ports" },
{ "Playback" , NULL, "Clock Domain Transfer" },
{ "Capture" , NULL, "Serial Output Routing" },
{ "Capture" , NULL, "Serial Ports" },
{ "Capture" , NULL, "Clock Domain Transfer" },
{ "AOUTL" , NULL, "Left Lineout Mixer" },
{ "AOUTR" , NULL, "Right Lineout Mixer" },
{ "SP" , NULL, "Speaker" },
};
static const struct snd_soc_dapm_route adau1781_adc_dapm_routes[] = {
{ "Left PGA" , NULL, "LMIC" },
{ "Right PGA" , NULL, "RMIC" },
{ "Left Decimator" , NULL, "Left PGA" },
{ "Right Decimator" , NULL, "Right PGA" },
};
static const char * const adau1781_dmic_select_text[] = {
"DMIC1" , "DMIC2" ,
};
static SOC_ENUM_SINGLE_VIRT_DECL(adau1781_dmic_select_enum,
adau1781_dmic_select_text);
static const struct snd_kcontrol_new adau1781_dmic_mux =
SOC_DAPM_ENUM("DMIC Select" , adau1781_dmic_select_enum);
static const struct snd_soc_dapm_widget adau1781_dmic_dapm_widgets[] = {
SND_SOC_DAPM_MUX("DMIC Select" , SND_SOC_NOPM, 0 , 0 , &adau1781_dmic_mux),
SND_SOC_DAPM_ADC("DMIC1" , NULL, ADAU1781_DMIC_BEEP_CTRL, 4 , 0 ),
SND_SOC_DAPM_ADC("DMIC2" , NULL, ADAU1781_DMIC_BEEP_CTRL, 5 , 0 ),
};
static const struct snd_soc_dapm_route adau1781_dmic_dapm_routes[] = {
{ "DMIC1" , NULL, "LMIC" },
{ "DMIC2" , NULL, "RMIC" },
{ "DMIC1" , NULL, "Digital Mic" },
{ "DMIC2" , NULL, "Digital Mic" },
{ "DMIC Select" , "DMIC1" , "DMIC1" },
{ "DMIC Select" , "DMIC2" , "DMIC2" },
{ "Left Decimator" , NULL, "DMIC Select" },
{ "Right Decimator" , NULL, "DMIC Select" },
};
static int adau1781_set_bias_level(struct snd_soc_component *component,
enum snd_soc_bias_level level)
{
struct adau *adau = snd_soc_component_get_drvdata(component);
switch (level) {
case SND_SOC_BIAS_ON:
break ;
case SND_SOC_BIAS_PREPARE:
break ;
case SND_SOC_BIAS_STANDBY:
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN);
/* Precharge */
regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0 x8, 0 x8);
break ;
case SND_SOC_BIAS_OFF:
regmap_update_bits(adau->regmap, ADAU1781_DIG_PWDN1, 0 xc, 0 x0);
regmap_update_bits(adau->regmap, ADAU17X1_CLOCK_CONTROL,
ADAU17X1_CLOCK_CONTROL_SYSCLK_EN, 0 );
break ;
}
return 0 ;
}
static bool adau1781_readable_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case ADAU1781_DMIC_BEEP_CTRL:
case ADAU1781_LEFT_PGA:
case ADAU1781_RIGHT_PGA:
case ADAU1781_LEFT_PLAYBACK_MIXER:
case ADAU1781_RIGHT_PLAYBACK_MIXER:
case ADAU1781_MONO_PLAYBACK_MIXER:
case ADAU1781_LEFT_LINEOUT:
case ADAU1781_RIGHT_LINEOUT:
case ADAU1781_SPEAKER:
case ADAU1781_BEEP_ZC:
case ADAU1781_DEJITTER:
case ADAU1781_DIG_PWDN0:
case ADAU1781_DIG_PWDN1:
return true ;
default :
break ;
}
return adau17x1_readable_register(dev, reg);
}
static int adau1781_set_input_mode(struct adau *adau, unsigned int reg,
bool differential)
{
unsigned int val;
if (differential)
val = ADAU1781_INPUT_DIFFERNTIAL;
else
val = 0 ;
return regmap_update_bits(adau->regmap, reg,
ADAU1781_INPUT_DIFFERNTIAL, val);
}
static int adau1781_component_probe(struct snd_soc_component *component)
{
struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
struct adau1781_platform_data *pdata = dev_get_platdata(component->dev);
struct adau *adau = snd_soc_component_get_drvdata(component);
int ret;
ret = adau17x1_add_widgets(component);
if (ret)
return ret;
if (pdata) {
ret = adau1781_set_input_mode(adau, ADAU1781_LEFT_PGA,
pdata->left_input_differential);
if (ret)
return ret;
ret = adau1781_set_input_mode(adau, ADAU1781_RIGHT_PGA,
pdata->right_input_differential);
if (ret)
return ret;
}
if (pdata && pdata->use_dmic) {
ret = snd_soc_dapm_new_controls(dapm,
adau1781_dmic_dapm_widgets,
ARRAY_SIZE(adau1781_dmic_dapm_widgets));
if (ret)
return ret;
ret = snd_soc_dapm_add_routes(dapm, adau1781_dmic_dapm_routes,
ARRAY_SIZE(adau1781_dmic_dapm_routes));
if (ret)
return ret;
} else {
ret = snd_soc_dapm_add_routes(dapm, adau1781_adc_dapm_routes,
ARRAY_SIZE(adau1781_adc_dapm_routes));
if (ret)
return ret;
}
ret = adau17x1_add_routes(component);
if (ret < 0 )
return ret;
return 0 ;
}
static const struct snd_soc_component_driver adau1781_component_driver = {
.probe = adau1781_component_probe,
.resume = adau17x1_resume,
.set_bias_level = adau1781_set_bias_level,
.controls = adau1781_controls,
.num_controls = ARRAY_SIZE(adau1781_controls),
.dapm_widgets = adau1781_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(adau1781_dapm_widgets),
.dapm_routes = adau1781_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(adau1781_dapm_routes),
.suspend_bias_off = 1 ,
.idle_bias_on = 1 ,
.use_pmdown_time = 1 ,
.endianness = 1 ,
};
#define ADAU1781_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_driver adau1781_dai_driver = {
.name = "adau-hifi" ,
.playback = {
.stream_name = "Playback" ,
.channels_min = 2 ,
.channels_max = 8 ,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1781_FORMATS,
},
.capture = {
.stream_name = "Capture" ,
.channels_min = 2 ,
.channels_max = 8 ,
.rates = SNDRV_PCM_RATE_8000_96000,
.formats = ADAU1781_FORMATS,
},
.ops = &adau17x1_dai_ops,
};
const struct regmap_config adau1781_regmap_config = {
.val_bits = 8 ,
.reg_bits = 16 ,
.max_register = 0 x40f8,
.reg_defaults = adau1781_reg_defaults,
.num_reg_defaults = ARRAY_SIZE(adau1781_reg_defaults),
.readable_reg = adau1781_readable_register,
.volatile_reg = adau17x1_volatile_register,
.precious_reg = adau17x1_precious_register,
.cache_type = REGCACHE_MAPLE,
};
EXPORT_SYMBOL_GPL(adau1781_regmap_config);
int adau1781_probe(struct device *dev, struct regmap *regmap,
enum adau17x1_type type, void (*switch_mode)(struct device *dev))
{
const char *firmware_name;
int ret;
switch (type) {
case ADAU1381:
firmware_name = ADAU1381_FIRMWARE;
break ;
case ADAU1781:
firmware_name = ADAU1781_FIRMWARE;
break ;
default :
return -EINVAL;
}
ret = adau17x1_probe(dev, regmap, type, switch_mode, firmware_name);
if (ret)
return ret;
return devm_snd_soc_register_component(dev, &adau1781_component_driver,
&adau1781_dai_driver, 1 );
}
EXPORT_SYMBOL_GPL(adau1781_probe);
MODULE_DESCRIPTION("ASoC ADAU1381/ADAU1781 driver" );
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>" );
MODULE_LICENSE("GPL" );
Messung V0.5 in Prozent C=96 H=92 G=93
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland