Devicetree
 help / color / mirror / Atom feed
From: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
To: Mark Brown <broonie@kernel.org>, Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Bard Liao <yung-chuan.liao@linux.intel.com>,
	Pierre-Louis Bossart <pierre-louis.bossart@linux.dev>,
	Charles Keepax <ckeepax@opensource.cirrus.com>
Cc: Liam Girdwood <lgirdwood@gmail.com>,
	Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
	Maciej Strozek <mstrozek@opensource.cirrus.com>,
	linux-sound@vger.kernel.org, linux-arm-msm@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
	patches@opensource.cirrus.com,
	Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
Subject: [PATCH v5 7/7] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
Date: Fri, 25 Sep 2026 17:10:48 +0100	[thread overview]
Message-ID: <20260925161049.758913-8-srinivas.kandagatla@oss.qualcomm.com> (raw)
In-Reply-To: <20260925161049.758913-1-srinivas.kandagatla@oss.qualcomm.com>

Add support for the Qualcomm Tambora (WCD9378) headset codec in SDCA
mode over SoundWire.  On ARM/DT platforms without ACPI/DisCo firmware
the SDCA topology and SoundWire port properties are supplied as static
data through the codec driver.

The codec exposes a single SimpleJack SDCA Function providing:
  - Headphone playback via FU 6 (mute + Q7.8 volume) and OT 43/45.
  - Headset mic capture via IT 33 with MICB2 bias at 2.75V.
  - MBHC-based headset jack detection.

The driver enables the codec supplies, releases the reset GPIO, and
uses:
  - sdw_slave_ops.read_prop to publish SoundWire slave properties and
    dpn caps for the compute-mode dataports.
  - sdca_class_ops.populate_function to hand the SDCA Function data
    (entities, clusters, init_table) to the class driver from static
    tables.

Binds SoundWire slave id 0x0217:0x0110 when qcom,sdca-compliant is
set on the DT node.

Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
 sound/soc/codecs/Kconfig        |   11 +
 sound/soc/codecs/Makefile       |    2 +
 sound/soc/codecs/wcd9378-sdca.c | 1080 +++++++++++++++++++++++++++++++
 sound/soc/codecs/wcd9378-sdca.h |   20 +
 sound/soc/codecs/wcd9378-sdw.c  |   51 ++
 5 files changed, 1164 insertions(+)
 create mode 100644 sound/soc/codecs/wcd9378-sdca.c
 create mode 100644 sound/soc/codecs/wcd9378-sdca.h
 create mode 100644 sound/soc/codecs/wcd9378-sdw.c

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 3cce95528dbf..ba59dabeeccd 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2552,6 +2552,17 @@ config SND_SOC_WCD939X_SDW
 	  The WCD9390/9395 is a audio codec IC Integrated in
 	  Qualcomm SoCs like SM8650.
 
+config SND_SOC_WCD9378_SDCA
+	tristate "Qualcomm Tambora (WCD9378) SDCA codec"
+	depends on SOUNDWIRE
+	depends on SND_SOC_SDCA_CLASS
+	help
+	  This enables support for the Qualcomm Tambora (WCD9378) headset
+	  codec when driven via the SDCA class driver on ARM platforms
+	  without ACPI/DisCo tables. It provides the static SDCA topology
+	  and SoundWire data port properties transcribed from the factory
+	  ACPI tables.
+
 config SND_SOC_WM0010
 	tristate
 	depends on SPI_MASTER
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 1679c3ffc634..3826f9e4fce8 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -369,6 +369,7 @@ snd-soc-wcd938x-y := wcd938x.o
 snd-soc-wcd938x-sdw-y := wcd938x-sdw.o
 snd-soc-wcd939x-y := wcd939x.o
 snd-soc-wcd939x-sdw-y := wcd939x-sdw.o
+snd-soc-wcd9378-y := wcd9378-sdw.o wcd9378-sdca.o
 snd-soc-wm-adsp-y := wm_adsp.o
 snd-soc-wm-adsp-test-y := wm_adsp_fw_find_test.o
 snd-soc-wm0010-y := wm0010.o
@@ -825,6 +826,7 @@ ifdef CONFIG_SND_SOC_WCD939X_SDW
 # avoid link failure by forcing sdw code built-in when needed
 obj-$(CONFIG_SND_SOC_WCD939X) += snd-soc-wcd939x-sdw.o
 endif
+obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378.o
 obj-$(CONFIG_SND_SOC_WM0010)	+= snd-soc-wm0010.o
 obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o
 obj-$(CONFIG_SND_SOC_WM2000)	+= snd-soc-wm2000.o
diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c
new file mode 100644
index 000000000000..7c15f885390c
--- /dev/null
+++ b/sound/soc/codecs/wcd9378-sdca.c
@@ -0,0 +1,1080 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright (c) 2025 Qualcomm Technologies, Inc. All rights reserved.
+
+/*
+ * WCD9378 (Tambora) SDCA SimpleJack codec. Supplies the static SDCA
+ * topology on DT platforms where no ACPI/DisCo enumeration exists.
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/workqueue.h>
+#include <sound/pcm.h>
+#include <sound/sdca.h>
+#include <sound/sdca_class.h>
+#include <sound/sdca_function.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include "wcd9378-sdca.h"
+
+struct wcd9378_priv {
+	struct sdca_class_drv class;
+};
+
+static struct sdca_entity wcd9378_sdca_entities[];
+/*
+ * Entity array index map. Order matches the ASL entity-id-list;
+ * Function (Entity 0) is last.
+ *
+ *  [0]  E001 IT 41    (0x1)   [12] E00F IT 33    (0xf)
+ *  [1]  E002 CS 41    (0x2)   [13] E010 PDE 34   (0x10)
+ *  [2]  E003 MFPU 21  (0x3)   [14] E011 FU 33    (0x11)
+ *  [3]  E004 XU 42    (0x4)   [15] E012 SU 35    (0x12)
+ *  [4]  E007 SU 43    (0x7)   [16] E013 XU 36    (0x13)
+ *  [5]  E008 SU 45    (0x8)   [17] E015 CS 36    (0x15)
+ *  [6]  E009 PDE 47   (0x9)   [18] E016 OT 36    (0x16)
+ *  [7]  E00A OT 43    (0xa)   [19] E017 MFPU 236 (0x17)
+ *  [8]  E00B OT 45    (0xb)   [20] E018 CS 236   (0x18)
+ *  [9]  E00C GE 35    (0xc)   [21] E019 OT 236   (0x19)
+ *  [10] E00D IT 131   (0xd)   [22] E006 FU 6     (0x6)
+ *  [11] E00E CS 131   (0xe)   [23] E000 Function (0x0)
+ */
+#define QSJ_IT41	0
+#define QSJ_CS41	1
+#define QSJ_MFPU21	2
+#define QSJ_XU42	3
+#define QSJ_SU43	4
+#define QSJ_SU45	5
+#define QSJ_PDE47	6
+#define QSJ_OT43	7
+#define QSJ_OT45	8
+#define QSJ_GE35	9
+#define QSJ_IT131	10
+#define QSJ_CS131	11
+#define QSJ_IT33	12
+#define QSJ_PDE34	13
+#define QSJ_FU33	14
+#define QSJ_SU35	15
+#define QSJ_XU36	16
+#define QSJ_CS36	17
+#define QSJ_OT36	18
+#define QSJ_MFPU236	19
+#define QSJ_CS236	20
+#define QSJ_OT236	21
+#define QSJ_FU6		22
+
+/* Range data: {cols, rows, data[cols*rows]}, transcribed from ASL. */
+
+/* IT 41 Usage: HIFI (0x2). ULP (0x3) does not route to the HPH analog driver. */
+static u32 range_it41_usage_data[] = {
+	/* usage, CBN, sample_rate, sample_width, full_scale, noise_floor, tag */
+	0x2, 0x2d0, 0xbb80, 0x10, 0x0, 0x0, 0x0,
+};
+
+static u32 range_it41_cluster_data[] = { 0x1, 0x1 };
+
+/* IT 41 DataPort selector: DP6 (HPH render). */
+static u32 range_it41_dp_data[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/* CS 41 SampleRateIndex: 1 -> 48kHz PCM. */
+static u32 range_cs41_sr_data[] = { 0x1, 0xbb80 };
+
+/* SU selector: disconnected / connected. */
+static u32 range_su_sel_data[] = { 0x0, 0x1 };
+
+/* PDE Requested_PS: PS0 / PS3. */
+static u32 range_pde_req_ps_data[] = { 0x0, 0x3 };
+
+/*
+ * GE 35 SelectedMode -> terminal type.
+ *
+ * Tambora MBHC only handles mechanical detection; ADC HP/HS
+ * discrimination is not implemented, so modes 0 (Unplugged) and 1
+ * (Unknown) are aliased to Headphone (mode 4) to keep the DAPM path
+ * alive. Mode 2 (Line-out) is not fitted on this board.
+ */
+static u32 range_ge35_mode_data[] = {
+	0x0, 0x6c0,	/* Unplugged -> HPH (alias) */
+	0x1, 0x6c0,	/* Unknown -> HPH (alias) */
+	0x3, 0x6d0,	/* Headset */
+	0x4, 0x6c0,	/* Headphone */
+};
+
+/* IT 131 Usage: optimization render stream at 192kHz. */
+static u32 range_it131_usage_data[] = {
+	0x3, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0,
+};
+
+static u32 range_it131_cluster_data[] = { 0x1, 0x3 };
+
+/* IT 131 DataPort selector: DP7. */
+static u32 range_it131_dp_data[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/* CS 131 SampleRateIndex: 1 -> 192kHz. */
+static u32 range_cs131_sr_data[] = { 0x1, 0x2ee00 };
+
+/* IT 33 MIC_BIAS: SDCA MIC_BIAS index 0x5 (2.75V). */
+static u32 range_it33_micbias_data[] = { 0x5 };
+
+static u32 range_it33_usage_data[] = {
+	0x1, 0x2c6, 0x0, 0x0, 0x0, 0x0, 0x0,
+};
+
+static u32 range_it33_cluster_data[] = { 0x1, 0x2 };
+
+static u32 range_cs36_sr_data[] = { 0x1, 0xbb80 };
+
+/* OT 36 Usage: PDM capture, host-visible 48kHz/16-bit PCM. */
+static u32 range_ot36_usage_data[] = {
+	0x1, 0x2c6, 0xbb80, 0x10, 0x0, 0x0, 0x0,
+};
+
+/* OT 36 DataPort selector: DP2. */
+static u32 range_ot36_dp_data[] = {
+	0xff, 0xff, 0x2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+static u32 range_cs236_sr_data[] = { 0x1, 0xbb80 };
+
+/* OT 236 Usage: optimization capture at 192kHz (clocked by CS 131). */
+static u32 range_ot236_usage_data[] = {
+	0x1, 0x334, 0x2ee00, 0x8, 0x0, 0x0, 0x0,
+};
+
+/* OT 236 DataPort selector: DP5. */
+static u32 range_ot236_dp_data[] = {
+	0xff, 0xff, 0xff, 0xff, 0xff, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
+};
+
+/* Entity 0 (Function) Control Values */
+static int ctrl_fun_sdca_ver_vals[] = { 0x11 };
+static int ctrl_fun_type_vals[] = { 0x08 };	/* SimpleJack */
+static int ctrl_fun_man_id_vals[] = { 0x0217 };
+static int ctrl_fun_id_vals[] = { 0x3 };
+static int ctrl_fun_ver_vals[] = { 0x0 };
+static int ctrl_dev_sdca_ver_vals[] = { 0x11 };
+
+/* Entity 1 (IT 41) Control Values */
+static int ctrl_it41_latency_vals[] = { 0x0 };
+static int ctrl_it41_cluster_vals[] = { 0x1 };
+static int ctrl_it41_dp_vals[] = { 0x6 };
+
+/* Entity 2 (CS 41) Control Values */
+static int ctrl_cs41_sr_vals[] = { 0x1 };
+
+/* Entity 3 (MFPU 21) Control Values */
+static int ctrl_mfpu21_bypass_vals[] = { 0x1 };
+
+/* Entity 4 (XU 42) Control Values */
+static int ctrl_xu42_id_vals[] = { 0x2131 };
+static int ctrl_xu42_ver_vals[] = { 0x1 };
+
+/* Entity 0xd (IT 131) Control Values */
+static int ctrl_it131_latency_vals[] = { 0x0 };
+static int ctrl_it131_cluster_vals[] = { 0x1 };
+static int ctrl_it131_dp_vals[] = { 0x7 };
+
+/* Entity 0xe (CS 131) Control Values */
+static int ctrl_cs131_sr_vals[] = { 0x1 };
+
+/* IT 33 MIC_BIAS: fixed 2.75V (SDCA MIC_BIAS index 0x5), reapplied by PDE34. */
+static int ctrl_it33_micbias_vals[] = { 0x5 };
+static int ctrl_it33_latency_vals[] = { 0x0 };
+static int ctrl_it33_cluster_vals[] = { 0x1 };
+
+/* Entity 0x13 (XU 36) Control Values */
+static int ctrl_xu36_bypass_vals[] = { 0x1 };
+static int ctrl_xu36_id_vals[] = { 0x2131 };
+static int ctrl_xu36_ver_vals[] = { 0x1 };
+
+/* Entity 0x15 (CS 36) Control Values */
+static int ctrl_cs36_sr_vals[] = { 0x1 };
+
+/* Entity 0x16 (OT 36) Control Values */
+static int ctrl_ot36_latency_vals[] = { 0x0 };
+static int ctrl_ot36_dp_vals[] = { 0x2 };
+
+/* Entity 0x17 (MFPU 236) Control Values */
+static int ctrl_mfpu236_bypass_vals[] = { 0x1 };
+
+/* Entity 0x18 (CS 236) Control Values */
+static int ctrl_cs236_sr_vals[] = { 0x1 };
+
+/* Entity 0x19 (OT 236) Control Values */
+static int ctrl_ot236_latency_vals[] = { 0x0 };
+static int ctrl_ot236_dp_vals[] = { 0x5 };
+
+/* Entity 0 (Function) Controls */
+static struct sdca_control entity0_controls[] = {
+	{ .sel = 0x1,  .mode = SDCA_ACCESS_MODE_RW,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_COMMIT_GROUP_MASK_NAME },
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_fun_sdca_ver_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_FUNCTION_SDCA_VERSION_NAME },
+	{ .sel = 0x5,  .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_fun_type_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_FUNCTION_TYPE_NAME },
+	{ .sel = 0x6,  .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_fun_man_id_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_FUNCTION_MANUFACTURER_ID_NAME },
+	{ .sel = 0x7,  .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_fun_id_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_FUNCTION_ID_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_fun_ver_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_FUNCTION_VERSION_NAME },
+	{ .sel = 0x9,  .mode = SDCA_ACCESS_MODE_RO,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_ID_NAME },
+	{ .sel = 0xa,  .mode = SDCA_ACCESS_MODE_RO,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_FUNCTION_EXTENSION_VERSION_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_RW1C, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_FUNCTION_STATUS_NAME },
+	{ .sel = 0x11, .mode = SDCA_ACCESS_MODE_RW1S, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_FUNCTION_ACTION_NAME },
+	{ .sel = 0x2c, .mode = SDCA_ACCESS_MODE_RO,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_DEVICE_MANUFACTURER_ID_NAME },
+	{ .sel = 0x2d, .mode = SDCA_ACCESS_MODE_RO,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_DEVICE_PART_ID_NAME },
+	{ .sel = 0x2e, .mode = SDCA_ACCESS_MODE_RO,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_DEVICE_VERSION_NAME },
+	{ .sel = 0x2f, .mode = SDCA_ACCESS_MODE_DC,   .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_dev_sdca_ver_vals, .has_fixed = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_DEVICE_SDCA_VERSION_NAME },
+};
+
+/* Entity 1 (IT 41) Controls */
+static struct sdca_control entity_it41_controls[] = {
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x7, .rows = 0x1, .data = range_it41_usage_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_USAGE_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_it41_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it41_cluster_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_it41_cluster_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_CLUSTERINDEX_NAME },
+	{ .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it41_dp_vals, .has_fixed = true,
+	  .range = { .cols = 0x10, .rows = 0x4, .data = range_it41_dp_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_DATAPORT_SELECTOR_NAME },
+};
+
+/* Entity 2 (CS 41) Controls */
+static struct sdca_control entity_cs41_controls[] = {
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_cs41_sr_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_cs41_sr_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SAMPLERATEINDEX_NAME },
+};
+
+/* Entity 3 (MFPU 21) Controls */
+static struct sdca_entity *entity_mfpu21_sources[] = {
+	&wcd9378_sdca_entities[QSJ_IT41],
+	&wcd9378_sdca_entities[QSJ_IT131],
+};
+
+static struct sdca_control entity_mfpu21_controls[] = {
+	{ .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 1,
+	  .values = ctrl_mfpu21_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME },
+};
+
+/* Entity 4 (XU 42) Controls */
+static struct sdca_entity *entity_xu42_sources[] = { &wcd9378_sdca_entities[QSJ_MFPU21] };
+
+static struct sdca_control entity_xu42_controls[] = {
+	{ .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 1,
+	  .label = SDCA_CTL_BYPASS_NAME },
+	{ .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_xu42_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME },
+	{ .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_xu42_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME },
+};
+
+/*
+ * SU Selector is RO but its value is deterministic from the current
+ * GE mode which the class framework drives; leave it non-volatile so
+ * regmap caches it and DAPM reads succeed when the codec is
+ * runtime-suspended.
+ */
+
+/* Entity 7 (SU 43) Controls */
+static struct sdca_entity *entity_su43_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] };
+
+static struct sdca_control entity_su43_controls[] = {
+	{ .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE,
+	  .cn_list = 0x1,
+	  .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SELECTOR_NAME },
+};
+
+/* Entity 8 (SU 45) Controls */
+static struct sdca_entity *entity_su45_sources[] = { &wcd9378_sdca_entities[QSJ_XU42] };
+
+static struct sdca_control entity_su45_controls[] = {
+	{ .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_DEVICE,
+	  .cn_list = 0x1,
+	  .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SELECTOR_NAME },
+};
+
+/*
+ * PDE 47 manages the HPH render path (OT 43, OT 45). FU 6 is listed
+ * so its cached mute/volume are reasserted on PS0 entry.
+ */
+static struct sdca_entity *entity_pde47_managed[] = {
+	&wcd9378_sdca_entities[QSJ_FU6],
+	&wcd9378_sdca_entities[QSJ_OT43],
+	&wcd9378_sdca_entities[QSJ_OT45],
+};
+
+static struct sdca_pde_delay pde47_delays[] = {
+	{ .from_ps = 3, .to_ps = 0, .us = 30000 },
+	{ .from_ps = 0, .to_ps = 3, .us = 30000 },
+};
+
+static struct sdca_control entity_pde47_controls[] = {
+	{ .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 3,
+	  .label = SDCA_CTL_REQUESTED_PS_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME },
+	/*
+	 * HPH protection IRQs (OCP/CNP/SURGE) fire in codec HW but are
+	 * not exposed to Linux; needs an SDCA framework interface for
+	 * standalone status-bit IRQs. Wire up in a follow-up.
+	 */
+};
+
+/* OT 43 (Headphone), OT 45 (Headset): no controls. */
+static struct sdca_entity *entity_ot43_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] };
+static struct sdca_entity *entity_ot45_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] };
+
+/*
+ * GE 35 mode -> SU selector. Modes 0/1 alias to Headphone (see
+ * range_ge35_mode_data). SU 45 = source 1 (XU 42) for HPH paths,
+ * SU 43 = source 1 (XU 42) for Headset.
+ */
+static struct sdca_ge_control ge35_mode0_controls[] = {
+	{ .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 },
+};
+
+static struct sdca_ge_control ge35_mode1_controls[] = {
+	{ .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 },
+};
+
+static struct sdca_ge_control ge35_mode3_controls[] = {
+	{ .id = 0x7, .sel = 0x1, .cn = 0x0, .val = 0x1 },
+};
+
+static struct sdca_ge_control ge35_mode4_controls[] = {
+	{ .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 },
+};
+
+static struct sdca_ge_mode ge35_modes[] = {
+	{ .val = 0x0, .num_controls = ARRAY_SIZE(ge35_mode0_controls), .controls = ge35_mode0_controls },
+	{ .val = 0x1, .num_controls = ARRAY_SIZE(ge35_mode1_controls), .controls = ge35_mode1_controls },
+	{ .val = 0x3, .num_controls = ARRAY_SIZE(ge35_mode3_controls), .controls = ge35_mode3_controls },
+	{ .val = 0x4, .num_controls = ARRAY_SIZE(ge35_mode4_controls), .controls = ge35_mode4_controls },
+};
+
+static struct sdca_control entity_ge35_controls[] = {
+	{ .sel = SDCA_CTL_GE_SELECTED_MODE, .mode = SDCA_ACCESS_MODE_RW,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .range = { .cols = 0x2, .rows = 0x4, .data = range_ge35_mode_data },
+	  .has_reset = true, .reset = 0,
+	  .label = SDCA_CTL_SELECTED_MODE_NAME },
+	{ .sel = 0x2, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = 4, /* SDCA_4 = GE_DETECTED_MODE, see init_table INTMASK_1 */
+	  .is_volatile = true, .label = SDCA_CTL_DETECTED_MODE_NAME },
+};
+
+static struct sdca_control entity_it131_controls[] = {
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x7, .rows = 0x1, .data = range_it131_usage_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_USAGE_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_it131_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it131_cluster_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_it131_cluster_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_CLUSTERINDEX_NAME },
+	{ .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it131_dp_vals, .has_fixed = true,
+	  .range = { .cols = 0x10, .rows = 0x4, .data = range_it131_dp_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_DATAPORT_SELECTOR_NAME },
+};
+
+/* Entity 0xe (CS 131) Controls */
+static struct sdca_control entity_cs131_controls[] = {
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_cs131_sr_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_cs131_sr_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SAMPLERATEINDEX_NAME },
+};
+
+/* Entity 0xf (IT 33) - headset mic input */
+static struct sdca_control entity_it33_controls[] = {
+	{ .sel = 0x3,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it33_micbias_vals, .has_default = true,
+	  .range = { .cols = 0x1, .rows = 0x1, .data = range_it33_micbias_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_MIC_BIAS_NAME },
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x7, .rows = 0x1, .data = range_it33_usage_data },
+	  .has_reset = true,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_USAGE_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_it33_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_it33_cluster_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_it33_cluster_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_CLUSTERINDEX_NAME },
+};
+
+/* Entity 0x10 (PDE 34) - manages IT 33 */
+static struct sdca_entity *entity_pde34_managed[] = { &wcd9378_sdca_entities[QSJ_IT33] };
+
+static struct sdca_pde_delay pde34_delays[] = {
+	{ .from_ps = 3, .to_ps = 0, .us = 30000 },
+	{ .from_ps = 0, .to_ps = 3, .us = 30000 },
+};
+
+static struct sdca_control entity_pde34_controls[] = {
+	{ .sel = SDCA_CTL_PDE_REQUESTED_PS, .mode = SDCA_ACCESS_MODE_RW,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x1, .rows = 0x2, .data = range_pde_req_ps_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 3,
+	  .label = SDCA_CTL_REQUESTED_PS_NAME },
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME },
+};
+
+/*
+ * FU 6 (vendor FU42): HPH mute + Q7.8 volume. DUAL-mode CVR-alias
+ * writes take effect without an SCP_COMMIT.
+ */
+/*
+ * FU 6 volume range: MIN, MAX, STEP in Q7.8 (LSB = 1/256 dB).
+ * 0x8000 = -128 dB, 0x7fff = +127.996 dB, STEP = 1. Framework
+ * sign-extends and converts to 0.01 dB TLV via (val * 100) >> 8.
+ */
+static u32 range_fu6_vol_data[] = {
+	0x00008000, 0x00007fff, 0x00000001,
+};
+
+static struct sdca_control entity_fu6_controls[] = {
+	{ .sel = SDCA_CTL_FU_MUTE, .mode = SDCA_ACCESS_MODE_DUAL,
+	  .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 1,
+	  .type = SDCA_CTL_DATATYPE_ONEBIT,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 1,
+	  .label = SDCA_CTL_MUTE_NAME },
+	{ .sel = SDCA_CTL_FU_CHANNEL_VOLUME, .mode = SDCA_ACCESS_MODE_DUAL,
+	  .layers = SDCA_ACCESS_LAYER_USER, .cn_list = 0x6, .nbits = 16,
+	  .type = SDCA_CTL_DATATYPE_Q7P8DB,
+	  .range = { .cols = SDCA_VOLUME_LINEAR_NCOLS, .rows = 1,
+		     .data = range_fu6_vol_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_CHANNEL_VOLUME_NAME },
+};
+
+/* Entity 0x11 (FU 33) - no controls */
+static struct sdca_entity *entity_fu33_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] };
+
+/* Entity 0x12 (SU 35) Controls */
+static struct sdca_entity *entity_su35_sources[] = { &wcd9378_sdca_entities[QSJ_FU33] };
+
+/*
+ * SU 35 is class-layer: the SDCA class framework drives the Selector
+ * directly via a DAPM mux widget, so the Control must be writeable.
+ */
+static struct sdca_control entity_su35_controls[] = {
+	{ .sel = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SELECTOR_NAME },
+};
+
+/* Entity 0x13 (XU 36) Controls */
+static struct sdca_entity *entity_xu36_sources[] = { &wcd9378_sdca_entities[QSJ_SU35] };
+
+static struct sdca_control entity_xu36_controls[] = {
+	/* bypass=1: pass mic signal through XU36 without proprietary processing */
+	{ .sel = SDCA_CTL_XU_BYPASS, .mode = SDCA_ACCESS_MODE_RW,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 1,
+	  .values = ctrl_xu36_bypass_vals, .has_default = true, .label = SDCA_CTL_BYPASS_NAME },
+	{ .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_xu36_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME },
+	{ .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_xu36_ver_vals, .has_fixed = true, .label = SDCA_CTL_XU_VERSION_NAME },
+};
+
+/* Entity 0x15 (CS 36) Controls */
+static struct sdca_control entity_cs36_controls[] = {
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_cs36_sr_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_cs36_sr_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SAMPLERATEINDEX_NAME },
+};
+
+/* OT 36 mic capture: IT33 -> FU33 -> SU35 -> XU36 -> OT36. */
+static struct sdca_entity *entity_ot36_sources[] = { &wcd9378_sdca_entities[QSJ_XU36] };
+
+static struct sdca_control entity_ot36_controls[] = {
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x7, .rows = 0x1, .data = range_ot36_usage_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_USAGE_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_ot36_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+	{ .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_ot36_dp_vals, .has_fixed = true,
+	  .range = { .cols = 0x10, .rows = 0x4, .data = range_ot36_dp_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_DATAPORT_SELECTOR_NAME },
+};
+
+/* Entity 0x17 (MFPU 236) Controls */
+static struct sdca_control entity_mfpu236_controls[] = {
+	{ .sel = SDCA_CTL_MFPU_BYPASS, .mode = SDCA_ACCESS_MODE_DC,
+	  .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .has_reset = true, .reset = 1,
+	  .values = ctrl_mfpu236_bypass_vals, .has_fixed = true, .label = SDCA_CTL_BYPASS_NAME },
+};
+
+/* Entity 0x18 (CS 236) Controls */
+static struct sdca_control entity_cs236_controls[] = {
+	{ .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_cs236_sr_vals, .has_fixed = true,
+	  .range = { .cols = 0x2, .rows = 0x1, .data = range_cs236_sr_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_SAMPLERATEINDEX_NAME },
+};
+
+/* Entity 0x19 (OT 236) - optimization stream capture output */
+static struct sdca_entity *entity_ot236_sources[] = { &wcd9378_sdca_entities[QSJ_IT33] };
+
+static struct sdca_control entity_ot236_controls[] = {
+	{ .sel = 0x4,  .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .range = { .cols = 0x7, .rows = 0x1, .data = range_ot236_usage_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_USAGE_NAME },
+	{ .sel = 0x8,  .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .values = ctrl_ot236_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+	{ .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+	  .values = ctrl_ot236_dp_vals, .has_fixed = true,
+	  .range = { .cols = 0x10, .rows = 0x4, .data = range_ot236_dp_data },
+	  .interrupt_position = SDCA_NO_INTERRUPT,
+	  .label = SDCA_CTL_DATAPORT_SELECTOR_NAME },
+};
+
+static struct sdca_entity wcd9378_sdca_entities[] = {
+	/* [0] E001: IT 41 - PDM render stream input */
+	{ .id = 0x1, .label = "IT 41", .type = SDCA_ENTITY_TYPE_IT,
+	  .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS41] },
+	  .num_controls = ARRAY_SIZE(entity_it41_controls), .controls = entity_it41_controls },
+	/* [1] E002: CS 41 */
+	{ .id = 0x2, .label = "CS 41", .type = SDCA_ENTITY_TYPE_CS,
+	  .cs = { .type = 0x0 },
+	  .num_controls = ARRAY_SIZE(entity_cs41_controls), .controls = entity_cs41_controls },
+	/* [2] E003: MFPU 21 */
+	{ .id = 0x3, .label = "MFPU 21", .type = SDCA_ENTITY_TYPE_MFPU,
+	  .num_controls = ARRAY_SIZE(entity_mfpu21_controls), .controls = entity_mfpu21_controls,
+	  .num_sources = ARRAY_SIZE(entity_mfpu21_sources), .sources = entity_mfpu21_sources },
+	/* [3] E004: XU 42 */
+	{ .id = 0x4, .label = "XU 42", .type = SDCA_ENTITY_TYPE_XU,
+	  .num_controls = ARRAY_SIZE(entity_xu42_controls), .controls = entity_xu42_controls,
+	  .num_sources = ARRAY_SIZE(entity_xu42_sources), .sources = entity_xu42_sources },
+	/* [4] E007: SU 43 - render selector (headset path), driven by GE 35 jack detection */
+	{ .id = 0x7, .label = "SU 43", .type = SDCA_ENTITY_TYPE_SU,
+	  .group = &wcd9378_sdca_entities[QSJ_GE35],
+	  .num_controls = ARRAY_SIZE(entity_su43_controls), .controls = entity_su43_controls,
+	  .num_sources = ARRAY_SIZE(entity_su43_sources), .sources = entity_su43_sources },
+	/* [5] E008: SU 45 - render selector (headphone path), driven by GE 35 jack detection */
+	{ .id = 0x8, .label = "SU 45", .type = SDCA_ENTITY_TYPE_SU,
+	  .group = &wcd9378_sdca_entities[QSJ_GE35],
+	  .num_controls = ARRAY_SIZE(entity_su45_controls), .controls = entity_su45_controls,
+	  .num_sources = ARRAY_SIZE(entity_su45_sources), .sources = entity_su45_sources },
+	/* [6] E009: PDE 47 - render power domain */
+	{ .id = 0x9, .label = "PDE 47", .type = SDCA_ENTITY_TYPE_PDE,
+	  .pde = { .num_managed = ARRAY_SIZE(entity_pde47_managed), .managed = entity_pde47_managed,
+		   .num_max_delay = ARRAY_SIZE(pde47_delays), .max_delay = pde47_delays },
+	  .num_controls = ARRAY_SIZE(entity_pde47_controls), .controls = entity_pde47_controls },
+	/* [7] E00A: OT 43 - Headphone on jack */
+	{ .id = 0xa, .label = "OT 43", .type = SDCA_ENTITY_TYPE_OT,
+	  .iot = { .type = 0x06c0 },
+	  .num_sources = ARRAY_SIZE(entity_ot43_sources), .sources = entity_ot43_sources },
+	/* [8] E00B: OT 45 - Headset output on jack */
+	{ .id = 0xb, .label = "OT 45", .type = SDCA_ENTITY_TYPE_OT,
+	  .iot = { .type = 0x06d0 },
+	  .num_sources = ARRAY_SIZE(entity_ot45_sources), .sources = entity_ot45_sources },
+	/* [9] E00C: GE 35 - jack detection group entity */
+	{ .id = 0xc, .label = "GE 35", .type = SDCA_ENTITY_TYPE_GE,
+	  .ge = { .num_modes = ARRAY_SIZE(ge35_modes), .modes = ge35_modes },
+	  .num_controls = ARRAY_SIZE(entity_ge35_controls), .controls = entity_ge35_controls },
+	/* [10] E00D: IT 131 - optimization stream input */
+	{ .id = 0xd, .label = "IT 131", .type = SDCA_ENTITY_TYPE_IT,
+	  .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] },
+	  .num_controls = ARRAY_SIZE(entity_it131_controls), .controls = entity_it131_controls },
+	/* [11] E00E: CS 131 */
+	{ .id = 0xe, .label = "CS 131", .type = SDCA_ENTITY_TYPE_CS,
+	  .cs = { .type = 0x0 },
+	  .num_controls = ARRAY_SIZE(entity_cs131_controls), .controls = entity_cs131_controls },
+	/* [12] E00F: IT 33 - headset mic input */
+	{ .id = 0xf, .label = "IT 33", .type = SDCA_ENTITY_TYPE_IT,
+	  .iot = { .type = 0x06d0 },
+	  .num_controls = ARRAY_SIZE(entity_it33_controls), .controls = entity_it33_controls },
+	/* [13] E010: PDE 34 - mic power domain */
+	{ .id = 0x10, .label = "PDE 34", .type = SDCA_ENTITY_TYPE_PDE,
+	  .pde = { .num_managed = ARRAY_SIZE(entity_pde34_managed), .managed = entity_pde34_managed,
+		   .num_max_delay = ARRAY_SIZE(pde34_delays), .max_delay = pde34_delays },
+	  .num_controls = ARRAY_SIZE(entity_pde34_controls), .controls = entity_pde34_controls },
+	/* [14] E011: FU 33 - mic feature unit */
+	{ .id = 0x11, .label = "FU 33", .type = SDCA_ENTITY_TYPE_FU,
+	  .num_sources = ARRAY_SIZE(entity_fu33_sources), .sources = entity_fu33_sources },
+	/* [15] E012: SU 35 - mic selector */
+	{ .id = 0x12, .label = "SU 35", .type = SDCA_ENTITY_TYPE_SU,
+	  .num_controls = ARRAY_SIZE(entity_su35_controls), .controls = entity_su35_controls,
+	  .num_sources = ARRAY_SIZE(entity_su35_sources), .sources = entity_su35_sources },
+	/* [16] E013: XU 36 - mic extension unit */
+	{ .id = 0x13, .label = "XU 36", .type = SDCA_ENTITY_TYPE_XU,
+	  .num_controls = ARRAY_SIZE(entity_xu36_controls), .controls = entity_xu36_controls,
+	  .num_sources = ARRAY_SIZE(entity_xu36_sources), .sources = entity_xu36_sources },
+	/* [17] E015: CS 36 */
+	{ .id = 0x15, .label = "CS 36", .type = SDCA_ENTITY_TYPE_CS,
+	  .cs = { .type = 0x0 },
+	  .num_controls = ARRAY_SIZE(entity_cs36_controls), .controls = entity_cs36_controls },
+	/* [18] E016: OT 36 - PDM mic capture output */
+	{ .id = 0x16, .label = "OT 36", .type = SDCA_ENTITY_TYPE_OT,
+	  .iot = { .type = 0x0191, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS36] },
+	  .num_controls = ARRAY_SIZE(entity_ot36_controls), .controls = entity_ot36_controls,
+	  .num_sources = ARRAY_SIZE(entity_ot36_sources), .sources = entity_ot36_sources },
+	/* [19] E017: MFPU 236 - optimization TX processing */
+	{ .id = 0x17, .label = "MFPU 236", .type = SDCA_ENTITY_TYPE_MFPU,
+	  .num_controls = ARRAY_SIZE(entity_mfpu236_controls), .controls = entity_mfpu236_controls },
+	/* [20] E018: CS 236 */
+	{ .id = 0x18, .label = "CS 236", .type = SDCA_ENTITY_TYPE_CS,
+	  .cs = { .type = 0x0 },
+	  .num_controls = ARRAY_SIZE(entity_cs236_controls), .controls = entity_cs236_controls },
+	/* [21] E019: OT 236 - optimization stream capture output */
+	{ .id = 0x19, .label = "OT 236", .type = SDCA_ENTITY_TYPE_OT,
+	  .iot = { .type = 0x0190, .is_dataport = true, .clock = &wcd9378_sdca_entities[QSJ_CS131] },
+	  .num_controls = ARRAY_SIZE(entity_ot236_controls), .controls = entity_ot236_controls,
+	  .num_sources = ARRAY_SIZE(entity_ot236_sources), .sources = entity_ot236_sources },
+	/* E006: FU 6 (FU42) - vendor Feature Unit; HPH mute + Q7.8 volume. */
+	{ .id = 0x6, .label = "FU 6", .type = SDCA_ENTITY_TYPE_FU,
+	  .num_controls = ARRAY_SIZE(entity_fu6_controls), .controls = entity_fu6_controls },
+	/* Entity 0 (Function) */
+	{ .id = 0x0, .label = "entity0",
+	  .num_controls = ARRAY_SIZE(entity0_controls), .controls = entity0_controls },
+};
+
+/* Clusters */
+static struct sdca_channel cl1_channels[] = {	/* CL01 - render (HPH) stereo */
+	{ .id = 0x1, .purpose = 0x1, .relationship = 0x2 },	/* Left */
+	{ .id = 0x2, .purpose = 0x1, .relationship = 0x3 },	/* Right */
+};
+
+static struct sdca_channel cl2_channels[] = {	/* CL02 - mic capture mono */
+	{ .id = 0xff, .purpose = 0x1, .relationship = 0x1 },
+};
+
+static struct sdca_channel cl3_channels[] = {	/* CL03 - optimization RX */
+	{ .id = 0xff, .purpose = 0x1, .relationship = 0x1 },	/* Mono */
+	{ .id = 0x1,  .purpose = 0x1, .relationship = 0x2 },	/* Left */
+	{ .id = 0x2,  .purpose = 0x1, .relationship = 0x3 },	/* Right */
+};
+
+static struct sdca_channel cl5_channels[] = {	/* CL05 - optimization TX mono */
+	{ .id = 0xff, .purpose = 0x1, .relationship = 0x1 },
+};
+
+static struct sdca_cluster wcd9378_sdca_clusters[] = {
+	{ .id = 0x1, .num_channels = ARRAY_SIZE(cl1_channels), .channels = cl1_channels },
+	{ .id = 0x2, .num_channels = ARRAY_SIZE(cl2_channels), .channels = cl2_channels },
+	{ .id = 0x3, .num_channels = ARRAY_SIZE(cl3_channels), .channels = cl3_channels },
+	{ .id = 0x5, .num_channels = ARRAY_SIZE(cl5_channels), .channels = cl5_channels },
+};
+
+/* Init table transcribed from ASL. */
+static struct sdca_init_write wcd9378_sdca_init_table[] = {
+	{ .addr = 0x401804f0, .val = 0x00 }, /* DIGITAL_PLATFORM_CTL */
+	{ .addr = 0x4018046e, .val = 0x10 }, /* DIGITAL_INTR_MODE */
+	{ .addr = 0x0000004d, .val = 0x01 }, /* SWRS_SCP_BUSCLOCK_BASE */
+	{ .addr = 0x00000062, .val = 0x02 }, /* SWRS_SCP_BUSCLOCK_SCALE_BANK */
+	{ .addr = 0x4018016a, .val = 0x80 }, /* CP_DTOP_CTRL_14 */
+	{ .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */
+	{ .addr = 0x40180103, .val = 0x1e }, /* SLEEP_CTL BG_CTL (0.9V) */
+	{ .addr = 0x40180103, .val = 0x9e }, /* SLEEP_CTL BG_EN */
+	{ .addr = 0x40180103, .val = 0xde }, /* SLEEP_CTL LDOL_BG_SEL */
+	{ .addr = 0x40180029, .val = 0xb5 }, /* BIAS_VBG_FINE_ADJ */
+	{ .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS ANALOG_BIAS_EN */
+	{ .addr = 0x40180001, .val = 0xc0 }, /* ANA_BIAS PRECHRG_EN(1) */
+	{ .addr = 0x40180001, .val = 0x80 }, /* ANA_BIAS PRECHRG_EN(0) */
+	{ .addr = 0x4018007b, .val = 0xa2 }, /* TX_COM_TXFE_DIV_CTL SEQ_BYPASS */
+	{ .addr = 0x40180465, .val = 0x17 }, /* PDM_WD_CTL0 TIME_OUT_SEL_PCM */
+	{ .addr = 0x40180466, .val = 0x17 }, /* PDM_WD_CTL1 TIME_OUT_SEL_PCM */
+	{ .addr = 0x4018006c, .val = 0x01 }, /* MICB1_TEST_CTL_2 IBIAS_LDO_DRIVER */
+	{ .addr = 0x40180072, .val = 0x81 }, /* MICB3_TEST_CTL_2 IBIAS_LDO_DRIVER */
+	{ .addr = 0x401800ce, .val = 0x38 }, /* HPH_OCP_CTL OCP_FSM_EN */
+	{ .addr = 0x401800ce, .val = 0x3a }, /* HPH_OCP_CTL SCD_OP_EN */
+	{ .addr = 0x401800d4, .val = 0xe1 }, /* HPH_L_TEST OCP_DET_EN */
+	{ .addr = 0x401800d7, .val = 0xe1 }, /* HPH_R_TEST OCP_DET_EN */
+	{ .addr = 0x4018044e, .val = 0x04 }, /* CDC_HPH_GAIN_CTL HPHL_RX_EN */
+	{ .addr = 0x4018044e, .val = 0x0c }, /* CDC_HPH_GAIN_CTL HPHR_RX_EN */
+	{ .addr = 0x4018000f, .val = 0x0c }, /* ANA_TX_CH2 GAIN (18.0dB) */
+	{ .addr = 0x40180133, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_L */
+	{ .addr = 0x40180136, .val = 0x84 }, /* HPH_NEW_INT_RDAC_HD2_CTL_R */
+	{ .addr = 0x401800d9, .val = 0x19 }, /* HPH_RDAC_CLK_CTL1 OPAMP_CHOP_CLK_EN */
+	{ .addr = 0x40180132, .val = 0x50 }, /* HPH_NEW_INT_RDAC_GAIN_CTL RDAC_GAINCTL(0.55) */
+	{ .addr = 0x40180510, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T0 */
+	{ .addr = 0x40180519, .val = 0x05 }, /* SEQR_CTRL HPH_UP_T9 */
+	{ .addr = 0x4018051b, .val = 0x06 }, /* SEQR_CTRL HPH_DN_T0 */
+	{ .addr = 0x40180414, .val = 0x02 }, /* CDC_COMP_CTL_0 HPHL_COMP_EN */
+	{ .addr = 0x40180414, .val = 0x03 }, /* CDC_COMP_CTL_0 HPHR_COMP_EN */
+	{ .addr = 0x401804f2, .val = 0x80 }, /* DRE_DLY_VAL SWR_HPHL(0) */
+	{ .addr = 0x401804f2, .val = 0x00 }, /* DRE_DLY_VAL SWR_HPHR(0) */
+	{ .addr = 0x40180501, .val = 0x01 }, /* SEQR_CTRL SYS_USAGE_CTRL */
+	/* Arms MBHC: jack insertion asserts SDCA_4 (GE_DETECTED_MODE). */
+	{ .addr = 0x40180601, .val = 0x01 }, /* MBHC_CTRL DEVICE_DET */
+	{ .addr = 0x40180414, .val = 0x00 }, /* CDC_COMP_CTL_0 */
+	{ .addr = 0x401804f2, .val = 0x88 }, /* DRE_DLY_VAL */
+	{ .addr = 0x40180517, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T7 */
+	{ .addr = 0x4018051c, .val = 0x07 }, /* SEQR_CTRL HPH_DN_T1 */
+	{ .addr = 0x401800ce, .val = 0x28 }, /* HPH_OCP_CTL */
+	{ .addr = 0x401800d4, .val = 0xe0 }, /* HPH_L_TEST */
+	{ .addr = 0x401800d7, .val = 0xe0 }, /* HPH_R_TEST */
+	{ .addr = 0x40180510, .val = 0x07 }, /* SEQR_CTRL HPH_UP_T0 */
+	{ .addr = 0x40c80008, .val = 0x01 }, /* SMP_JACK_CTRL FUNC_ACT (RESET_FUNCTION_NOW) */
+	{ .addr = 0x40c00008, .val = 0x02 }, /* SMP_JACK_CTRL CMT_GRP_MASK */
+	{ .addr = 0x40c80000, .val = 0xff }, /* SMP_JACK_CTRL FUNC_STAT */
+	{ .addr = 0x00000000, .val = 0x08 }, /* clear DP0 INT status SDCA_CASCADE */
+	{ .addr = 0x00000041, .val = 0x08 }, /* SCP_INT_STATUS_MASK_1 PORT_0_CASCADE_3 */
+	/* Only SDCA_4 unmasked; protection IRQs stay masked. */
+	{ .addr = 0x0000005c, .val = 0x10 }, /* INTMASK_1 SDCA_4 (GE_DETECTED_MODE) */
+	{ .addr = 0x4018016a, .val = 0x00 }, /* CP_DTOP_CTRL_14 */
+	{ .addr = 0x40180165, .val = 0x6b }, /* CP_DTOP_CTRL_9 */
+	/*
+	 * L_DET_EN is left disabled: asserting SDCA_4 before the machine
+	 * card registers the jack blocks the deferred card probe.
+	 */
+};
+
+/* Function Descriptor */
+static struct sdca_function_desc wcd9378_sdca_desc = {
+	.adr  = 0x3,
+	.type = SDCA_FUNCTION_TYPE_SIMPLE_JACK,
+	.name = SDCA_FUNCTION_TYPE_SIMPLE_NAME,
+};
+
+/* Main Function Data */
+static struct sdca_function_data wcd9378_sdca_data = {
+	.desc             = &wcd9378_sdca_desc,
+	.num_entities     = ARRAY_SIZE(wcd9378_sdca_entities),
+	.entities         = wcd9378_sdca_entities,
+	.num_clusters     = ARRAY_SIZE(wcd9378_sdca_clusters),
+	.clusters         = wcd9378_sdca_clusters,
+	.num_init_table   = ARRAY_SIZE(wcd9378_sdca_init_table),
+	.init_table       = wcd9378_sdca_init_table,
+	.reset_max_delay  = 25000, /* 25ms — WCD9378 power-on reset completes well within this */
+};
+
+/* Vendor SCP register: host clock divide-by-2 (bank 1 shadow). */
+#define WCD9378_SCP_HOST_CLK_DIV2_CTL_B1	0xf0
+
+/* Slave ports 1..8, mapped to master ports via qcom,port-mapping. */
+#define WCD9378_SDCA_MAX_PORTS			8
+
+static const char * const wcd9378_sdca_supplies[] = {
+	"vdd-buck", "vdd-rxtx", "vdd-io", "vdd-mic-bias",
+};
+
+int wcd9378_sdca_read_prop(struct sdw_slave *slave)
+{
+	struct sdw_slave_prop *prop = &slave->prop;
+	struct device *dev = &slave->dev;
+	struct sdw_dpn_prop *sink, *src;
+	int ret;
+
+	/*
+	 * All SoundWire slave properties for compute-mode WCD9378 are
+	 * fixed by the silicon and are not described in DT, so we do
+	 * not call sdw_slave_read_prop() here.
+	 */
+	prop->use_domain_irq = true;
+	prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY |
+			      SDW_SCP_INT1_IMPL_DEF;
+	prop->simple_clk_stop_capable = true;
+	prop->paging_support = true;
+	prop->clock_reg_supported = true;
+	prop->lane_control_support = true;
+
+	/* Source ports: DP2 (headset mic), DP5 (optimisation TX). */
+	prop->source_ports = BIT(2) | BIT(5);
+	/* Sink ports: DP6 (HPH audio), DP7 (HPH envelope), DP8 (optimisation RX). */
+	prop->sink_ports = BIT(6) | BIT(7) | BIT(8);
+
+	src = devm_kcalloc(dev, 2, sizeof(*src), GFP_KERNEL);
+	if (!src)
+		return -ENOMEM;
+
+	src[0].num = 2;
+	src[0].type = SDW_DPN_SIMPLE;
+	src[0].simple_ch_prep_sm = true;
+	src[0].ch_prep_timeout = 10;
+	src[0].max_ch = 1;
+	src[0].min_ch = 1;
+
+	src[1].num = 5;
+	src[1].type = SDW_DPN_SIMPLE;
+	src[1].simple_ch_prep_sm = true;
+	src[1].ch_prep_timeout = 10;
+	src[1].max_ch = 1;
+	src[1].min_ch = 1;
+
+	prop->src_dpn_prop = src;
+
+	sink = devm_kcalloc(dev, 3, sizeof(*sink), GFP_KERNEL);
+	if (!sink)
+		return -ENOMEM;
+
+	sink[0].num = 6;
+	sink[0].type = SDW_DPN_SIMPLE;
+	sink[0].simple_ch_prep_sm = true;
+	sink[0].ch_prep_timeout = 10;
+	sink[0].max_ch = 2;
+	sink[0].min_ch = 1;
+
+	sink[1].num = 7;
+	sink[1].type = SDW_DPN_FULL;
+	sink[1].simple_ch_prep_sm = true;
+	sink[1].ch_prep_timeout = 10;
+	sink[1].max_ch = 1;
+	sink[1].min_ch = 1;
+
+	sink[2].num = 8;
+	sink[2].type = SDW_DPN_REDUCED;
+	sink[2].simple_ch_prep_sm = true;
+	sink[2].ch_prep_timeout = 10;
+	sink[2].max_ch = 2;
+	sink[2].min_ch = 1;
+
+	prop->sink_dpn_prop = sink;
+
+	ret = device_property_read_u32_array(dev, "qcom,port-mapping",
+					     &slave->m_port_map[1],
+					     WCD9378_SDCA_MAX_PORTS);
+	if (ret)
+		return dev_err_probe(dev, ret, "qcom,port-mapping missing\n");
+
+	return 0;
+}
+
+static int wcd9378_sdca_populate_function(struct device *dev,
+					  struct sdca_function_data *function)
+{
+	/* @function->desc is already set by the framework; fill payload only. */
+	if (function->desc->type != wcd9378_sdca_desc.type)
+		return -EINVAL;
+
+	function->num_entities    = wcd9378_sdca_data.num_entities;
+	function->entities        = wcd9378_sdca_data.entities;
+	function->num_clusters    = wcd9378_sdca_data.num_clusters;
+	function->clusters        = wcd9378_sdca_data.clusters;
+	function->num_init_table  = wcd9378_sdca_data.num_init_table;
+	function->init_table      = wcd9378_sdca_data.init_table;
+	function->reset_max_delay = wcd9378_sdca_data.reset_max_delay;
+
+	return 0;
+}
+
+static const struct sdca_class_ops wcd9378_sdca_class_ops = {
+	.populate_function = wcd9378_sdca_populate_function,
+};
+
+int wcd9378_sdca_probe(struct sdw_slave *slave,
+		       const struct sdw_device_id *id)
+{
+	struct device *dev = &slave->dev;
+	struct sdca_device_data *data = &slave->sdca_data;
+	struct wcd9378_priv *priv;
+	struct gpio_desc *reset;
+	int ret;
+
+	/*
+	 * 0x0217:0x0110 covers both mobile (non-SDCA) and SDCA-compliant
+	 * fuse variants. Only SDCA-compliant nodes bind to this driver;
+	 * mobile-mode nodes are handled by a different driver.
+	 */
+	if (!device_property_read_bool(dev, "qcom,sdca-compliant"))
+		return -ENODEV;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	dev_set_drvdata(dev, priv);
+
+	/* No SPMI parent: supplies and reset live on the SoundWire DT node. */
+	ret = devm_regulator_bulk_get_enable(dev,
+					     ARRAY_SIZE(wcd9378_sdca_supplies),
+					     wcd9378_sdca_supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable supplies\n");
+
+	reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(reset))
+		return dev_err_probe(dev, PTR_ERR(reset),
+				     "failed to get reset GPIO\n");
+
+	if (reset) {
+		gpiod_set_value(reset, 1);
+		usleep_range(20, 30);
+		gpiod_set_value(reset, 0);
+		usleep_range(20, 30);
+	}
+
+	/* SCP writes below need the slave attached. */
+	ret = sdw_slave_wait_for_init(slave, 5000);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "slave attach timeout: %d\n", ret);
+
+	/*
+	 * TX PDM clock: bank-1 shadow + SCP_COMMIT.  SCP survives PDE
+	 * cycles; one-shot before any port is enabled.
+	 */
+	ret = sdw_write_no_pm(slave, WCD9378_SCP_HOST_CLK_DIV2_CTL_B1, 0x01);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "HOST_CLK_DIV2_CTL_B1: %d\n", ret);
+
+	ret = sdw_write_no_pm(slave, SDW_SCP_COMMIT, 0x02);
+	if (ret)
+		return dev_err_probe(dev, ret, "SCP_COMMIT: %d\n", ret);
+
+	/* DT has no DisCo enumeration; seed the descriptor here. */
+	if (!data->num_functions) {
+		data->function[0].type = wcd9378_sdca_desc.type;
+		data->function[0].adr  = wcd9378_sdca_desc.adr;
+		data->function[0].name = wcd9378_sdca_desc.name;
+		data->num_functions    = 1;
+	}
+
+	return sdca_class_probe(slave, &priv->class, &wcd9378_sdca_class_ops);
+}
+
+void wcd9378_sdca_remove(struct sdw_slave *slave)
+{
+	struct wcd9378_priv *priv = dev_get_drvdata(&slave->dev);
+
+	sdca_class_remove(&priv->class);
+}
+
+int wcd9378_sdca_runtime_suspend(struct device *dev)
+{
+	struct wcd9378_priv *priv = dev_get_drvdata(dev);
+
+	return sdca_class_runtime_suspend(&priv->class);
+}
+
+int wcd9378_sdca_runtime_resume(struct device *dev)
+{
+	struct wcd9378_priv *priv = dev_get_drvdata(dev);
+
+	return sdca_class_runtime_resume(&priv->class);
+}
+
+int wcd9378_sdca_system_suspend(struct device *dev)
+{
+	struct wcd9378_priv *priv = dev_get_drvdata(dev);
+
+	return sdca_class_system_suspend(&priv->class);
+}
+
+int wcd9378_sdca_system_resume(struct device *dev)
+{
+	struct wcd9378_priv *priv = dev_get_drvdata(dev);
+
+	return sdca_class_system_resume(&priv->class);
+}
+
+/* SoundWire slave-driver plumbing lives in wcd9378-sdw.c. */
diff --git a/sound/soc/codecs/wcd9378-sdca.h b/sound/soc/codecs/wcd9378-sdca.h
new file mode 100644
index 000000000000..6ddae9da395b
--- /dev/null
+++ b/sound/soc/codecs/wcd9378-sdca.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright (c) 2025 Qualcomm Technologies, Inc. */
+
+#ifndef _WCD9378_SDCA_H
+#define _WCD9378_SDCA_H
+
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+
+int  wcd9378_sdca_probe(struct sdw_slave *slave,
+			const struct sdw_device_id *id);
+void wcd9378_sdca_remove(struct sdw_slave *slave);
+int  wcd9378_sdca_read_prop(struct sdw_slave *slave);
+
+int wcd9378_sdca_runtime_suspend(struct device *dev);
+int wcd9378_sdca_runtime_resume(struct device *dev);
+int wcd9378_sdca_system_suspend(struct device *dev);
+int wcd9378_sdca_system_resume(struct device *dev);
+
+#endif /* _WCD9378_SDCA_H */
diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c
new file mode 100644
index 000000000000..52295e7646cf
--- /dev/null
+++ b/sound/soc/codecs/wcd9378-sdw.c
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright (c) 2025 Qualcomm Technologies, Inc.
+
+/*
+ * WCD9378 (Tambora) SoundWire driver glue for the SDCA compute-mode
+ * codec.  The SDCA topology and hardware-specific probe logic live in
+ * wcd9378-sdca.c; this file carries the SoundWire slave driver plumbing
+ * and the module boilerplate.
+ */
+
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include "wcd9378-sdca.h"
+
+static const struct dev_pm_ops wcd9378_sdw_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(wcd9378_sdca_system_suspend,
+			    wcd9378_sdca_system_resume)
+	RUNTIME_PM_OPS(wcd9378_sdca_runtime_suspend,
+		       wcd9378_sdca_runtime_resume, NULL)
+};
+
+static const struct sdw_slave_ops wcd9378_sdw_ops = {
+	.read_prop = wcd9378_sdca_read_prop,
+};
+
+static const struct sdw_device_id wcd9378_sdw_id[] = {
+	SDW_SLAVE_ENTRY(0x0217, 0x0110, 0),
+	{ }
+};
+MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id);
+
+static struct sdw_driver wcd9378_sdw_driver = {
+	.driver = {
+		.name	= "wcd9378",
+		.pm	= pm_ptr(&wcd9378_sdw_pm_ops),
+	},
+	.probe		= wcd9378_sdca_probe,
+	.remove		= wcd9378_sdca_remove,
+	.id_table	= wcd9378_sdw_id,
+	.ops		= &wcd9378_sdw_ops,
+};
+module_sdw_driver(wcd9378_sdw_driver);
+
+MODULE_DESCRIPTION("Qualcomm WCD9378 (Tambora) SoundWire codec");
+MODULE_AUTHOR("Qualcomm Technologies, Inc.");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
+MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS");
-- 
2.53.0


  parent reply	other threads:[~2026-09-25 16:11 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-25 16:10 [PATCH v5 0/7] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
2026-09-25 16:10 ` [PATCH v5 1/7] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
2026-09-28  9:32   ` Charles Keepax
2026-09-25 16:10 ` [PATCH v5 2/7] ASoC: SDCA: export PM helpers keyed on sdca_class_drv Srinivas Kandagatla
2026-09-28  9:32   ` Charles Keepax
2026-09-25 16:10 ` [PATCH v5 3/7] ASoC: SDCA: expose class SoundWire probe/remove as library Srinivas Kandagatla
2026-09-25 16:21   ` sashiko-bot
2026-09-28  9:34   ` Charles Keepax
2026-09-25 16:10 ` [PATCH v5 4/7] ASoC: SDCA: add class_ops with populate_function Srinivas Kandagatla
2026-09-28  9:34   ` Charles Keepax
2026-09-25 16:10 ` [PATCH v5 5/7] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
2026-09-28  9:35   ` Charles Keepax
2026-09-25 16:10 ` [PATCH v5 6/7] ASoC: dt-bindings: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
2026-09-29  8:22   ` Krzysztof Kozlowski
2026-09-25 16:10 ` Srinivas Kandagatla [this message]
2026-09-25 17:31   ` [PATCH v5 7/7] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Mark Brown
2026-09-25 20:28     ` Srinivas Kandagatla
2026-09-25 21:52       ` Mark Brown
2026-09-29 11:51 ` [PATCH v5 0/7] ASoC: " 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=20260925161049.758913-8-srinivas.kandagatla@oss.qualcomm.com \
    --to=srinivas.kandagatla@oss.qualcomm.com \
    --cc=broonie@kernel.org \
    --cc=ckeepax@opensource.cirrus.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lgirdwood@gmail.com \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-sound@vger.kernel.org \
    --cc=mstrozek@opensource.cirrus.com \
    --cc=patches@opensource.cirrus.com \
    --cc=perex@perex.cz \
    --cc=pierre-louis.bossart@linux.dev \
    --cc=robh@kernel.org \
    --cc=tiwai@suse.com \
    --cc=yung-chuan.liao@linux.intel.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox