diff mbox series

[v1] add tas2780

Message ID 20220704104759.21083-1-13691752556@139.com (mailing list archive)
State Superseded
Headers show
Series [v1] add tas2780 | expand

Commit Message

Raphael-Xu July 4, 2022, 10:47 a.m. UTC
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

Comments

Mark Brown July 4, 2022, 11:43 a.m. UTC | #1
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.
Amadeusz Sławiński July 4, 2022, 12:30 p.m. UTC | #2
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 },
> +};
> +
diff mbox series

Patch

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__ */