[PATCH v1] add tas2780
1.update Kconfig and Makefile 2.add tas2780.c and tas2780.h
Signed-off-by: Raphael-Xu 13691752556@139.com --- sound/soc/codecs/Kconfig | 8 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tas2780.c | 726 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/tas2780.h | 101 ++++++ 4 files changed, 837 insertions(+) create mode 100644 sound/soc/codecs/tas2780.c create mode 100644 sound/soc/codecs/tas2780.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 6165db92a629..4cb767642219 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -219,6 +219,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_TAS2562 imply SND_SOC_TAS2764 imply SND_SOC_TAS2770 + imply SND_SOC_TAS2780 imply SND_SOC_TAS5086 imply SND_SOC_TAS571X imply SND_SOC_TAS5720 @@ -1524,6 +1525,13 @@ config SND_SOC_TAS2770 tristate "Texas Instruments TAS2770 speaker amplifier" depends on I2C
+config SND_SOC_TAS2780 + tristate "Texas Instruments TAS2780 Mono Audio amplifier" + depends on I2C + help + Enable support for Texas Instruments TAS2780 high-efficiency mono + digital input Class-D audio power amplifiers. + config SND_SOC_TAS5086 tristate "Texas Instruments TAS5086 speaker amplifier" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 28dc4edfd01f..14273b68a7ec 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -346,6 +346,7 @@ snd-soc-tpa6130a2-objs := tpa6130a2.o snd-soc-tas2552-objs := tas2552.o snd-soc-tas2562-objs := tas2562.o snd-soc-tas2764-objs := tas2764.o +snd-soc-tas2780-objs := tas2780.o # Mux snd-soc-simple-mux-objs := simple-mux.o
@@ -589,6 +590,7 @@ obj-$(CONFIG_SND_SOC_STI_SAS) += snd-soc-sti-sas.o obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS2562) += snd-soc-tas2562.o obj-$(CONFIG_SND_SOC_TAS2764) += snd-soc-tas2764.o +obj-$(CONFIG_SND_SOC_TAS2780) += snd-soc-tas2780.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o diff --git a/sound/soc/codecs/tas2780.c b/sound/soc/codecs/tas2780.c new file mode 100644 index 000000000000..0e452c7464fb --- /dev/null +++ b/sound/soc/codecs/tas2780.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0 +// Driver for the Texas Instruments TAS2780 Mono +// Audio amplifier +// Copyright (C) 2022 Texas Instruments Inc. + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "tas2780.h" + +struct tas2780_priv { + struct snd_soc_component *component; + struct gpio_desc *reset_gpio; + struct gpio_desc *sdz_gpio; + struct regmap *regmap; + struct device *dev; + int v_sense_slot; + int i_sense_slot; + +}; + +static void tas2780_reset(struct tas2780_priv *tas2780) +{ + if (tas2780->reset_gpio) { + gpiod_set_value_cansleep(tas2780->reset_gpio, 0); + usleep_range(2000, 2050); + gpiod_set_value_cansleep(tas2780->reset_gpio, 1); + usleep_range(2000, 2050); + } + + snd_soc_component_write(tas2780->component, TAS2780_SW_RST, + TAS2780_RST); +} + +#ifdef CONFIG_PM +static int tas2780_codec_suspend(struct snd_soc_component *component) +{ + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + ret = snd_soc_component_update_bits(component, TAS2780_PWR_CTRL, + TAS2780_PWR_CTRL_MASK, + TAS2780_PWR_CTRL_SHUTDOWN); + if (ret < 0) { + pr_err("%s:errCode:0x%0x:power down error\n", + __func__, ret); + goto EXIT; + } + ret = 0; + if (tas2780->sdz_gpio) + gpiod_set_value_cansleep(tas2780->sdz_gpio, 0); + + regcache_cache_only(tas2780->regmap, true); + regcache_mark_dirty(tas2780->regmap); +EXIT: + return ret; +} + +static int tas2780_codec_resume(struct snd_soc_component *component) +{ + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + if (tas2780->sdz_gpio) + gpiod_set_value_cansleep(tas2780->sdz_gpio, 1); + + ret = snd_soc_component_update_bits(component, TAS2780_PWR_CTRL, + TAS2780_PWR_CTRL_MASK, + TAS2780_PWR_CTRL_ACTIVE); + + if (ret < 0) { + pr_err("%s:errCode:0x%0x:power down error\n", + __func__, ret); + goto EXIT; + } + ret = 0; + regcache_cache_only(tas2780->regmap, false); + ret = regcache_sync(tas2780->regmap); +EXIT: + return ret; +} +#endif + +static const char * const tas2780_ASI1_src[] = { + "I2C offset", "Left", "Right", "LeftRightDiv2", +}; + +static SOC_ENUM_SINGLE_DECL( + tas2780_ASI1_src_enum, TAS2780_TDM_CFG2, 4, tas2780_ASI1_src); + +static const struct snd_kcontrol_new tas2780_asi1_mux = + SOC_DAPM_ENUM("ASI1 Source", tas2780_ASI1_src_enum); + +static int tas2780_dac_event(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 tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + ret = snd_soc_component_update_bits(component, + TAS2780_PWR_CTRL, + TAS2780_PWR_CTRL_MASK, + TAS2780_PWR_CTRL_MUTE); + break; + case SND_SOC_DAPM_PRE_PMD: + ret = snd_soc_component_update_bits(component, + TAS2780_PWR_CTRL, + TAS2780_PWR_CTRL_MASK, + TAS2780_PWR_CTRL_SHUTDOWN); + break; + default: + dev_err(tas2780->dev, "Unsupported event\n"); + ret = -EINVAL; + } + if (ret < 0) { + pr_err("%s:errCode:0x%0x:PWR_CTRL error\n", + __func__, ret); + } else { + ret = 0; + } + + return ret; +} + +static const struct snd_kcontrol_new isense_switch = + SOC_DAPM_SINGLE("Switch", TAS2780_PWR_CTRL, + TAS2780_ISENSE_POWER_EN, 1, 1); +static const struct snd_kcontrol_new vsense_switch = + SOC_DAPM_SINGLE("Switch", TAS2780_PWR_CTRL, + TAS2780_VSENSE_POWER_EN, 1, 1); + +static const struct snd_soc_dapm_widget tas2780_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, &tas2780_asi1_mux), + SND_SOC_DAPM_SWITCH("ISENSE", TAS2780_PWR_CTRL, + TAS2780_ISENSE_POWER_EN, 1, &isense_switch), + SND_SOC_DAPM_SWITCH("VSENSE", TAS2780_PWR_CTRL, + TAS2780_VSENSE_POWER_EN, 1, &vsense_switch), + SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2780_dac_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_OUTPUT("OUT"), + SND_SOC_DAPM_SIGGEN("VMON"), + SND_SOC_DAPM_SIGGEN("IMON") +}; + +static const struct snd_soc_dapm_route tas2780_audio_map[] = { + {"ASI1 Sel", "I2C offset", "ASI1"}, + {"ASI1 Sel", "Left", "ASI1"}, + {"ASI1 Sel", "Right", "ASI1"}, + {"ASI1 Sel", "LeftRightDiv2", "ASI1"}, + {"DAC", NULL, "ASI1 Sel"}, + {"OUT", NULL, "DAC"}, + {"ISENSE", "Switch", "IMON"}, + {"VSENSE", "Switch", "VMON"}, +}; + +static int tas2780_mute(struct snd_soc_dai *dai, int mute, int direction) +{ + struct snd_soc_component *component = dai->component; + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int ret = 0; + + if (!mute) { + ret = snd_soc_component_update_bits(component, + TAS2780_CLK_CFG, TAS2780_CLK_CFG_MASK, + TAS2780_CLK_CFG_ENABLE); + + if (ret < 0) { + dev_err(tas2780->dev, + "%s: Failed to CLK_CFG_ENABLE\n", + __func__); + goto EXIT; + } + } + ret = snd_soc_component_update_bits(component, TAS2780_PWR_CTRL, + TAS2780_PWR_CTRL_MASK, + mute ? TAS2780_PWR_CTRL_MUTE : 0); + if (ret < 0) { + dev_err(tas2780->dev, "%s: Failed to set powercontrol\n", + __func__); + goto EXIT; + } + ret = 0; +EXIT: + return ret; +} + +static int tas2780_set_bitwidth(struct tas2780_priv *tas2780, int bitwidth) +{ + struct snd_soc_component *component = tas2780->component; + int sense_en; + int val; + int ret; + int slot_size; + + switch (bitwidth) { + case SNDRV_PCM_FORMAT_S16_LE: + ret = snd_soc_component_update_bits(component, + TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_RXW_MASK, + TAS2780_TDM_CFG2_RXW_16BITS); + slot_size = TAS2780_TDM_CFG2_RXS_16BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + ret = snd_soc_component_update_bits(component, + TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_RXW_MASK, + TAS2780_TDM_CFG2_RXW_24BITS); + slot_size = TAS2780_TDM_CFG2_RXS_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + ret = snd_soc_component_update_bits(component, + TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_RXW_MASK, + TAS2780_TDM_CFG2_RXW_32BITS); + slot_size = TAS2780_TDM_CFG2_RXS_32BITS; + break; + + default: + ret = -EINVAL; + } + + if (ret < 0) { + pr_err("%s:errCode:0x%x set bitwidth error\n", + __func__, ret); + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_RXS_MASK, slot_size); + if (ret < 0) { + pr_err("%s:errCode:0x%x set RX slot size error\n", + __func__, ret); + goto EXIT; + } + + val = snd_soc_component_read(tas2780->component, TAS2780_PWR_CTRL); + if (val < 0) { + pr_err("%s:errCode:0x%x read PWR_CTRL error\n", + __func__, val); + ret = val; + goto EXIT; + } + + if (val & (1 << TAS2780_VSENSE_POWER_EN)) + sense_en = 0; + else + sense_en = TAS2780_TDM_CFG5_VSNS_ENABLE; + + ret = snd_soc_component_update_bits(tas2780->component, + TAS2780_TDM_CFG5, TAS2780_TDM_CFG5_VSNS_ENABLE, sense_en); + if (ret < 0) { + pr_err("%s:errCode:0x%x enable vSNS error\n", + __func__, ret); + goto EXIT; + } + + if (val & (1 << TAS2780_ISENSE_POWER_EN)) + sense_en = 0; + else + sense_en = TAS2780_TDM_CFG6_ISNS_ENABLE; + + ret = snd_soc_component_update_bits(tas2780->component, + TAS2780_TDM_CFG6, TAS2780_TDM_CFG6_ISNS_ENABLE, sense_en); + if (ret < 0) { + pr_err("%s:errCode:0x%x enable iSNS error\n", + __func__, ret); + } + ret = 0; +EXIT: + return ret; +} + +static int tas2780_set_samplerate( + struct tas2780_priv *tas2780, int samplerate) +{ + struct snd_soc_component *component = tas2780->component; + int ramp_rate_val; + int ret; + + switch (samplerate) { + case 48000: + ramp_rate_val = TAS2780_TDM_CFG0_SMP_48KHZ | + TAS2780_TDM_CFG0_44_1_48KHZ; + break; + case 44100: + ramp_rate_val = TAS2780_TDM_CFG0_SMP_44_1KHZ | + TAS2780_TDM_CFG0_44_1_48KHZ; + break; + case 96000: + ramp_rate_val = TAS2780_TDM_CFG0_SMP_48KHZ | + TAS2780_TDM_CFG0_88_2_96KHZ; + break; + case 88200: + ramp_rate_val = TAS2780_TDM_CFG0_SMP_44_1KHZ | + TAS2780_TDM_CFG0_88_2_96KHZ; + break; + default: + return -EINVAL; + } + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG0, + TAS2780_TDM_CFG0_SMP_MASK | TAS2780_TDM_CFG0_MASK, + ramp_rate_val); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set ramp_rate_val\n", + __func__, ret); + goto EXIT; + } + ret = 0; +EXIT: + return ret; +} + +static int tas2780_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int ret; + + ret = tas2780_set_bitwidth(tas2780, params_format(params)); + if (ret < 0) + return ret; + + return tas2780_set_samplerate(tas2780, params_rate(params)); +} + +static int tas2780_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_component *component = dai->component; + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + u8 tdm_rx_start_slot = 0, asi_cfg_1 = 0; + int iface; + int ret = 0; + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + asi_cfg_1 = TAS2780_TDM_CFG1_RX_RISING; + break; + case SND_SOC_DAIFMT_IB_NF: + asi_cfg_1 = TAS2780_TDM_CFG1_RX_FALLING; + break; + default: + dev_err(tas2780->dev, "ASI format Inverse is not found\n"); + return -EINVAL; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG1, + TAS2780_TDM_CFG1_RX_MASK, asi_cfg_1); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set asi_cfg_1\n", + __func__, ret); + goto EXIT; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_DSP_A: + iface = TAS2780_TDM_CFG2_SCFG_I2S; + tdm_rx_start_slot = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_LEFT_J: + iface = TAS2780_TDM_CFG2_SCFG_LEFT_J; + tdm_rx_start_slot = 0; + break; + default: + pr_err("%s:DAI Format is not found, fmt=0x%x\n", + __func__, fmt); + ret = -EINVAL; + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG1, + TAS2780_TDM_CFG1_MASK, + (tdm_rx_start_slot << TAS2780_TDM_CFG1_51_SHIFT)); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set tdm_rx_start_slot\n", + __func__, ret); + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_SCFG_MASK, iface); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set iface\n", + __func__, ret); + goto EXIT; + } + ret = 0; +EXIT: + return ret; +} + +static int tas2780_set_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + int left_slot, right_slot; + int slots_cfg; + int slot_size; + int ret = 0; + + if (tx_mask == 0 || rx_mask != 0) + return -EINVAL; + + if (slots == 1) { + if (tx_mask != 1) + return -EINVAL; + left_slot = 0; + right_slot = 0; + } else { + left_slot = __ffs(tx_mask); + tx_mask &= ~(1 << left_slot); + if (tx_mask == 0) { + right_slot = left_slot; + } else { + right_slot = __ffs(tx_mask); + tx_mask &= ~(1 << right_slot); + } + } + + if (tx_mask != 0 || left_slot >= slots || right_slot >= slots) + return -EINVAL; + + slots_cfg = (right_slot << TAS2780_TDM_CFG3_RXS_SHIFT) | left_slot; + ret = snd_soc_component_write(component, TAS2780_TDM_CFG3, slots_cfg); + if (ret) { + pr_err("%s:errCode:0x%x Failed to set slots_cfg\n", + __func__, ret); + goto EXIT; + } + + switch (slot_width) { + case 16: + slot_size = TAS2780_TDM_CFG2_RXS_16BITS; + break; + case 24: + slot_size = TAS2780_TDM_CFG2_RXS_24BITS; + break; + case 32: + slot_size = TAS2780_TDM_CFG2_RXS_32BITS; + break; + default: + ret = -EINVAL; + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG2, + TAS2780_TDM_CFG2_RXS_MASK, slot_size); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set slot_size\n", + __func__, ret); + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG5, + TAS2780_TDM_CFG5_50_MASK, tas2780->v_sense_slot); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set v_sense_slot\n", + __func__, ret); + goto EXIT; + } + + ret = snd_soc_component_update_bits(component, TAS2780_TDM_CFG6, + TAS2780_TDM_CFG6_50_MASK, tas2780->i_sense_slot); + if (ret < 0) { + pr_err("%s:errCode:0x%x Failed to set i_sense_slot\n", + __func__, ret); + goto EXIT; + } + ret = 0; +EXIT: + return ret; +} + +static const struct snd_soc_dai_ops tas2780_dai_ops = { + .mute_stream = tas2780_mute, + .hw_params = tas2780_hw_params, + .set_fmt = tas2780_set_fmt, + .set_tdm_slot = tas2780_set_dai_tdm_slot, + .no_capture_mute = 1, +}; + +#define TAS2780_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +#define TAS2780_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_88200) + +static struct snd_soc_dai_driver tas2780_dai_driver[] = { + { + .name = "tas2780 ASI1", + .id = 0, + .playback = { + .stream_name = "ASI1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = TAS2780_RATES, + .formats = TAS2780_FORMATS, + }, + .capture = { + .stream_name = "ASI1 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = TAS2780_RATES, + .formats = TAS2780_FORMATS, + }, + .ops = &tas2780_dai_ops, + .symmetric_rate = 1, + }, +}; + +static int tas2780_codec_probe(struct snd_soc_component *component) +{ + struct tas2780_priv *tas2780 = + snd_soc_component_get_drvdata(component); + + tas2780->component = component; + + if (tas2780->sdz_gpio) + gpiod_set_value_cansleep(tas2780->sdz_gpio, 1); + + tas2780_reset(tas2780); + + return 0; +} + +static DECLARE_TLV_DB_SCALE(tas2780_digital_tlv, 1100, 50, 0); +static DECLARE_TLV_DB_SCALE(tas2780_playback_volume, -10000, 50, 0); + +static const struct snd_kcontrol_new tas2780_snd_controls[] = { + SOC_SINGLE_TLV("Speaker Volume", TAS2780_DVC, 0, + TAS2780_DVC_MAX, 1, tas2780_playback_volume), + SOC_SINGLE_TLV("Amp Gain Volume", TAS2780_CHNL_0, 0, 0x14, 0, + tas2780_digital_tlv), +}; + +static const struct snd_soc_component_driver soc_component_driver_tas2780 = { + .probe = tas2780_codec_probe, +#ifdef CONFIG_PM + .suspend = tas2780_codec_suspend, + .resume = tas2780_codec_resume, +#endif + .controls = tas2780_snd_controls, + .num_controls = ARRAY_SIZE(tas2780_snd_controls), + .dapm_widgets = tas2780_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas2780_dapm_widgets), + .dapm_routes = tas2780_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas2780_audio_map), + .idle_bias_on = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static const struct reg_default tas2780_reg_defaults[] = { + { TAS2780_PAGE, 0x00 }, + { TAS2780_SW_RST, 0x00 }, + { TAS2780_PWR_CTRL, 0x1a }, + { TAS2780_DVC, 0x00 }, + { TAS2780_CHNL_0, 0x00 }, + { TAS2780_TDM_CFG0, 0x09 }, + { TAS2780_TDM_CFG1, 0x02 }, + { TAS2780_TDM_CFG2, 0x0a }, + { TAS2780_TDM_CFG3, 0x10 }, + { TAS2780_TDM_CFG5, 0x42 }, +}; + +static const struct regmap_range_cfg tas2780_regmap_ranges[] = { + { + .range_min = 0, + .range_max = 1 * 128, + .selector_reg = TAS2780_PAGE, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 128, + }, +}; + +static bool tas2780_volatile(struct device *dev, + unsigned int reg) +{ + return true; +} + +static const struct regmap_config tas2780_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .reg_defaults = tas2780_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas2780_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .ranges = tas2780_regmap_ranges, + .num_ranges = ARRAY_SIZE(tas2780_regmap_ranges), + .max_register = 1 * 128, + .volatile_reg = tas2780_volatile, +}; + +static int tas2780_parse_dt(struct device *dev, struct tas2780_priv *tas2780) +{ + int ret = 0; + + tas2780->reset_gpio = devm_gpiod_get_optional(tas2780->dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(tas2780->reset_gpio)) { + if (PTR_ERR(tas2780->reset_gpio) == -EPROBE_DEFER) { + tas2780->reset_gpio = NULL; + return -EPROBE_DEFER; + } + } + + tas2780->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", + GPIOD_OUT_HIGH); + if (IS_ERR(tas2780->sdz_gpio)) { + if (PTR_ERR(tas2780->sdz_gpio) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + tas2780->sdz_gpio = NULL; + } + + ret = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no", + &tas2780->i_sense_slot); + if (ret) + tas2780->i_sense_slot = 0; + + ret = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no", + &tas2780->v_sense_slot); + if (ret) + tas2780->v_sense_slot = 2; + + return 0; +} + +static int tas2780_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tas2780_priv *tas2780; + int result; + + tas2780 = devm_kzalloc(&client->dev, sizeof(struct tas2780_priv), + GFP_KERNEL); + if (!tas2780) + return -ENOMEM; + tas2780->dev = &client->dev; + i2c_set_clientdata(client, tas2780); + dev_set_drvdata(&client->dev, tas2780); + + tas2780->regmap = devm_regmap_init_i2c(client, &tas2780_i2c_regmap); + if (IS_ERR(tas2780->regmap)) { + result = PTR_ERR(tas2780->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + result); + return result; + } + + if (client->dev.of_node) { + result = tas2780_parse_dt(&client->dev, tas2780); + if (result) { + dev_err(tas2780->dev, + "%s: Failed to parse devicetree\n", __func__); + return result; + } + } + + return devm_snd_soc_register_component(tas2780->dev, + &soc_component_driver_tas2780, tas2780_dai_driver, + ARRAY_SIZE(tas2780_dai_driver)); +} + +static const struct i2c_device_id tas2780_i2c_id[] = { + { "tas2780", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas2780_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id tas2780_of_match[] = { + { .compatible = "ti,tas2780" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tas2780_of_match); +#endif + +static struct i2c_driver tas2780_i2c_driver = { + .driver = { + .name = "tas2780", + .of_match_table = of_match_ptr(tas2780_of_match), + }, + .probe = tas2780_i2c_probe, + .id_table = tas2780_i2c_id, +}; +module_i2c_driver(tas2780_i2c_driver); + +MODULE_AUTHOR("Raphael Xu raphael-xu@ti.com"); +MODULE_DESCRIPTION("TAS2780 I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas2780.h b/sound/soc/codecs/tas2780.h new file mode 100644 index 000000000000..470ae24facd6 --- /dev/null +++ b/sound/soc/codecs/tas2780.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * TAS2780.h - ALSA SoC Texas Instruments TAS2780 Mono Audio Amplifier + * + * Copyright (C) 2020-2022 Texas Instruments Incorporated - https://www.ti.com + * + * Author: Raphael Xu raphael-xu@ti.com + */ + +#ifndef __TAS2780_H__ +#define __TAS2780_H__ + +/* Book Control Register */ +#define TAS2780_BOOKCTL_PAGE 0 +#define TAS2780_BOOKCTL_REG 127 +#define TAS2780_REG(page, reg) ((page * 128) + reg) + +/* Page */ +#define TAS2780_PAGE TAS2780_REG(0X0, 0x00) +#define TAS2780_PAGE_PAGE_MASK 255 + +/* Software Reset */ +#define TAS2780_SW_RST TAS2780_REG(0X0, 0x01) +#define TAS2780_RST BIT(0) + +/* Power Control */ +#define TAS2780_PWR_CTRL TAS2780_REG(0X0, 0x02) +#define TAS2780_PWR_CTRL_MASK GENMASK(1, 0) +#define TAS2780_PWR_CTRL_ACTIVE 0x0 +#define TAS2780_PWR_CTRL_MUTE BIT(0) +#define TAS2780_PWR_CTRL_SHUTDOWN BIT(1) + +#define TAS2780_VSENSE_POWER_EN 3 +#define TAS2780_ISENSE_POWER_EN 4 + +/* Digital Volume Control */ +#define TAS2780_DVC TAS2780_REG(0X0, 0x1a) +#define TAS2780_DVC_MAX 0xc9 + +#define TAS2780_CHNL_0 TAS2780_REG(0X0, 0x03) + +/* TDM Configuration Reg0 */ +#define TAS2780_TDM_CFG0 TAS2780_REG(0X0, 0x08) +#define TAS2780_TDM_CFG0_SMP_MASK BIT(5) +#define TAS2780_TDM_CFG0_SMP_48KHZ 0x0 +#define TAS2780_TDM_CFG0_SMP_44_1KHZ BIT(5) +#define TAS2780_TDM_CFG0_MASK GENMASK(3, 1) +#define TAS2780_TDM_CFG0_44_1_48KHZ BIT(3) +#define TAS2780_TDM_CFG0_88_2_96KHZ (BIT(3) | BIT(1)) + +/* TDM Configuration Reg1 */ +#define TAS2780_TDM_CFG1 TAS2780_REG(0X0, 0x09) +#define TAS2780_TDM_CFG1_MASK GENMASK(5, 1) +#define TAS2780_TDM_CFG1_51_SHIFT 1 +#define TAS2780_TDM_CFG1_RX_MASK BIT(0) +#define TAS2780_TDM_CFG1_RX_RISING 0x0 +#define TAS2780_TDM_CFG1_RX_FALLING BIT(0) + +/* TDM Configuration Reg2 */ +#define TAS2780_TDM_CFG2 TAS2780_REG(0X0, 0x0a) +#define TAS2780_TDM_CFG2_RXW_MASK GENMASK(3, 2) +#define TAS2780_TDM_CFG2_RXW_16BITS 0x0 +#define TAS2780_TDM_CFG2_RXW_24BITS BIT(3) +#define TAS2780_TDM_CFG2_RXW_32BITS (BIT(3) | BIT(2)) +#define TAS2780_TDM_CFG2_RXS_MASK GENMASK(1, 0) +#define TAS2780_TDM_CFG2_RXS_16BITS 0x0 +#define TAS2780_TDM_CFG2_RXS_24BITS BIT(0) +#define TAS2780_TDM_CFG2_RXS_32BITS BIT(1) +#define TAS2780_TDM_CFG2_SCFG_MASK GENMASK(5, 4) +#define TAS2780_TDM_CFG2_SCFG_I2S 0x0 +#define TAS2780_TDM_CFG2_SCFG_LEFT_J BIT(4) +#define TAS2780_TDM_CFG2_SCFG_RIGHT_J BIT(5) + +/* TDM Configuration Reg3 */ +#define TAS2780_TDM_CFG3 TAS2780_REG(0X0, 0x0c) +#define TAS2780_TDM_CFG3_RXS_MASK GENMASK(7, 4) +#define TAS2780_TDM_CFG3_RXS_SHIFT 0x4 +#define TAS2780_TDM_CFG3_MASK GENMASK(3, 0) + +/* TDM Configuration Reg4 */ +#define TAS2780_TDM_CFG4 TAS2780_REG(0X0, 0x0d) +#define TAS2780_TDM_CFG4_TX_OFFSET_MASK GENMASK(3, 1) + +/* TDM Configuration Reg5 */ +#define TAS2780_TDM_CFG5 TAS2780_REG(0X0, 0x0e) +#define TAS2780_TDM_CFG5_VSNS_MASK BIT(6) +#define TAS2780_TDM_CFG5_VSNS_ENABLE BIT(6) +#define TAS2780_TDM_CFG5_50_MASK GENMASK(5, 0) + +/* TDM Configuration Reg6 */ +#define TAS2780_TDM_CFG6 TAS2780_REG(0X0, 0x0f) +#define TAS2780_TDM_CFG6_ISNS_MASK BIT(6) +#define TAS2780_TDM_CFG6_ISNS_ENABLE BIT(6) +#define TAS2780_TDM_CFG6_50_MASK GENMASK(5, 0) + +/* INT&CLK CFG */ +#define TAS2780_CLK_CFG TAS2780_REG(0X0, 0x5c) +#define TAS2780_CLK_CFG_MASK GENMASK(7, 6) +#define TAS2780_CLK_CFG_ENABLE (BIT(7) | BIT(6)) + +#endif /* __TAS2780_H__ */
On Mon, Jul 04, 2022 at 06:47:59PM +0800, Raphael-Xu wrote:
1.update Kconfig and Makefile 2.add tas2780.c and tas2780.h
This looks pretty good, there's some mostly stylistic things below but they're all fairly minor and you also need to provide documentation for the DT binding.
snd-soc-tas2562-objs := tas2562.o snd-soc-tas2764-objs := tas2764.o +snd-soc-tas2780-objs := tas2780.o # Mux
Please preserve these blank lines here.
- ret = snd_soc_component_update_bits(component, TAS2780_PWR_CTRL,
TAS2780_PWR_CTRL_MASK,
TAS2780_PWR_CTRL_SHUTDOWN);
- if (ret < 0) {
pr_err("%s:errCode:0x%0x:power down error\n",
__func__, ret);
dev_err(), and the style used in the log message doesn't really match the typical kernel style at all.
goto EXIT;
Labels should generally be lower case.
+static int tas2780_dac_event(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 tas2780_priv *tas2780 =
snd_soc_component_get_drvdata(component);
- int ret = 0;
- switch (event) {
- case SND_SOC_DAPM_POST_PMU:
ret = snd_soc_component_update_bits(component,
TAS2780_PWR_CTRL,
TAS2780_PWR_CTRL_MASK,
TAS2780_PWR_CTRL_MUTE);
break;
- case SND_SOC_DAPM_PRE_PMD:
ret = snd_soc_component_update_bits(component,
TAS2780_PWR_CTRL,
TAS2780_PWR_CTRL_MASK,
TAS2780_PWR_CTRL_SHUTDOWN);
break;
This looks like it should perhaps be a mute_stream operation while...
+static int tas2780_mute(struct snd_soc_dai *dai, int mute, int direction) +{
- struct snd_soc_component *component = dai->component;
- struct tas2780_priv *tas2780 =
snd_soc_component_get_drvdata(component);
- int ret = 0;
- if (!mute) {
ret = snd_soc_component_update_bits(component,
TAS2780_CLK_CFG, TAS2780_CLK_CFG_MASK,
TAS2780_CLK_CFG_ENABLE);
if (ret < 0) {
dev_err(tas2780->dev,
"%s: Failed to CLK_CFG_ENABLE\n",
__func__);
goto EXIT;
}
- }
- ret = snd_soc_component_update_bits(component, TAS2780_PWR_CTRL,
TAS2780_PWR_CTRL_MASK,
mute ? TAS2780_PWR_CTRL_MUTE : 0);
...this is managing clocks which doesn't look like what I'd expect for a mute operation, that should probably be part of the power management (either a DAPM supply or in the bias level handling)?
- switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
- case SND_SOC_DAIFMT_I2S:
- case SND_SOC_DAIFMT_DSP_A:
iface = TAS2780_TDM_CFG2_SCFG_I2S;
tdm_rx_start_slot = 1;
break;
- case SND_SOC_DAIFMT_DSP_B:
- case SND_SOC_DAIFMT_LEFT_J:
iface = TAS2780_TDM_CFG2_SCFG_LEFT_J;
tdm_rx_start_slot = 0;
break;
This doesn't seem right - it's using exactly the same configuration for multiple DAI formats.
+static bool tas2780_volatile(struct device *dev,
- unsigned int reg)
+{
return true;
+}
Just don't specify a cache.
+static int tas2780_parse_dt(struct device *dev, struct tas2780_priv *tas2780) +{
- int ret = 0;
- tas2780->reset_gpio = devm_gpiod_get_optional(tas2780->dev, "reset",
GPIOD_OUT_HIGH);
- if (IS_ERR(tas2780->reset_gpio)) {
if (PTR_ERR(tas2780->reset_gpio) == -EPROBE_DEFER) {
tas2780->reset_gpio = NULL;
return -EPROBE_DEFER;
}
- }
This has a DT binding but there's no DT binding document, any new DT bindings need to be documented.
On 7/4/2022 12:47 PM, Raphael-Xu wrote:
1.update Kconfig and Makefile 2.add tas2780.c and tas2780.h
Signed-off-by: Raphael-Xu 13691752556@139.com
...
diff --git a/sound/soc/codecs/tas2780.c b/sound/soc/codecs/tas2780.c new file mode 100644 index 000000000000..0e452c7464fb --- /dev/null +++ b/sound/soc/codecs/tas2780.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0 +// Driver for the Texas Instruments TAS2780 Mono +// Audio amplifier +// Copyright (C) 2022 Texas Instruments Inc.
+#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/gpio/consumer.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <sound/soc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/tlv.h>
+#include "tas2780.h"
+struct tas2780_priv {
- struct snd_soc_component *component;
- struct gpio_desc *reset_gpio;
- struct gpio_desc *sdz_gpio;
- struct regmap *regmap;
- struct device *dev;
- int v_sense_slot;
- int i_sense_slot;
Unnecessary empty line?
+};
+static void tas2780_reset(struct tas2780_priv *tas2780) +{
- if (tas2780->reset_gpio) {
gpiod_set_value_cansleep(tas2780->reset_gpio, 0);
usleep_range(2000, 2050);
gpiod_set_value_cansleep(tas2780->reset_gpio, 1);
usleep_range(2000, 2050);
- }
- snd_soc_component_write(tas2780->component, TAS2780_SW_RST,
TAS2780_RST);
+}
...
+static const struct snd_soc_component_driver soc_component_driver_tas2780 = {
- .probe = tas2780_codec_probe,
+#ifdef CONFIG_PM
- .suspend = tas2780_codec_suspend,
- .resume = tas2780_codec_resume,
+#endif
- .controls = tas2780_snd_controls,
- .num_controls = ARRAY_SIZE(tas2780_snd_controls),
- .dapm_widgets = tas2780_dapm_widgets,
- .num_dapm_widgets = ARRAY_SIZE(tas2780_dapm_widgets),
- .dapm_routes = tas2780_audio_map,
- .num_dapm_routes = ARRAY_SIZE(tas2780_audio_map),
- .idle_bias_on = 1,
- .endianness = 1,
- .non_legacy_dai_naming = 1,
From what I see change removing non_legacy_dai_naming field is merged in for-next branch, so you can skip it, as it is default now.
+};
+static const struct reg_default tas2780_reg_defaults[] = {
- { TAS2780_PAGE, 0x00 },
- { TAS2780_SW_RST, 0x00 },
- { TAS2780_PWR_CTRL, 0x1a },
- { TAS2780_DVC, 0x00 },
- { TAS2780_CHNL_0, 0x00 },
- { TAS2780_TDM_CFG0, 0x09 },
- { TAS2780_TDM_CFG1, 0x02 },
- { TAS2780_TDM_CFG2, 0x0a },
- { TAS2780_TDM_CFG3, 0x10 },
- { TAS2780_TDM_CFG5, 0x42 },
+};
participants (3)
-
Amadeusz Sławiński
-
Mark Brown
-
Raphael-Xu