All of lore.kernel.org
 help / color / mirror / Atom feed
From: Thomas Preston <thomas.preston@codethink.co.uk>
To: Liam Girdwood <lgirdwood@gmail.com>,
	Mark Brown <broonie@kernel.org>, Rob Herring <robh+dt@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
	Charles Keepax <ckeepax@opensource.cirrus.com>,
	Jerome Brunet <jbrunet@baylibre.com>,
	Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
	Marco Felsch <m.felsch@pengutronix.de>,
	Paul Cercueil <paul@crapouillou.net>,
	Kirill Marinushkin <kmarinushkin@birdec.tech>,
	Cheng-Yi Chiang <cychiang@chromium.org>,
	Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>,
	Vinod Koul <vkoul@kernel.org>,
	Annaliese McDermond <nh6z@nh6z.net>,
	Thomas Preston <thomas.preston@codethink.co.uk>,
	alsa-devel@alsa-project.org, devicetree@vger.kernel.org,
	linux-kernel@vger.kernel.org
Cc: Patrick Glaser <pglaser@tesla.com>,
	Rob Duncan <rduncan@tesla.com>, Nate Case <ncase@tesla.com>
Subject: [PATCH v2 2/3] ASoC: Add codec driver for ST TDA7802
Date: Tue, 30 Jul 2019 13:09:36 +0100	[thread overview]
Message-ID: <20190730120937.16271-3-thomas.preston@codethink.co.uk> (raw)
In-Reply-To: <20190730120937.16271-1-thomas.preston@codethink.co.uk>

Add an I2C based codec driver for ST TDA7802 amplifier. The amplifier
supports 4 audio channels but can support up to 16 with multiple
devices.

Signed-off-by: Thomas Preston <thomas.preston@codethink.co.uk>
Cc: Patrick Glaser <pglaser@tesla.com>
Cc: Rob Duncan <rduncan@tesla.com>
Cc: Nate Case <ncase@tesla.com>
---
Changes since v1:
- Use ALSA kcontrol interface to expose device controls to userland
	- Gain
	- Channel diagnostic mode
	- Impedance efficiency optimiser. I decided against setting this
	  as a DT property since it seems like something that can be
	  changed on the fly.
- Add regmap default values
	- Channel unmute by default is added in a downstream patch.
	- I'm not sure if I should keep this since they're all zero,
	  although there are other drivers will all-zero reg_defaults.
- I believe the "//" style is used for SPDX headers in normal C source files.
  https://lwn.net/Articles/739183/
- Drop the "enable" sysfs device attribute.
- Don't set TDM format using magic numbers.
- Set sample rate using hw_params.
- Remove unecessary defines.
- Use DAPM to handle AMP_ON.
- Cosmetic fixups

 sound/soc/codecs/Kconfig   |   6 +
 sound/soc/codecs/Makefile  |   2 +
 sound/soc/codecs/tda7802.c | 509 +++++++++++++++++++++++++++++++++++++
 3 files changed, 517 insertions(+)
 create mode 100644 sound/soc/codecs/tda7802.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9f89a5346299..7e3117eab735 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -182,6 +182,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_TAS5720 if I2C
 	select SND_SOC_TAS6424 if I2C
 	select SND_SOC_TDA7419 if I2C
+	select SND_SOC_TDA7802 if I2C
 	select SND_SOC_TFA9879 if I2C
 	select SND_SOC_TLV320AIC23_I2C if I2C
 	select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
@@ -1121,6 +1122,11 @@ config SND_SOC_TDA7419
 	depends on I2C
 	select REGMAP_I2C
 
+config SND_SOC_TDA7802
+	tristate "ST TDA7802 audio processor"
+	depends on I2C
+	select REGMAP_I2C
+
 config SND_SOC_TFA9879
 	tristate "NXP Semiconductors TFA9879 amplifier"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5b4bb8cf4325..31dec8930e98 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -194,6 +194,7 @@ snd-soc-tas571x-objs := tas571x.o
 snd-soc-tas5720-objs := tas5720.o
 snd-soc-tas6424-objs := tas6424.o
 snd-soc-tda7419-objs := tda7419.o
+snd-soc-tda7802-objs := tda7802.o
 snd-soc-tfa9879-objs := tfa9879.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -474,6 +475,7 @@ obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o
 obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o
 obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o
 obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o
+obj-$(CONFIG_SND_SOC_TDA7802)	+= snd-soc-tda7802.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)	+= snd-soc-tlv320aic23.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C)	+= snd-soc-tlv320aic23-i2c.o
diff --git a/sound/soc/codecs/tda7802.c b/sound/soc/codecs/tda7802.c
new file mode 100644
index 000000000000..0f82a88bc1a4
--- /dev/null
+++ b/sound/soc/codecs/tda7802.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tda7802.c  --  codec driver for ST TDA7802
+ *
+ * Copyright (C) 2016-2019 Tesla Motors, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#define ENABLE_DELAY_MS			10
+
+#define TDA7802_IB0			0x00
+#define TDA7802_IB1			0x01
+#define TDA7802_IB2			0x02
+#define TDA7802_IB3			0x03
+#define TDA7802_IB4			0x04
+#define TDA7802_IB5			0x05
+
+#define TDA7802_DB0			0x10
+#define TDA7802_DB5			0x15
+
+#define IB2_DIGITAL_MUTE_DISABLED	(1 << 2)
+
+#define IB3_SAMPLE_RATE_SHIFT		6
+#define IB3_SAMPLE_RATE_MASK		(3 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_44_KHZ		(0 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_48_KHZ		(1 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_96_KHZ		(2 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_192_KHZ		(3 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_FORMAT_SHIFT		3
+#define IB3_FORMAT_MASK			(7 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_I2S			(0 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM4			(1 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM8_DEV1		(2 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM8_DEV2		(3 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV1		(4 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV2		(5 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV3		(6 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV4		(7 << IB3_FORMAT_SHIFT)
+
+enum tda7802_type {
+	tda7802_base,
+};
+
+struct tda7802_priv {
+	struct i2c_client *i2c;
+	struct regmap *regmap;
+	struct regulator *enable_reg;
+};
+
+static const struct reg_default tda7802_reg[] = {
+	{ TDA7802_IB0, 0x0 },
+	{ TDA7802_IB1, 0x0 },
+	{ TDA7802_IB2, 0x0 },
+	{ TDA7802_IB3, 0x0 },
+	{ TDA7802_IB4, 0x0 },
+	{ TDA7802_IB5, 0x0 },
+};
+
+static bool tda7802_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_IB0 ... TDA7802_IB5:
+	case TDA7802_DB0 ... TDA7802_DB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool tda7802_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_DB0 ... TDA7802_DB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool tda7802_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_IB0 ... TDA7802_IB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config tda7802_regmap_config = {
+	.val_bits = 8,
+	.reg_bits = 8,
+	.max_register = TDA7802_DB5,
+	.use_single_read = 1,
+	.use_single_write = 1,
+
+	.readable_reg = tda7802_readable_reg,
+	.volatile_reg = tda7802_volatile_reg,
+	.writeable_reg = tda7802_writeable_reg,
+
+	.reg_defaults = tda7802_reg,
+	.num_reg_defaults = ARRAY_SIZE(tda7802_reg),
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int tda7802_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	const u8 mute_disabled = mute ? 0 : IB2_DIGITAL_MUTE_DISABLED;
+	struct device *dev = dai->dev;
+	int err;
+
+	dev_dbg(dev, "%s mute=%d\n", __func__, mute);
+
+	err = snd_soc_component_update_bits(dai->component, TDA7802_IB2,
+			IB2_DIGITAL_MUTE_DISABLED, mute_disabled);
+	if (err < 0)
+		dev_err(dev, "Cannot mute amp %d\n", err);
+
+	return err;
+}
+
+static int tda7802_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+		unsigned int rx_mask, int slots, int slot_width)
+{
+	struct device *dev = dai->dev;
+	u8 tdm_format;
+	int ret;
+
+	dev_dbg(dai->dev, "%s tx %x, rx %x, slots %d, slot_width %d\n",
+			__func__, tx_mask, rx_mask, slots, slot_width);
+
+	switch (slots) {
+	case 4:
+		tdm_format = IB3_FORMAT_TDM4;
+		break;
+	case 8:
+		switch (tx_mask) {
+		case 0x000f:
+			tdm_format = IB3_FORMAT_TDM8_DEV1;
+			break;
+		case 0x00f0:
+			tdm_format = IB3_FORMAT_TDM8_DEV2;
+			break;
+		default:
+			dev_err(dev,
+				"Failed to set tdm fmt, slots %d, tx_mask %x\n",
+				slots, tx_mask);
+			return -ENOTSUPP;
+		}
+		break;
+	case 16:
+		switch (tx_mask) {
+		case 0x000f:
+			tdm_format = IB3_FORMAT_TDM16_DEV1;
+			break;
+		case 0x00f0:
+			tdm_format = IB3_FORMAT_TDM16_DEV2;
+			break;
+		case 0x0f00:
+			tdm_format = IB3_FORMAT_TDM16_DEV3;
+			break;
+		case 0xf000:
+			tdm_format = IB3_FORMAT_TDM16_DEV4;
+			break;
+		default:
+			dev_err(dev,
+				"Failed to set tdm fmt, slots %d, tx_mask %x\n",
+				slots, tx_mask);
+			return -ENOTSUPP;
+		}
+		break;
+	default:
+		dev_err(dev, "Failed to set %d slots, supported: 4, 8, 16\n",
+				slots);
+		return -ENOTSUPP;
+	}
+
+	ret = snd_soc_component_update_bits(dai->component, TDA7802_IB3,
+			IB3_FORMAT_MASK, tdm_format);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write IB3 config %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tda7802_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	int err;
+	u8 val;
+
+	dev_dbg(dai->dev, "%s rate %d\n", __func__, params_rate(params));
+
+	switch (params_rate(params)) {
+	case 44100:
+		val = IB3_SAMPLE_RATE_44_KHZ;
+		break;
+	case 48000:
+		val = IB3_SAMPLE_RATE_48_KHZ;
+		break;
+	case 96000:
+		val = IB3_SAMPLE_RATE_96_KHZ;
+		break;
+	case 192000:
+		val = IB3_SAMPLE_RATE_192_KHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = snd_soc_component_update_bits(dai->component, TDA7802_IB3,
+			IB3_SAMPLE_RATE_MASK, val);
+	if (err < 0)
+		dev_err(dai->dev, "Could not set hw_params, %d\n", err);
+
+	return err;
+}
+
+static const struct snd_soc_dai_ops tda7802_dai_ops = {
+	.digital_mute = tda7802_digital_mute,
+	.hw_params = tda7802_hw_params,
+	.set_tdm_slot = tda7802_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver tda7802_dai_driver = {
+	.name = "tda7802",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 4,
+		.channels_max = 4,
+		.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.ops = &tda7802_dai_ops,
+};
+
+static int tda7802_set_bias_level(struct snd_soc_component *component,
+		enum snd_soc_bias_level level)
+{
+	const struct tda7802_priv *tda7802 =
+		snd_soc_component_get_drvdata(component);
+	struct snd_soc_dapm_context *dapm_context =
+			snd_soc_component_get_dapm(component);
+	const enum snd_soc_bias_level oldlevel =
+		snd_soc_dapm_get_bias_level(dapm_context);
+	int err = 0;
+
+	dev_dbg(component->dev, "%s level %d\n", __func__, level);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		err = regulator_enable(tda7802->enable_reg);
+		if (err < 0) {
+			dev_err(component->dev, "Could not enable.\n");
+			return err;
+		}
+		dev_dbg(component->dev, "Regulator enabled\n");
+		msleep(ENABLE_DELAY_MS);
+
+		if (oldlevel == SND_SOC_BIAS_OFF) {
+			dev_dbg(component->dev, "Syncing regcache\n");
+			err = regcache_sync(component->regmap);
+			if (err < 0)
+				dev_err(component->dev,
+					"Could not sync regcache, %d\n", err);
+		}
+		break;
+	case SND_SOC_BIAS_OFF:
+		regcache_mark_dirty(component->regmap);
+		err = regulator_disable(tda7802->enable_reg);
+		if (err < 0)
+			dev_err(component->dev, "Could not disable.\n");
+		break;
+	}
+
+	return err;
+}
+
+static const char * const amp_mode_str[] = {
+	"High Efficiency",
+	"Standard Class AB",
+};
+
+static SOC_ENUM_SINGLE_DECL(ch1_amp_mode, TDA7802_IB0, 0, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch2_amp_mode, TDA7802_IB0, 1, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch3_amp_mode, TDA7802_IB0, 2, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch4_amp_mode, TDA7802_IB0, 3, amp_mode_str);
+
+static const char * const zopt_str[] = {
+	"2ohm",
+	"4ohm",
+};
+
+static SOC_ENUM_SINGLE_DECL(zopt_ch24, TDA7802_IB1, 7, zopt_str);
+static SOC_ENUM_SINGLE_DECL(zopt_ch13, TDA7802_IB2, 0, zopt_str);
+
+static const char * const diag_timing_str[] = {
+	"default",
+	"x2",
+	"x4",
+	"x8",
+};
+
+static SOC_ENUM_SINGLE_DECL(diag_timing, TDA7802_IB1, 5, diag_timing_str);
+
+static const char * const mute_time_str[] = {
+	"1.45ms",
+	"5.8ms",
+	"11.6ms",
+	"23.2ms",
+	"46.4ms",
+	"92.8ms",
+	"185.6ms",
+	"371.2ms",
+};
+
+static SOC_ENUM_SINGLE_DECL(mute_time, TDA7802_IB2, 5, mute_time_str);
+
+static const char * const automute_threshold_str[] = {
+	"5.3V",
+	"7.3V",
+};
+
+static SOC_ENUM_SINGLE_DECL(automute_threshold, TDA7802_IB2, 1,
+		automute_threshold_str);
+
+static const char * const ac_diag_threshold_str[] = {
+	"High",
+	"Low",
+};
+
+static SOC_ENUM_SINGLE_DECL(ac_diag_threshold, TDA7802_IB3, 4,
+		ac_diag_threshold_str);
+
+static const char * const ch_diag_mode_str[] = {
+	"Speaker",
+	"Boosted",
+};
+
+static SOC_ENUM_SINGLE_DECL(diag_mode_ch13, TDA7802_IB4, 2, ch_diag_mode_str);
+static SOC_ENUM_SINGLE_DECL(diag_mode_ch24, TDA7802_IB4, 1, ch_diag_mode_str);
+
+static const char * const temp_warn_str[] = {
+	"TW1",
+	"TW2",
+	"TW3",
+	"TW4",
+	"None",
+};
+
+static SOC_ENUM_SINGLE_DECL(temp_warn, TDA7802_IB5, 5, temp_warn_str);
+
+static const char * const clip_detect_str[] = {
+	"2%",
+	"5%",
+	"10%",
+	"None",
+};
+
+static SOC_ENUM_SINGLE_DECL(clip_detect_ch13, TDA7802_IB5, 3, clip_detect_str);
+static SOC_ENUM_SINGLE_DECL(clip_detect_ch24, TDA7802_IB5, 1, clip_detect_str);
+
+static const struct snd_kcontrol_new tda7802_snd_controls[] = {
+	SOC_SINGLE("Channel 4 Tristate", TDA7802_IB0, 7, 1, 0),
+	SOC_SINGLE("Channel 3 Tristate", TDA7802_IB0, 6, 1, 0),
+	SOC_SINGLE("Channel 2 Tristate", TDA7802_IB0, 5, 1, 0),
+	SOC_SINGLE("Channel 1 Tristate", TDA7802_IB0, 4, 1, 0),
+	SOC_ENUM("Channel 4 Amplifier Mode", ch4_amp_mode),
+	SOC_ENUM("Channel 3 Amplifier Mode", ch3_amp_mode),
+	SOC_ENUM("Channel 2 Amplifier Mode", ch2_amp_mode),
+	SOC_ENUM("Channel 1 Amplifier Mode", ch1_amp_mode),
+
+	/* Impedance (Z) efficiency optimiser */
+	SOC_ENUM("Z efficiency opt channels 2 & 4", zopt_ch24),
+	SOC_ENUM("Z efficiency opt channels 1 & 3", zopt_ch13),
+
+	SOC_ENUM("Long diag config timing", diag_timing),
+	SOC_SINGLE_RANGE("Gain channels 1 & 3", TDA7802_IB1, 3, 0, 3, 0),
+	SOC_SINGLE_RANGE("Gain channels 2 & 4", TDA7802_IB1, 1, 0, 3, 0),
+	SOC_SINGLE("Digital gain boost +6db", TDA7802_IB1, 0, 1, 0),
+
+	/* Mute settings */
+	SOC_ENUM("Mute time", mute_time),
+	SOC_SINGLE("Unmute channels 1 & 3", TDA7802_IB2, 4, 1, 0),
+	SOC_SINGLE("Unmute channels 2 & 4", TDA7802_IB2, 3, 1, 0),
+	SOC_ENUM("Automute threshold", automute_threshold),
+
+	SOC_SINGLE("High pass filter enable", TDA7802_IB3, 0, 1, 0),
+
+	/* Interactive diagnostics */
+	SOC_SINGLE("Noise gating func enable", TDA7802_IB4, 7, 1, 1),
+	SOC_SINGLE("CDdiag: short fault", TDA7802_IB4, 6, 1, 1),
+	SOC_SINGLE("CDdiag: offset", TDA7802_IB4, 5, 1, 1),
+	SOC_ENUM("CDdiag: temperature warning", temp_warn),
+	SOC_ENUM("AC diag current threshold", ac_diag_threshold),
+	SOC_SINGLE("AC diag enable", TDA7802_IB4, 3, 1, 0),
+	SOC_ENUM("Diag mode channels 1 & 3", diag_mode_ch13),
+	SOC_ENUM("Diag mode channels 2 & 4", diag_mode_ch24),
+	SOC_SINGLE("Diag mode enable", TDA7802_IB4, 0, 1, 0),
+
+	SOC_ENUM("Clipping detect channels 1 & 3", clip_detect_ch13),
+	SOC_ENUM("Clipping detect channels 2 & 4", clip_detect_ch24),
+};
+
+static const struct snd_soc_dapm_widget tda7802_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("AIFIN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC", NULL, TDA7802_IB5, 0, 0),
+	SND_SOC_DAPM_OUTPUT("SPK"),
+};
+
+static const struct snd_soc_dapm_route tda7802_dapm_routes[] = {
+	{ "AIFIN", NULL, "Playback" },
+	{ "DAC", NULL, "AIFIN" },
+	{ "SPK", NULL, "DAC" },
+};
+
+static const struct snd_soc_component_driver tda7802_component_driver = {
+	.set_bias_level = tda7802_set_bias_level,
+	.idle_bias_on = 1,
+	.suspend_bias_off = 1,
+	.controls = tda7802_snd_controls,
+	.num_controls = ARRAY_SIZE(tda7802_snd_controls),
+	.dapm_widgets = tda7802_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(tda7802_dapm_widgets),
+	.dapm_routes = tda7802_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(tda7802_dapm_routes),
+};
+
+static int tda7802_i2c_probe(struct i2c_client *i2c,
+			     const struct i2c_device_id *id)
+{
+	struct device *dev = &i2c->dev;
+	struct tda7802_priv *tda7802;
+	int err;
+
+	dev_dbg(dev, "%s addr=0x%02hx, id %p\n", __func__, i2c->addr, id);
+
+	tda7802 = devm_kmalloc(dev, sizeof(*tda7802), GFP_KERNEL);
+	if (!tda7802)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, tda7802);
+	tda7802->i2c = i2c;
+
+	tda7802->enable_reg = devm_regulator_get(dev, "enable");
+	if (IS_ERR(tda7802->enable_reg)) {
+		dev_err(dev, "Failed to get enable regulator\n");
+		return PTR_ERR(tda7802->enable_reg);
+	}
+
+	tda7802->regmap = devm_regmap_init_i2c(tda7802->i2c,
+			&tda7802_regmap_config);
+	if (IS_ERR(tda7802->regmap))
+		return PTR_ERR(tda7802->regmap);
+
+	err = devm_snd_soc_register_component(dev, &tda7802_component_driver,
+			&tda7802_dai_driver, 1);
+	if (err < 0)
+		dev_err(dev, "Failed to register codec: %d\n", err);
+	return err;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id tda7802_of_match[] = {
+	{ .compatible = "st,tda7802" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, tda7802_of_match);
+#endif
+
+static const struct i2c_device_id tda7802_i2c_id[] = {
+	{ "tda7802", tda7802_base },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, tda7802_i2c_id);
+
+static struct i2c_driver tda7802_i2c_driver = {
+	.driver = {
+		.name  = "tda7802-codec",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(tda7802_of_match),
+	},
+	.probe = tda7802_i2c_probe,
+	.id_table = tda7802_i2c_id,
+};
+module_i2c_driver(tda7802_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC ST TDA7802 driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rob Duncan <rduncan@tesla.com>");
+MODULE_AUTHOR("Thomas Preston <thomas.preston@codethink.co.uk>");
-- 
2.21.0


WARNING: multiple messages have this Message-ID (diff)
From: Thomas Preston <thomas.preston@codethink.co.uk>
To: Liam Girdwood <lgirdwood@gmail.com>,
	Mark Brown <broonie@kernel.org>, Rob Herring <robh+dt@kernel.org>,
	Mark Rutland <mark.rutland@arm.com>,
	Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
	Charles Keepax <ckeepax@opensource.cirrus.com>,
	Jerome Brunet <jbrunet@baylibre.com>,
	Srinivas Kandagatla <srinivas.kandagatla@linaro.org>,
	Marco Felsch <m.felsch@pengutronix.de>,
	Paul Cercueil <paul@crapouillou.net>,
	Kirill Marinushkin <kmarinushkin@birdec.tech>,
	Cheng-Yi Chiang <cychiang@chromium.org>,
	Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>,
	Vinod Koul <vkoul@kernel.org>,
	Annaliese McDermond <nh6z@nh6z.net>,
	Thomas Preston <thomas.preston@codethink.co.uk>,
	alsa-devel@alsa-project.org, devicetree@vger.kernel.org,
	linux-kernel
Cc: Patrick Glaser <pglaser@tesla.com>,
	Rob Duncan <rduncan@tesla.com>, Nate Case <ncase@tesla.com>
Subject: [PATCH v2 2/3] ASoC: Add codec driver for ST TDA7802
Date: Tue, 30 Jul 2019 13:09:36 +0100	[thread overview]
Message-ID: <20190730120937.16271-3-thomas.preston@codethink.co.uk> (raw)
In-Reply-To: <20190730120937.16271-1-thomas.preston@codethink.co.uk>

Add an I2C based codec driver for ST TDA7802 amplifier. The amplifier
supports 4 audio channels but can support up to 16 with multiple
devices.

Signed-off-by: Thomas Preston <thomas.preston@codethink.co.uk>
Cc: Patrick Glaser <pglaser@tesla.com>
Cc: Rob Duncan <rduncan@tesla.com>
Cc: Nate Case <ncase@tesla.com>
---
Changes since v1:
- Use ALSA kcontrol interface to expose device controls to userland
	- Gain
	- Channel diagnostic mode
	- Impedance efficiency optimiser. I decided against setting this
	  as a DT property since it seems like something that can be
	  changed on the fly.
- Add regmap default values
	- Channel unmute by default is added in a downstream patch.
	- I'm not sure if I should keep this since they're all zero,
	  although there are other drivers will all-zero reg_defaults.
- I believe the "//" style is used for SPDX headers in normal C source files.
  https://lwn.net/Articles/739183/
- Drop the "enable" sysfs device attribute.
- Don't set TDM format using magic numbers.
- Set sample rate using hw_params.
- Remove unecessary defines.
- Use DAPM to handle AMP_ON.
- Cosmetic fixups

 sound/soc/codecs/Kconfig   |   6 +
 sound/soc/codecs/Makefile  |   2 +
 sound/soc/codecs/tda7802.c | 509 +++++++++++++++++++++++++++++++++++++
 3 files changed, 517 insertions(+)
 create mode 100644 sound/soc/codecs/tda7802.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 9f89a5346299..7e3117eab735 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -182,6 +182,7 @@ config SND_SOC_ALL_CODECS
 	select SND_SOC_TAS5720 if I2C
 	select SND_SOC_TAS6424 if I2C
 	select SND_SOC_TDA7419 if I2C
+	select SND_SOC_TDA7802 if I2C
 	select SND_SOC_TFA9879 if I2C
 	select SND_SOC_TLV320AIC23_I2C if I2C
 	select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
@@ -1121,6 +1122,11 @@ config SND_SOC_TDA7419
 	depends on I2C
 	select REGMAP_I2C
 
+config SND_SOC_TDA7802
+	tristate "ST TDA7802 audio processor"
+	depends on I2C
+	select REGMAP_I2C
+
 config SND_SOC_TFA9879
 	tristate "NXP Semiconductors TFA9879 amplifier"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 5b4bb8cf4325..31dec8930e98 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -194,6 +194,7 @@ snd-soc-tas571x-objs := tas571x.o
 snd-soc-tas5720-objs := tas5720.o
 snd-soc-tas6424-objs := tas6424.o
 snd-soc-tda7419-objs := tda7419.o
+snd-soc-tda7802-objs := tda7802.o
 snd-soc-tfa9879-objs := tfa9879.o
 snd-soc-tlv320aic23-objs := tlv320aic23.o
 snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -474,6 +475,7 @@ obj-$(CONFIG_SND_SOC_TAS571X)	+= snd-soc-tas571x.o
 obj-$(CONFIG_SND_SOC_TAS5720)	+= snd-soc-tas5720.o
 obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o
 obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o
+obj-$(CONFIG_SND_SOC_TDA7802)	+= snd-soc-tda7802.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23)	+= snd-soc-tlv320aic23.o
 obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C)	+= snd-soc-tlv320aic23-i2c.o
diff --git a/sound/soc/codecs/tda7802.c b/sound/soc/codecs/tda7802.c
new file mode 100644
index 000000000000..0f82a88bc1a4
--- /dev/null
+++ b/sound/soc/codecs/tda7802.c
@@ -0,0 +1,509 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * tda7802.c  --  codec driver for ST TDA7802
+ *
+ * Copyright (C) 2016-2019 Tesla Motors, Inc.
+ */
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/string.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#define ENABLE_DELAY_MS			10
+
+#define TDA7802_IB0			0x00
+#define TDA7802_IB1			0x01
+#define TDA7802_IB2			0x02
+#define TDA7802_IB3			0x03
+#define TDA7802_IB4			0x04
+#define TDA7802_IB5			0x05
+
+#define TDA7802_DB0			0x10
+#define TDA7802_DB5			0x15
+
+#define IB2_DIGITAL_MUTE_DISABLED	(1 << 2)
+
+#define IB3_SAMPLE_RATE_SHIFT		6
+#define IB3_SAMPLE_RATE_MASK		(3 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_44_KHZ		(0 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_48_KHZ		(1 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_96_KHZ		(2 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_SAMPLE_RATE_192_KHZ		(3 << IB3_SAMPLE_RATE_SHIFT)
+#define IB3_FORMAT_SHIFT		3
+#define IB3_FORMAT_MASK			(7 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_I2S			(0 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM4			(1 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM8_DEV1		(2 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM8_DEV2		(3 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV1		(4 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV2		(5 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV3		(6 << IB3_FORMAT_SHIFT)
+#define IB3_FORMAT_TDM16_DEV4		(7 << IB3_FORMAT_SHIFT)
+
+enum tda7802_type {
+	tda7802_base,
+};
+
+struct tda7802_priv {
+	struct i2c_client *i2c;
+	struct regmap *regmap;
+	struct regulator *enable_reg;
+};
+
+static const struct reg_default tda7802_reg[] = {
+	{ TDA7802_IB0, 0x0 },
+	{ TDA7802_IB1, 0x0 },
+	{ TDA7802_IB2, 0x0 },
+	{ TDA7802_IB3, 0x0 },
+	{ TDA7802_IB4, 0x0 },
+	{ TDA7802_IB5, 0x0 },
+};
+
+static bool tda7802_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_IB0 ... TDA7802_IB5:
+	case TDA7802_DB0 ... TDA7802_DB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool tda7802_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_DB0 ... TDA7802_DB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool tda7802_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TDA7802_IB0 ... TDA7802_IB5:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config tda7802_regmap_config = {
+	.val_bits = 8,
+	.reg_bits = 8,
+	.max_register = TDA7802_DB5,
+	.use_single_read = 1,
+	.use_single_write = 1,
+
+	.readable_reg = tda7802_readable_reg,
+	.volatile_reg = tda7802_volatile_reg,
+	.writeable_reg = tda7802_writeable_reg,
+
+	.reg_defaults = tda7802_reg,
+	.num_reg_defaults = ARRAY_SIZE(tda7802_reg),
+	.cache_type = REGCACHE_RBTREE,
+};
+
+static int tda7802_digital_mute(struct snd_soc_dai *dai, int mute)
+{
+	const u8 mute_disabled = mute ? 0 : IB2_DIGITAL_MUTE_DISABLED;
+	struct device *dev = dai->dev;
+	int err;
+
+	dev_dbg(dev, "%s mute=%d\n", __func__, mute);
+
+	err = snd_soc_component_update_bits(dai->component, TDA7802_IB2,
+			IB2_DIGITAL_MUTE_DISABLED, mute_disabled);
+	if (err < 0)
+		dev_err(dev, "Cannot mute amp %d\n", err);
+
+	return err;
+}
+
+static int tda7802_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
+		unsigned int rx_mask, int slots, int slot_width)
+{
+	struct device *dev = dai->dev;
+	u8 tdm_format;
+	int ret;
+
+	dev_dbg(dai->dev, "%s tx %x, rx %x, slots %d, slot_width %d\n",
+			__func__, tx_mask, rx_mask, slots, slot_width);
+
+	switch (slots) {
+	case 4:
+		tdm_format = IB3_FORMAT_TDM4;
+		break;
+	case 8:
+		switch (tx_mask) {
+		case 0x000f:
+			tdm_format = IB3_FORMAT_TDM8_DEV1;
+			break;
+		case 0x00f0:
+			tdm_format = IB3_FORMAT_TDM8_DEV2;
+			break;
+		default:
+			dev_err(dev,
+				"Failed to set tdm fmt, slots %d, tx_mask %x\n",
+				slots, tx_mask);
+			return -ENOTSUPP;
+		}
+		break;
+	case 16:
+		switch (tx_mask) {
+		case 0x000f:
+			tdm_format = IB3_FORMAT_TDM16_DEV1;
+			break;
+		case 0x00f0:
+			tdm_format = IB3_FORMAT_TDM16_DEV2;
+			break;
+		case 0x0f00:
+			tdm_format = IB3_FORMAT_TDM16_DEV3;
+			break;
+		case 0xf000:
+			tdm_format = IB3_FORMAT_TDM16_DEV4;
+			break;
+		default:
+			dev_err(dev,
+				"Failed to set tdm fmt, slots %d, tx_mask %x\n",
+				slots, tx_mask);
+			return -ENOTSUPP;
+		}
+		break;
+	default:
+		dev_err(dev, "Failed to set %d slots, supported: 4, 8, 16\n",
+				slots);
+		return -ENOTSUPP;
+	}
+
+	ret = snd_soc_component_update_bits(dai->component, TDA7802_IB3,
+			IB3_FORMAT_MASK, tdm_format);
+	if (ret < 0) {
+		dev_err(dev, "Failed to write IB3 config %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tda7802_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *params,
+		struct snd_soc_dai *dai)
+{
+	int err;
+	u8 val;
+
+	dev_dbg(dai->dev, "%s rate %d\n", __func__, params_rate(params));
+
+	switch (params_rate(params)) {
+	case 44100:
+		val = IB3_SAMPLE_RATE_44_KHZ;
+		break;
+	case 48000:
+		val = IB3_SAMPLE_RATE_48_KHZ;
+		break;
+	case 96000:
+		val = IB3_SAMPLE_RATE_96_KHZ;
+		break;
+	case 192000:
+		val = IB3_SAMPLE_RATE_192_KHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	err = snd_soc_component_update_bits(dai->component, TDA7802_IB3,
+			IB3_SAMPLE_RATE_MASK, val);
+	if (err < 0)
+		dev_err(dai->dev, "Could not set hw_params, %d\n", err);
+
+	return err;
+}
+
+static const struct snd_soc_dai_ops tda7802_dai_ops = {
+	.digital_mute = tda7802_digital_mute,
+	.hw_params = tda7802_hw_params,
+	.set_tdm_slot = tda7802_set_tdm_slot,
+};
+
+static struct snd_soc_dai_driver tda7802_dai_driver = {
+	.name = "tda7802",
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 4,
+		.channels_max = 4,
+		.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
+			SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000,
+		.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	},
+	.ops = &tda7802_dai_ops,
+};
+
+static int tda7802_set_bias_level(struct snd_soc_component *component,
+		enum snd_soc_bias_level level)
+{
+	const struct tda7802_priv *tda7802 =
+		snd_soc_component_get_drvdata(component);
+	struct snd_soc_dapm_context *dapm_context =
+			snd_soc_component_get_dapm(component);
+	const enum snd_soc_bias_level oldlevel =
+		snd_soc_dapm_get_bias_level(dapm_context);
+	int err = 0;
+
+	dev_dbg(component->dev, "%s level %d\n", __func__, level);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		err = regulator_enable(tda7802->enable_reg);
+		if (err < 0) {
+			dev_err(component->dev, "Could not enable.\n");
+			return err;
+		}
+		dev_dbg(component->dev, "Regulator enabled\n");
+		msleep(ENABLE_DELAY_MS);
+
+		if (oldlevel == SND_SOC_BIAS_OFF) {
+			dev_dbg(component->dev, "Syncing regcache\n");
+			err = regcache_sync(component->regmap);
+			if (err < 0)
+				dev_err(component->dev,
+					"Could not sync regcache, %d\n", err);
+		}
+		break;
+	case SND_SOC_BIAS_OFF:
+		regcache_mark_dirty(component->regmap);
+		err = regulator_disable(tda7802->enable_reg);
+		if (err < 0)
+			dev_err(component->dev, "Could not disable.\n");
+		break;
+	}
+
+	return err;
+}
+
+static const char * const amp_mode_str[] = {
+	"High Efficiency",
+	"Standard Class AB",
+};
+
+static SOC_ENUM_SINGLE_DECL(ch1_amp_mode, TDA7802_IB0, 0, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch2_amp_mode, TDA7802_IB0, 1, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch3_amp_mode, TDA7802_IB0, 2, amp_mode_str);
+static SOC_ENUM_SINGLE_DECL(ch4_amp_mode, TDA7802_IB0, 3, amp_mode_str);
+
+static const char * const zopt_str[] = {
+	"2ohm",
+	"4ohm",
+};
+
+static SOC_ENUM_SINGLE_DECL(zopt_ch24, TDA7802_IB1, 7, zopt_str);
+static SOC_ENUM_SINGLE_DECL(zopt_ch13, TDA7802_IB2, 0, zopt_str);
+
+static const char * const diag_timing_str[] = {
+	"default",
+	"x2",
+	"x4",
+	"x8",
+};
+
+static SOC_ENUM_SINGLE_DECL(diag_timing, TDA7802_IB1, 5, diag_timing_str);
+
+static const char * const mute_time_str[] = {
+	"1.45ms",
+	"5.8ms",
+	"11.6ms",
+	"23.2ms",
+	"46.4ms",
+	"92.8ms",
+	"185.6ms",
+	"371.2ms",
+};
+
+static SOC_ENUM_SINGLE_DECL(mute_time, TDA7802_IB2, 5, mute_time_str);
+
+static const char * const automute_threshold_str[] = {
+	"5.3V",
+	"7.3V",
+};
+
+static SOC_ENUM_SINGLE_DECL(automute_threshold, TDA7802_IB2, 1,
+		automute_threshold_str);
+
+static const char * const ac_diag_threshold_str[] = {
+	"High",
+	"Low",
+};
+
+static SOC_ENUM_SINGLE_DECL(ac_diag_threshold, TDA7802_IB3, 4,
+		ac_diag_threshold_str);
+
+static const char * const ch_diag_mode_str[] = {
+	"Speaker",
+	"Boosted",
+};
+
+static SOC_ENUM_SINGLE_DECL(diag_mode_ch13, TDA7802_IB4, 2, ch_diag_mode_str);
+static SOC_ENUM_SINGLE_DECL(diag_mode_ch24, TDA7802_IB4, 1, ch_diag_mode_str);
+
+static const char * const temp_warn_str[] = {
+	"TW1",
+	"TW2",
+	"TW3",
+	"TW4",
+	"None",
+};
+
+static SOC_ENUM_SINGLE_DECL(temp_warn, TDA7802_IB5, 5, temp_warn_str);
+
+static const char * const clip_detect_str[] = {
+	"2%",
+	"5%",
+	"10%",
+	"None",
+};
+
+static SOC_ENUM_SINGLE_DECL(clip_detect_ch13, TDA7802_IB5, 3, clip_detect_str);
+static SOC_ENUM_SINGLE_DECL(clip_detect_ch24, TDA7802_IB5, 1, clip_detect_str);
+
+static const struct snd_kcontrol_new tda7802_snd_controls[] = {
+	SOC_SINGLE("Channel 4 Tristate", TDA7802_IB0, 7, 1, 0),
+	SOC_SINGLE("Channel 3 Tristate", TDA7802_IB0, 6, 1, 0),
+	SOC_SINGLE("Channel 2 Tristate", TDA7802_IB0, 5, 1, 0),
+	SOC_SINGLE("Channel 1 Tristate", TDA7802_IB0, 4, 1, 0),
+	SOC_ENUM("Channel 4 Amplifier Mode", ch4_amp_mode),
+	SOC_ENUM("Channel 3 Amplifier Mode", ch3_amp_mode),
+	SOC_ENUM("Channel 2 Amplifier Mode", ch2_amp_mode),
+	SOC_ENUM("Channel 1 Amplifier Mode", ch1_amp_mode),
+
+	/* Impedance (Z) efficiency optimiser */
+	SOC_ENUM("Z efficiency opt channels 2 & 4", zopt_ch24),
+	SOC_ENUM("Z efficiency opt channels 1 & 3", zopt_ch13),
+
+	SOC_ENUM("Long diag config timing", diag_timing),
+	SOC_SINGLE_RANGE("Gain channels 1 & 3", TDA7802_IB1, 3, 0, 3, 0),
+	SOC_SINGLE_RANGE("Gain channels 2 & 4", TDA7802_IB1, 1, 0, 3, 0),
+	SOC_SINGLE("Digital gain boost +6db", TDA7802_IB1, 0, 1, 0),
+
+	/* Mute settings */
+	SOC_ENUM("Mute time", mute_time),
+	SOC_SINGLE("Unmute channels 1 & 3", TDA7802_IB2, 4, 1, 0),
+	SOC_SINGLE("Unmute channels 2 & 4", TDA7802_IB2, 3, 1, 0),
+	SOC_ENUM("Automute threshold", automute_threshold),
+
+	SOC_SINGLE("High pass filter enable", TDA7802_IB3, 0, 1, 0),
+
+	/* Interactive diagnostics */
+	SOC_SINGLE("Noise gating func enable", TDA7802_IB4, 7, 1, 1),
+	SOC_SINGLE("CDdiag: short fault", TDA7802_IB4, 6, 1, 1),
+	SOC_SINGLE("CDdiag: offset", TDA7802_IB4, 5, 1, 1),
+	SOC_ENUM("CDdiag: temperature warning", temp_warn),
+	SOC_ENUM("AC diag current threshold", ac_diag_threshold),
+	SOC_SINGLE("AC diag enable", TDA7802_IB4, 3, 1, 0),
+	SOC_ENUM("Diag mode channels 1 & 3", diag_mode_ch13),
+	SOC_ENUM("Diag mode channels 2 & 4", diag_mode_ch24),
+	SOC_SINGLE("Diag mode enable", TDA7802_IB4, 0, 1, 0),
+
+	SOC_ENUM("Clipping detect channels 1 & 3", clip_detect_ch13),
+	SOC_ENUM("Clipping detect channels 2 & 4", clip_detect_ch24),
+};
+
+static const struct snd_soc_dapm_widget tda7802_dapm_widgets[] = {
+	SND_SOC_DAPM_AIF_IN("AIFIN", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_DAC("DAC", NULL, TDA7802_IB5, 0, 0),
+	SND_SOC_DAPM_OUTPUT("SPK"),
+};
+
+static const struct snd_soc_dapm_route tda7802_dapm_routes[] = {
+	{ "AIFIN", NULL, "Playback" },
+	{ "DAC", NULL, "AIFIN" },
+	{ "SPK", NULL, "DAC" },
+};
+
+static const struct snd_soc_component_driver tda7802_component_driver = {
+	.set_bias_level = tda7802_set_bias_level,
+	.idle_bias_on = 1,
+	.suspend_bias_off = 1,
+	.controls = tda7802_snd_controls,
+	.num_controls = ARRAY_SIZE(tda7802_snd_controls),
+	.dapm_widgets = tda7802_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(tda7802_dapm_widgets),
+	.dapm_routes = tda7802_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(tda7802_dapm_routes),
+};
+
+static int tda7802_i2c_probe(struct i2c_client *i2c,
+			     const struct i2c_device_id *id)
+{
+	struct device *dev = &i2c->dev;
+	struct tda7802_priv *tda7802;
+	int err;
+
+	dev_dbg(dev, "%s addr=0x%02hx, id %p\n", __func__, i2c->addr, id);
+
+	tda7802 = devm_kmalloc(dev, sizeof(*tda7802), GFP_KERNEL);
+	if (!tda7802)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, tda7802);
+	tda7802->i2c = i2c;
+
+	tda7802->enable_reg = devm_regulator_get(dev, "enable");
+	if (IS_ERR(tda7802->enable_reg)) {
+		dev_err(dev, "Failed to get enable regulator\n");
+		return PTR_ERR(tda7802->enable_reg);
+	}
+
+	tda7802->regmap = devm_regmap_init_i2c(tda7802->i2c,
+			&tda7802_regmap_config);
+	if (IS_ERR(tda7802->regmap))
+		return PTR_ERR(tda7802->regmap);
+
+	err = devm_snd_soc_register_component(dev, &tda7802_component_driver,
+			&tda7802_dai_driver, 1);
+	if (err < 0)
+		dev_err(dev, "Failed to register codec: %d\n", err);
+	return err;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id tda7802_of_match[] = {
+	{ .compatible = "st,tda7802" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, tda7802_of_match);
+#endif
+
+static const struct i2c_device_id tda7802_i2c_id[] = {
+	{ "tda7802", tda7802_base },
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, tda7802_i2c_id);
+
+static struct i2c_driver tda7802_i2c_driver = {
+	.driver = {
+		.name  = "tda7802-codec",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(tda7802_of_match),
+	},
+	.probe = tda7802_i2c_probe,
+	.id_table = tda7802_i2c_id,
+};
+module_i2c_driver(tda7802_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC ST TDA7802 driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rob Duncan <rduncan@tesla.com>");
+MODULE_AUTHOR("Thomas Preston <thomas.preston@codethink.co.uk>");
-- 
2.21.0

  parent reply	other threads:[~2019-07-30 12:10 UTC|newest]

Thread overview: 42+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-07-30 12:09 [PATCH v2 0/3] ASoC: Codecs: Add TDA7802 codec Thomas Preston
2019-07-30 12:09 ` Thomas Preston
2019-07-30 12:09 ` [PATCH v2 1/3] dt-bindings: ASoC: Add TDA7802 amplifier Thomas Preston
2019-07-30 12:09   ` Thomas Preston
2019-07-30 12:27   ` Charles Keepax
2019-07-30 12:27     ` Charles Keepax
2019-07-30 13:12     ` Marco Felsch
2019-07-30 13:12       ` Marco Felsch
2019-07-30 14:12       ` [alsa-devel] " Thomas Preston
2019-07-30 14:33         ` Mark Brown
2019-07-30 14:10     ` Thomas Preston
2019-07-30 12:09 ` Thomas Preston [this message]
2019-07-30 12:09   ` [PATCH v2 2/3] ASoC: Add codec driver for ST TDA7802 Thomas Preston
2019-07-30 12:38   ` Charles Keepax
2019-07-30 12:38     ` Charles Keepax
2019-07-30 15:49     ` [alsa-devel] " Thomas Preston
2019-07-30 14:58   ` Mark Brown
2019-07-30 14:58     ` Mark Brown
2019-07-30 17:26     ` [alsa-devel] " Thomas Preston
2019-07-31  6:06       ` Marco Felsch
2019-07-31  8:57         ` Thomas Preston
2019-07-30 12:09 ` [PATCH v2 3/3] ASoC: TDA7802: Add turn-on diagnostic routine Thomas Preston
2019-07-30 12:09   ` Thomas Preston
2019-07-30 12:41   ` Charles Keepax
2019-07-30 12:41     ` Charles Keepax
2019-07-30 14:04     ` [alsa-devel] " Thomas Preston
2019-07-30 14:18       ` Charles Keepax
2019-07-30 14:18         ` Charles Keepax
2019-07-30 14:20       ` [alsa-devel] " Mark Brown
2019-07-30 15:27         ` Thomas Preston
2019-07-30 14:19   ` Mark Brown
2019-07-30 14:19     ` Mark Brown
2019-07-30 15:25     ` [alsa-devel] " Thomas Preston
2019-07-30 15:50       ` Mark Brown
2019-07-30 16:28         ` Thomas Preston
2019-07-31  8:03           ` Charles Keepax
2019-07-31  8:03             ` Charles Keepax
2019-08-01 23:42           ` Mark Brown
2019-08-02  8:32             ` Thomas Preston
2019-08-02 11:10               ` Mark Brown
2019-08-02 14:51                 ` Thomas Preston
2019-08-02 17:27                   ` Mark Brown

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190730120937.16271-3-thomas.preston@codethink.co.uk \
    --to=thomas.preston@codethink.co.uk \
    --cc=alsa-devel@alsa-project.org \
    --cc=broonie@kernel.org \
    --cc=ckeepax@opensource.cirrus.com \
    --cc=cychiang@chromium.org \
    --cc=devicetree@vger.kernel.org \
    --cc=jbrunet@baylibre.com \
    --cc=kmarinushkin@birdec.tech \
    --cc=kuninori.morimoto.gx@renesas.com \
    --cc=lgirdwood@gmail.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=m.felsch@pengutronix.de \
    --cc=mark.rutland@arm.com \
    --cc=ncase@tesla.com \
    --cc=nh6z@nh6z.net \
    --cc=paul@crapouillou.net \
    --cc=perex@perex.cz \
    --cc=pglaser@tesla.com \
    --cc=rduncan@tesla.com \
    --cc=robh+dt@kernel.org \
    --cc=srinivas.kandagatla@linaro.org \
    --cc=tiwai@suse.com \
    --cc=vkoul@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.