Linux Sound subsystem development
 help / color / mirror / Atom feed
* [PATCH 0/2] ASoC: Add the NXP TFA9865 amplifier
@ 2026-11-15 20:20 Venkat Yadlapati via B4 Relay
  2026-11-15 20:20 ` [PATCH 1/2] ASoC: dt-bindings: Add " Venkat Yadlapati via B4 Relay
  2026-11-15 20:20 ` [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver Venkat Yadlapati via B4 Relay
  0 siblings, 2 replies; 5+ messages in thread
From: Venkat Yadlapati via B4 Relay @ 2026-11-15 20:20 UTC (permalink / raw)
  To: Venkat Yadlapati, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: linux-sound, devicetree, linux-kernel

The Motorola Signature has two NXP (now Goodix) TFA9865 mono class-D
speaker amplifiers. This series adds the devicetree binding and a driver.

The chip has no DSP and a different register map than the TFA989x parts
of tfa989x.c. The driver applies the initialization sequence for the
device revision, selects the I2S slot feeding the amplifier and powers
the amplifier and its boost converter up and down. The register settings
come from the NXP tfa98xx driver. Only the B0 revision and the 32-bit I2S
slots configured at reset are supported for now.

Tested on a Motorola Signature: with the matching sound card the speaker
and the earpiece play audio.

I used an LLM (Claude) to help write these patches and their commit
messages. I reviewed all of them and tested the result on the device.

---
Venkat Yadlapati (2):
      ASoC: dt-bindings: Add NXP TFA9865 amplifier
      ASoC: codecs: Add NXP TFA9865 amplifier driver

 .../devicetree/bindings/sound/nxp,tfa9865.yaml     |  65 ++++
 MAINTAINERS                                        |   7 +
 sound/soc/codecs/Kconfig                           |  14 +
 sound/soc/codecs/Makefile                          |   2 +
 sound/soc/codecs/tfa9865.c                         | 359 +++++++++++++++++++++
 5 files changed, 447 insertions(+)
---
base-commit: 4e019254b80b70f6fb90d17a50f211de1daf7ffe
change-id: 20261116-vantage-tfa9865-8885091e9443

Best regards,
--  
Venkat Yadlapati <venkat.yadlapati@yahoo.com>



^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/2] ASoC: dt-bindings: Add NXP TFA9865 amplifier
  2026-11-15 20:20 [PATCH 0/2] ASoC: Add the NXP TFA9865 amplifier Venkat Yadlapati via B4 Relay
@ 2026-11-15 20:20 ` Venkat Yadlapati via B4 Relay
  2026-11-15 20:34   ` sashiko-bot
  2026-11-15 20:20 ` [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver Venkat Yadlapati via B4 Relay
  1 sibling, 1 reply; 5+ messages in thread
From: Venkat Yadlapati via B4 Relay @ 2026-11-15 20:20 UTC (permalink / raw)
  To: Venkat Yadlapati, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: linux-sound, devicetree, linux-kernel

From: Venkat Yadlapati <venkat.yadlapati@yahoo.com>

The TFA9865 is a mono class-D speaker amplifier with a boost converter
and voltage/current sensing but no DSP. It is controlled over I2C and
receives audio over I2S/TDM.

An LLM was used to help write this commit message.

Signed-off-by: Venkat Yadlapati <venkat.yadlapati@yahoo.com>
---
 .../devicetree/bindings/sound/nxp,tfa9865.yaml     | 65 ++++++++++++++++++++++
 MAINTAINERS                                        |  7 +++
 2 files changed, 72 insertions(+)

diff --git a/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml b/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml
new file mode 100644
index 000000000000..955e25abe749
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml
@@ -0,0 +1,65 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/nxp,tfa9865.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: NXP/Goodix TFA9865 Audio Amplifier
+
+maintainers:
+  - Venkat Yadlapati <venkat.yadlapati@yahoo.com>
+
+description:
+  The TFA9865 is a mono class-D speaker amplifier with an integrated boost
+  converter and voltage/current sensing. It is controlled over I2C and
+  receives audio over I2S/TDM.
+
+allOf:
+  - $ref: dai-common.yaml#
+
+properties:
+  compatible:
+    const: nxp,tfa9865
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  vddd-supply:
+    description: Digital supply (1.8 V)
+
+  '#sound-dai-cells':
+    const: 0
+
+  sound-name-prefix: true
+
+required:
+  - compatible
+  - reg
+  - '#sound-dai-cells'
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        audio-codec@35 {
+            compatible = "nxp,tfa9865";
+            reg = <0x35>;
+            interrupts-extended = <&tlmm 201 IRQ_TYPE_EDGE_FALLING>;
+            reset-gpios = <&tlmm 183 GPIO_ACTIVE_LOW>;
+            sound-name-prefix = "Speaker";
+            #sound-dai-cells = <0>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 222b7fc2c3aa..bbe812e4257c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20386,6 +20386,13 @@ F:	drivers/gpu/drm/bridge/tda998x_drv.c
 F:	include/dt-bindings/display/tda998x.h
 K:	"nxp,tda998x"
 
+NXP TFA9865 AMPLIFIER DRIVER
+M:	Venkat Yadlapati <venkat.yadlapati@yahoo.com>
+L:	linux-sound@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml
+F:	sound/soc/codecs/tfa9865.c
+
 NXP TFA9879 DRIVER
 M:	Peter Rosin <peda@lysator.liu.se>
 L:	linux-sound@vger.kernel.org

-- 
2.56.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver
  2026-11-15 20:20 [PATCH 0/2] ASoC: Add the NXP TFA9865 amplifier Venkat Yadlapati via B4 Relay
  2026-11-15 20:20 ` [PATCH 1/2] ASoC: dt-bindings: Add " Venkat Yadlapati via B4 Relay
@ 2026-11-15 20:20 ` Venkat Yadlapati via B4 Relay
  2026-11-15 20:44   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Venkat Yadlapati via B4 Relay @ 2026-11-15 20:20 UTC (permalink / raw)
  To: Venkat Yadlapati, Liam Girdwood, Mark Brown, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Jaroslav Kysela, Takashi Iwai
  Cc: linux-sound, devicetree, linux-kernel

From: Venkat Yadlapati <venkat.yadlapati@yahoo.com>

Add a driver for the NXP (now Goodix) TFA9865 speaker amplifier. The
chip has no DSP, so the driver only has to apply the initialization
sequence for the device revision, select the I2S slot that feeds the
amplifier and power the amplifier and its boost converter up and down.

The register settings are taken from the NXP tfa98xx driver. Only the
B0 revision and the 32-bit I2S slots configured at reset are supported
for now.

Assisted-by: LLM
Signed-off-by: Venkat Yadlapati <venkat.yadlapati@yahoo.com>
---
 sound/soc/codecs/Kconfig   |  14 ++
 sound/soc/codecs/Makefile  |   2 +
 sound/soc/codecs/tfa9865.c | 359 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 375 insertions(+)

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index ba59dabeeccd..a30057b2d1ef 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -284,6 +284,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TAS6424
 	imply SND_SOC_TAS675X
 	imply SND_SOC_TDA7419
+	imply SND_SOC_TFA9865
 	imply SND_SOC_TFA9879
 	imply SND_SOC_TFA989X
 	imply SND_SOC_TLV320ADC3XXX
@@ -2321,6 +2322,19 @@ config SND_SOC_TDA7419
 	depends on I2C
 	select REGMAP_I2C
 
+config SND_SOC_TFA9865
+	tristate "NXP/Goodix TFA9865 amplifier"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Enable support for the NXP (now Goodix) TFA9865 mono class-D
+	  speaker amplifier with boost converter. The amplifier has no DSP,
+	  speaker protection is left to the host. The driver supports the
+	  B0 revision of the chip with 32-bit I2S slots.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-soc-tfa9865.
+
 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 3826f9e4fce8..0e421a3c620b 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -334,6 +334,7 @@ snd-soc-tas2781-comlib-i2c-y := tas2781-comlib-i2c.o
 snd-soc-tas2781-fmwlib-y := tas2781-fmwlib.o
 snd-soc-tas2781-i2c-y := tas2781-i2c.o
 snd-soc-tas2783-sdw-y := tas2783-sdw.o
+snd-soc-tfa9865-y := tfa9865.o
 snd-soc-tfa9879-y := tfa9879.o
 snd-soc-tfa989x-y := tfa989x.o
 snd-soc-tlv320adc3xxx-y := tlv320adc3xxx.o
@@ -782,6 +783,7 @@ obj-$(CONFIG_SND_SOC_TAS6424)	+= snd-soc-tas6424.o
 obj-$(CONFIG_SND_SOC_TAS675X)	+= snd-soc-tas675x.o
 obj-$(CONFIG_SND_SOC_TDA7419)	+= snd-soc-tda7419.o
 obj-$(CONFIG_SND_SOC_TAS2770) += snd-soc-tas2770.o
+obj-$(CONFIG_SND_SOC_TFA9865)	+= snd-soc-tfa9865.o
 obj-$(CONFIG_SND_SOC_TFA9879)	+= snd-soc-tfa9879.o
 obj-$(CONFIG_SND_SOC_TFA989X)	+= snd-soc-tfa989x.o
 obj-$(CONFIG_SND_SOC_TLV320ADC3XXX)	+= snd-soc-tlv320adc3xxx.o
diff --git a/sound/soc/codecs/tfa9865.c b/sound/soc/codecs/tfa9865.c
new file mode 100644
index 000000000000..7467b8b21eda
--- /dev/null
+++ b/sound/soc/codecs/tfa9865.c
@@ -0,0 +1,359 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for the NXP TFA9865 mono class-D speaker amplifier
+ *
+ * The TFA9865 has no DSP of its own, speaker protection (if any) is done by
+ * the host. Register definitions and the initialization sequence are taken
+ * from the NXP tfa98xx kernel driver:
+ * Copyright (C) 2014-2020 NXP Semiconductors, All Rights Reserved.
+ *
+ * Copyright (c) 2026 Venkat Yadlapati <venkat.yadlapati@yahoo.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define TFA9865_SYS_CTRL0		0x00
+#define TFA9865_SYS_CTRL0_PWDN		0	/* power down */
+#define TFA9865_SYS_CTRL0_AMPE		3	/* enable amplifier */
+#define TFA9865_SYS_CTRL0_DCA		4	/* enable boost converter */
+#define TFA9865_SYS_CTRL1		0x01
+#define TFA9865_SYS_CTRL1_MANSCONF	BIT(2)	/* I2C configuration done */
+#define TFA9865_SYS_CTRL2		0x02
+#define TFA9865_SYS_CTRL2_AUDFS		GENMASK(3, 0)	/* sample rate */
+#define TFA9865_REVISIONNUMBER		0x03
+#define TFA9865_REVISIONNUMBER_REV	GENMASK(7, 0)
+#define TFA9865_REVISIONNUMBER_FAM	GENMASK(15, 8)
+#define TFA9865_CLK_GATING_CTRL		0x04
+#define TFA9865_CLK_GATING_CTRL_MANAOOSC BIT(6)	/* oscillator off in power down */
+#define TFA9865_DEVICE_INFO		0x06
+#define TFA9865_DEVICE_INFO_DEVREV	GENMASK(15, 8)
+#define TFA9865_HIDE_UNHIDE_KEY		0x0f
+#define TFA9865_STATUS_FLAGS2		0x12
+#define TFA9865_STATUS_FLAGS2_MANSTATE	GENMASK(3, 0)	/* device manager state */
+#define TFA9865_TDM_CFG1		0x21
+#define TFA9865_TDM_CFG1_TDMSPKS	8	/* slot for the amplifier input */
+#define TFA9865_TDM_GAIN		0x5f
+#define TFA9865_TDM_GAIN_TDMSPKG	5	/* input attenuation */
+#define TFA9865_TDM_GAIN_TDMSPKG_MAX	31
+#define TFA9865_KEY1			0xa0
+#define TFA9865_KEY2			0xa1
+#define TFA9865_KEY1_SEED		0xfb
+
+#define TFA9865_FAMILY			0x98
+#define TFA9865_REVISION		0x66
+
+#define TFA9865_MANSTATE_POWERDOWN	0
+#define TFA9865_MANSTATE_WAIT_SRC	1
+
+struct tfa9865 {
+	struct regmap *regmap;
+	struct gpio_desc *reset_gpiod;
+};
+
+static bool tfa9865_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case TFA9865_SYS_CTRL0 ... TFA9865_DEVICE_INFO:
+	case 0x10 ... 0x15:	/* status and interrupt flags */
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct regmap_config tfa9865_regmap = {
+	.reg_bits = 8,
+	.val_bits = 16,
+	.max_register = 0xff,
+
+	.volatile_reg = tfa9865_volatile_reg,
+	.cache_type = REGCACHE_NONE,
+};
+
+static const char * const tdmspks_text[] = { "Left", "Right" };
+static SOC_ENUM_SINGLE_DECL(tdmspks_enum, TFA9865_TDM_CFG1, TFA9865_TDM_CFG1_TDMSPKS,
+			    tdmspks_text);
+static const struct snd_kcontrol_new tdmspks_mux = SOC_DAPM_ENUM("Amp Input", tdmspks_enum);
+static const struct snd_kcontrol_new amp_switch = SOC_DAPM_SINGLE_VIRT("Switch", 1);
+
+static const struct snd_kcontrol_new tfa9865_controls[] = {
+	/* The input attenuation has no documented scale, larger is quieter */
+	SOC_SINGLE("Playback Volume", TFA9865_TDM_GAIN, TFA9865_TDM_GAIN_TDMSPKG,
+		   TFA9865_TDM_GAIN_TDMSPKG_MAX, 1),
+};
+
+static const struct snd_soc_dapm_widget tfa9865_dapm_widgets[] = {
+	SND_SOC_DAPM_OUTPUT("OUT"),
+	SND_SOC_DAPM_SUPPLY("POWER", TFA9865_SYS_CTRL0, TFA9865_SYS_CTRL0_PWDN, 1, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("BOOST", TFA9865_SYS_CTRL0, TFA9865_SYS_CTRL0_DCA, 0, NULL, 0),
+	SND_SOC_DAPM_OUT_DRV("AMPE", SND_SOC_NOPM, 0, 0, NULL, 0),
+	SND_SOC_DAPM_SWITCH("Amp", SND_SOC_NOPM, 0, 0, &amp_switch),
+
+	SND_SOC_DAPM_MUX("Amp Input", SND_SOC_NOPM, 0, 0, &tdmspks_mux),
+	SND_SOC_DAPM_AIF_IN("AIFINL", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("AIFINR", "HiFi Playback", 1, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route tfa9865_dapm_routes[] = {
+	{"OUT", NULL, "AMPE"},
+	{"AMPE", NULL, "POWER"},
+	{"AMPE", NULL, "BOOST"},
+	{"AMPE", NULL, "Amp"},
+	{"Amp", "Switch", "Amp Input"},
+	{"Amp Input", "Left", "AIFINL"},
+	{"Amp Input", "Right", "AIFINR"},
+};
+
+static const struct snd_soc_component_driver tfa9865_component = {
+	.controls		= tfa9865_controls,
+	.num_controls		= ARRAY_SIZE(tfa9865_controls),
+	.dapm_widgets		= tfa9865_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(tfa9865_dapm_widgets),
+	.dapm_routes		= tfa9865_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(tfa9865_dapm_routes),
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static const unsigned int tfa9865_rates[] = {
+	8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000
+};
+
+static int tfa9865_find_sample_rate(unsigned int rate)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(tfa9865_rates); i++)
+		if (tfa9865_rates[i] == rate)
+			return i;
+
+	return -EINVAL;
+}
+
+static int tfa9865_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;
+	int sr;
+
+	/* Only the 32-bit I2S slots configured at reset are supported */
+	if (params_physical_width(params) != 32) {
+		dev_err(component->dev, "unsupported slot width: %d\n",
+			params_physical_width(params));
+		return -EINVAL;
+	}
+
+	sr = tfa9865_find_sample_rate(params_rate(params));
+	if (sr < 0) {
+		dev_err(component->dev, "unsupported sample rate: %d\n",
+			params_rate(params));
+		return sr;
+	}
+
+	return snd_soc_component_update_bits(component, TFA9865_SYS_CTRL2,
+					     TFA9865_SYS_CTRL2_AUDFS,
+					     FIELD_PREP(TFA9865_SYS_CTRL2_AUDFS, sr));
+}
+
+static int tfa9865_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct snd_soc_component *component = dai->component;
+	unsigned int val;
+
+	/*
+	 * Enable the amplifier only once the I2S clocks run, enabling it
+	 * earlier makes it pop when the clocks start. Leave it off if the
+	 * output is not powered.
+	 */
+	if (!mute) {
+		val = snd_soc_component_read(component, TFA9865_SYS_CTRL0);
+		if (val & BIT(TFA9865_SYS_CTRL0_PWDN))
+			return 0;
+	}
+
+	return snd_soc_component_update_bits(component, TFA9865_SYS_CTRL0,
+					     BIT(TFA9865_SYS_CTRL0_AMPE),
+					     mute ? 0 : BIT(TFA9865_SYS_CTRL0_AMPE));
+}
+
+static const struct snd_soc_dai_ops tfa9865_dai_ops = {
+	.hw_params = tfa9865_hw_params,
+	.mute_stream = tfa9865_mute_stream,
+	.no_capture_mute = 1,
+	.mute_unmute_on_trigger = 1,
+};
+
+static struct snd_soc_dai_driver tfa9865_dai = {
+	.name = "tfa9865-hifi",
+	.playback = {
+		.stream_name	= "HiFi Playback",
+		.formats	= SNDRV_PCM_FMTBIT_S32_LE,
+		.rates		= SNDRV_PCM_RATE_8000_48000,
+		.rate_min	= 8000,
+		.rate_max	= 48000,
+		.channels_min	= 1,
+		.channels_max	= 2,
+	},
+	.ops = &tfa9865_dai_ops,
+};
+
+static int tfa9865_wait_manstate(struct tfa9865 *tfa9865, unsigned int state)
+{
+	unsigned int val;
+
+	return regmap_read_poll_timeout(tfa9865->regmap, TFA9865_STATUS_FLAGS2, val,
+					FIELD_GET(TFA9865_STATUS_FLAGS2_MANSTATE, val) == state,
+					1000, 50000);
+}
+
+static const struct reg_sequence tfa9865_b0_init[] = {
+	/* "generated code" version 2 for TFA9865 B0 from the NXP driver */
+	{ 0x00, 0xf241 },
+	{ 0x02, 0x0c28 },
+	{ 0x08, 0x009a },
+	{ 0x50, 0xc000 },
+	{ 0x54, 0x10e0 },
+	{ 0x5a, 0x5f5e },
+	{ 0x5b, 0x74e2 },
+	{ 0x5c, 0xb02b },
+	{ 0x5f, 0x00a0 },
+	{ 0x62, 0x06c4 },
+	{ 0x63, 0x80d4 },
+	{ 0x65, 0x0c58 },
+	{ 0x67, 0x006e },
+	{ 0x68, 0x0820 },
+	{ 0x69, 0x0119 },
+	{ 0x74, 0x6028 },
+	{ 0x75, 0x1daa },
+	{ 0x78, 0x0001 },
+	{ 0x7c, 0x10f2 },
+	{ 0xdd, 0x01b6 },
+};
+
+static int tfa9865_init(struct tfa9865 *tfa9865)
+{
+	struct regmap *regmap = tfa9865->regmap;
+	unsigned int seed;
+	int ret;
+
+	/* Leave power down to make the protected registers accessible */
+	regmap_clear_bits(regmap, TFA9865_SYS_CTRL0, BIT(TFA9865_SYS_CTRL0_PWDN));
+	regmap_clear_bits(regmap, TFA9865_CLK_GATING_CTRL, TFA9865_CLK_GATING_CTRL_MANAOOSC);
+	ret = tfa9865_wait_manstate(tfa9865, TFA9865_MANSTATE_WAIT_SRC);
+	if (ret)
+		return ret;
+
+	/* Unlock key 1 and key 2 */
+	regmap_write(regmap, TFA9865_HIDE_UNHIDE_KEY, 0x5a6b);
+	ret = regmap_read(regmap, TFA9865_KEY1_SEED, &seed);
+	if (ret)
+		return ret;
+	regmap_write(regmap, TFA9865_KEY1, seed ^ 0x005a);
+	regmap_write(regmap, TFA9865_KEY2, 0x005a);
+
+	ret = regmap_multi_reg_write(regmap, tfa9865_b0_init, ARRAY_SIZE(tfa9865_b0_init));
+
+	/* Lock the keys again */
+	regmap_write(regmap, TFA9865_KEY2, 0);
+	regmap_write(regmap, TFA9865_KEY1, 0);
+	regmap_write(regmap, TFA9865_HIDE_UNHIDE_KEY, 0);
+	if (ret)
+		return ret;
+
+	/* Go back to power down with the oscillator off */
+	regmap_set_bits(regmap, TFA9865_SYS_CTRL0, BIT(TFA9865_SYS_CTRL0_PWDN));
+	ret = tfa9865_wait_manstate(tfa9865, TFA9865_MANSTATE_POWERDOWN);
+	if (ret)
+		return ret;
+	regmap_set_bits(regmap, TFA9865_CLK_GATING_CTRL, TFA9865_CLK_GATING_CTRL_MANAOOSC);
+
+	/* Input attenuation used by the vendor configuration */
+	regmap_update_bits(regmap, TFA9865_TDM_GAIN,
+			   TFA9865_TDM_GAIN_TDMSPKG_MAX << TFA9865_TDM_GAIN_TDMSPKG,
+			   5 << TFA9865_TDM_GAIN_TDMSPKG);
+
+	/* Configuration done, let the device manager start on power up */
+	return regmap_set_bits(regmap, TFA9865_SYS_CTRL1, TFA9865_SYS_CTRL1_MANSCONF);
+}
+
+static int tfa9865_i2c_probe(struct i2c_client *i2c)
+{
+	struct device *dev = &i2c->dev;
+	struct tfa9865 *tfa9865;
+	unsigned int val, devrev;
+	int ret;
+
+	tfa9865 = devm_kzalloc(dev, sizeof(*tfa9865), GFP_KERNEL);
+	if (!tfa9865)
+		return -ENOMEM;
+
+	i2c_set_clientdata(i2c, tfa9865);
+
+	ret = devm_regulator_get_enable_optional(dev, "vddd");
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to enable vddd supply\n");
+
+	tfa9865->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(tfa9865->reset_gpiod))
+		return dev_err_probe(dev, PTR_ERR(tfa9865->reset_gpiod),
+				     "failed to get reset gpio\n");
+	if (tfa9865->reset_gpiod) {
+		usleep_range(1000, 2000);
+		gpiod_set_value_cansleep(tfa9865->reset_gpiod, 0);
+		usleep_range(1000, 2000);
+	}
+
+	tfa9865->regmap = devm_regmap_init_i2c(i2c, &tfa9865_regmap);
+	if (IS_ERR(tfa9865->regmap))
+		return PTR_ERR(tfa9865->regmap);
+
+	ret = regmap_read(tfa9865->regmap, TFA9865_REVISIONNUMBER, &val);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to read revision number\n");
+
+	if (FIELD_GET(TFA9865_REVISIONNUMBER_FAM, val) != TFA9865_FAMILY ||
+	    FIELD_GET(TFA9865_REVISIONNUMBER_REV, val) != TFA9865_REVISION)
+		return dev_err_probe(dev, -ENODEV, "unknown revision number %#x\n", val);
+
+	ret = regmap_read(tfa9865->regmap, TFA9865_DEVICE_INFO, &val);
+	if (ret)
+		return ret;
+
+	/* Only the B0 init sequence is known */
+	devrev = FIELD_GET(TFA9865_DEVICE_INFO_DEVREV, val);
+	if (((devrev >> 2) & 0x3) != 1 || (devrev & 0xf0))
+		return dev_err_probe(dev, -ENODEV, "unsupported device revision %#x\n", devrev);
+
+	ret = tfa9865_init(tfa9865);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to initialize\n");
+
+	return devm_snd_soc_register_component(dev, &tfa9865_component, &tfa9865_dai, 1);
+}
+
+static const struct of_device_id tfa9865_of_match[] = {
+	{ .compatible = "nxp,tfa9865" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, tfa9865_of_match);
+
+static struct i2c_driver tfa9865_i2c_driver = {
+	.driver = {
+		.name = "tfa9865",
+		.of_match_table = tfa9865_of_match,
+	},
+	.probe = tfa9865_i2c_probe,
+};
+module_i2c_driver(tfa9865_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC NXP TFA9865 speaker amplifier driver");
+MODULE_LICENSE("GPL");

-- 
2.56.0



^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH 1/2] ASoC: dt-bindings: Add NXP TFA9865 amplifier
  2026-11-15 20:20 ` [PATCH 1/2] ASoC: dt-bindings: Add " Venkat Yadlapati via B4 Relay
@ 2026-11-15 20:34   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-15 20:34 UTC (permalink / raw)
  To: Venkat Yadlapati; +Cc: linux-sound, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] ASoC: dt-bindings: redundant sound-name-prefix in
  nxp,tfa9865.yaml
--

commit cf7a1e88df6af54e01055454b9dbda2662c5f8d6
Author: Venkat Yadlapati <venkat.yadlapati@yahoo.com>

ASoC: dt-bindings: Add NXP TFA9865 amplifier

This commit adds the Devicetree binding schema and MAINTAINERS entry for
the NXP TFA9865 mono class-D speaker amplifier.

> diff --git a/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml b/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml
> new file mode 100644
> index 0000000000000..955e25abe7494
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/nxp,tfa9865.yaml
> @@ -0,0 +1,65 @@

[ ... ]

> +allOf:
> +  - $ref: dai-common.yaml#
> +
> +properties:
> +  compatible:
> +    const: nxp,tfa9865
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  reset-gpios:
> +    maxItems: 1
> +
> +  vddd-supply:
> +    description: Digital supply (1.8 V)
> +
> +  '#sound-dai-cells':
> +    const: 0
> +
> +  sound-name-prefix: true
> +
> +required:
> +  - compatible
> +  - reg
> +  - '#sound-dai-cells'
> +
> +unevaluatedProperties: false

[Severity: Low]
Is sound-name-prefix: true needed here?  dai-common.yaml# references
component-common.yaml#, which already defines sound-name-prefix, and
unevaluatedProperties: false permits properties from referenced allOf
schemas without re-listing them when no extra constraints are added.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261116-vantage-tfa9865-v1-0-e88f0dccc427@yahoo.com?part=1

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver
  2026-11-15 20:20 ` [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver Venkat Yadlapati via B4 Relay
@ 2026-11-15 20:44   ` sashiko-bot
  0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-11-15 20:44 UTC (permalink / raw)
  To: Venkat Yadlapati; +Cc: linux-sound, devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [High] ASoC: tfa9865: sleeping in atomic trigger context in
  tfa9865_mute_stream()
- [Medium] ASoC: tfa9865: AMPE state desync on Amp Switch toggle in
  tfa9865_mute_stream()
- [Medium] ASoC: tfa9865: missing software reset or MANSCONF clear in
  tfa9865_init()
--

commit 9362aea049536ac41ce721fb8b374074a607c998
Author: Venkat Yadlapati <venkat.yadlapati@yahoo.com>

ASoC: codecs: Add NXP TFA9865 amplifier driver

This commit adds an ASoC codec driver for the NXP/Goodix TFA9865 mono
class-D speaker amplifier, supporting the B0 revision with 32-bit I2S
slots.

> diff --git a/sound/soc/codecs/tfa9865.c b/sound/soc/codecs/tfa9865.c
> new file mode 100644
> index 0000000000000..7467b8b21eda7
> --- /dev/null
> +++ b/sound/soc/codecs/tfa9865.c

[ ... ]

> +static const struct snd_soc_dapm_widget tfa9865_dapm_widgets[] = {
> +	SND_SOC_DAPM_OUTPUT("OUT"),
> +	SND_SOC_DAPM_SUPPLY("POWER", TFA9865_SYS_CTRL0, TFA9865_SYS_CTRL0_PWDN, 1, NULL, 0),
> +	SND_SOC_DAPM_SUPPLY("BOOST", TFA9865_SYS_CTRL0, TFA9865_SYS_CTRL0_DCA, 0, NULL, 0),
> +	SND_SOC_DAPM_OUT_DRV("AMPE", SND_SOC_NOPM, 0, 0, NULL, 0),
> +	SND_SOC_DAPM_SWITCH("Amp", SND_SOC_NOPM, 0, 0, &amp_switch),

[ ... ]

> +static int tfa9865_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
> +{
> +	struct snd_soc_component *component = dai->component;
> +	unsigned int val;
> +
> +	/*
> +	 * Enable the amplifier only once the I2S clocks run, enabling it
> +	 * earlier makes it pop when the clocks start. Leave it off if the
> +	 * output is not powered.
> +	 */
> +	if (!mute) {
> +		val = snd_soc_component_read(component, TFA9865_SYS_CTRL0);
> +		if (val & BIT(TFA9865_SYS_CTRL0_PWDN))
> +			return 0;
> +	}
> +
> +	return snd_soc_component_update_bits(component, TFA9865_SYS_CTRL0,
> +					     BIT(TFA9865_SYS_CTRL0_AMPE),
> +					     mute ? 0 : BIT(TFA9865_SYS_CTRL0_AMPE));
> +}

[Severity: Medium]
Does toggling the virtual "Amp Switch" control during playback leave the
amplifier enable bit out of sync with the dapm power state?

The "AMPE" widget is configured with no register control and no dapm
event callback, so the amplifier enable bit is only updated in
tfa9865_mute_stream().  If playback starts while "Amp Switch" is off (its
initial state), the power supply widget is off, so tfa9865_mute_stream()
sees the power-down bit set and returns 0 without enabling the amplifier.
When userspace later turns "Amp Switch" on while the stream is active,
dapm powers up the supply and boost widgets, but tfa9865_mute_stream() is
not called again, leaving the speaker silent for the rest of the stream.
Conversely, turning "Amp Switch" off mid-stream powers down the boost and
supply widgets while leaving the amplifier enable bit set.

Also, snd_soc_component_read() returns a negative error code cast to
unsigned int on failure, so testing val without checking for errors first
misinterprets failed reads.

> +
> +static const struct snd_soc_dai_ops tfa9865_dai_ops = {
> +	.hw_params = tfa9865_hw_params,
> +	.mute_stream = tfa9865_mute_stream,
> +	.no_capture_mute = 1,
> +	.mute_unmute_on_trigger = 1,
> +};

[Severity: High]
Does setting .mute_unmute_on_trigger sleep in atomic context when a stream
starts or stops?

With .mute_unmute_on_trigger set, snd_soc_pcm_dai_trigger() calls
snd_soc_dai_digital_mute() -> tfa9865_mute_stream() directly from the pcm
trigger callback while holding the pcm substream spinlock with interrupts
disabled.  tfa9865_mute_stream() then calls snd_soc_component_read() and
snd_soc_component_update_bits(), which lock component->io_mutex and
regmap->mutex and perform sleeping i2c transfers.

[ ... ]

> +static int tfa9865_init(struct tfa9865 *tfa9865)
> +{
> +	struct regmap *regmap = tfa9865->regmap;
> +	unsigned int seed;
> +	int ret;
> +
> +	/* Leave power down to make the protected registers accessible */
> +	regmap_clear_bits(regmap, TFA9865_SYS_CTRL0, BIT(TFA9865_SYS_CTRL0_PWDN));
> +	regmap_clear_bits(regmap, TFA9865_CLK_GATING_CTRL, TFA9865_CLK_GATING_CTRL_MANAOOSC);
> +	ret = tfa9865_wait_manstate(tfa9865, TFA9865_MANSTATE_WAIT_SRC);
> +	if (ret)
> +		return ret;

[ ... ]

> +	/* Configuration done, let the device manager start on power up */
> +	return regmap_set_bits(regmap, TFA9865_SYS_CTRL1, TFA9865_SYS_CTRL1_MANSCONF);
> +}

[Severity: Medium]
Can tfa9865_wait_manstate() time out on driver rebind or warm reboot when
no reset gpio is wired?

tfa9865_init() sets the configuration-done bit at the end of probe so the
device manager advances past the wait-for-source state on later power-ups.
Because reset_gpiod is optional and tfa9865_init() does not issue a
software reset or clear the configuration-done bit before clearing the
power-down bit, re-probing the driver while vddd stays powered lets the
device manager advance past the wait-for-source state and causes
tfa9865_wait_manstate() to fail with -ETIMEDOUT.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261116-vantage-tfa9865-v1-0-e88f0dccc427@yahoo.com?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-15 20:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-15 20:20 [PATCH 0/2] ASoC: Add the NXP TFA9865 amplifier Venkat Yadlapati via B4 Relay
2026-11-15 20:20 ` [PATCH 1/2] ASoC: dt-bindings: Add " Venkat Yadlapati via B4 Relay
2026-11-15 20:34   ` sashiko-bot
2026-11-15 20:20 ` [PATCH 2/2] ASoC: codecs: Add NXP TFA9865 amplifier driver Venkat Yadlapati via B4 Relay
2026-11-15 20:44   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox