* [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec
@ 2026-09-18 13:19 Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 1/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
` (7 more replies)
0 siblings, 8 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
Add support for the Qualcomm Tambora (WCD9378) headset codec in
SDCA-compliant 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.
Patches 1-6 extend the ASoC SDCA class framework so that a
class-based codec driver can be built on ARM/DT: allow builds
without ACPI, expose the SoundWire probe/remove library entry
points, add class_ops with a populate_function hook, translate
sound-dai cells by entity index, and add a
sdca_apply_default_control_classifiers() helper.
Patches 7-8 add the WCD9378 dt-binding and driver. The same
silicon can be fused for a mobile (non-SDCA) or an SDCA-compliant
mode; the two share the SoundWire class-ID compatible
(sdw20217011000) and are selected apart by the new
qcom,sdca-compliant boolean property.
Testing: HPH playback and headset-mic capture tested on the
Snapdragon X2 Elite Glymur CRD and Lenovo T14s Gen7.
Jack detection is not wired up in this series.
Changes since v3 [1]:
- dt-bindings: sound: qcom,wcd9378-sdw.yaml -- drop the
qcom,wcd9378c front compatible so the schema plays nicely with
the SoundWire class-ID compatible; introduce a
qcom,sdca-compliant boolean flag to select the SDCA fuse; drop
the fake top-level wrapper node so the slave sits directly
under the SoundWire controller; drop '|' from qcom,port-mapping
description (Krzysztof).
- ASoC: codecs: wcd9378-sdca: gate probe on qcom,sdca-compliant.
- set SDCA_NO_INTERRUPT correctly on controls, (Mark)
[1] https://lore.kernel.org/all/20260915165652.914893-1-srinivas.kandagatla@oss.qualcomm.com/
Srinivas Kandagatla (8):
ASoC: SDCA: allow building without ACPI
ASoC: SDCA: export PM helpers keyed on sdca_class_drv
ASoC: SDCA: expose class SoundWire probe/remove as library
ASoC: SDCA: add class_ops with populate_function
ASoC: SDCA: class_function: xlate sound-dai cell by entity index
ASoC: SDCA: add sdca_apply_default_control_classifiers() helper
dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
.../bindings/sound/qcom,wcd9378-sdw.yaml | 102 ++
include/sound/sdca.h | 23 +-
include/sound/sdca_class.h | 70 ++
include/sound/sdca_function.h | 2 +
sound/soc/codecs/Kconfig | 11 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wcd9378-sdca.c | 1058 +++++++++++++++++
sound/soc/codecs/wcd9378-sdca.h | 20 +
sound/soc/codecs/wcd9378-sdw.c | 51 +
sound/soc/sdca/Kconfig | 6 +-
sound/soc/sdca/sdca_class.c | 148 ++-
sound/soc/sdca/sdca_class.h | 35 -
sound/soc/sdca/sdca_class_function.c | 45 +-
sound/soc/sdca/sdca_device.c | 4 +
sound/soc/sdca/sdca_functions.c | 31 +
15 files changed, 1537 insertions(+), 71 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
create mode 100644 include/sound/sdca_class.h
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
delete mode 100644 sound/soc/sdca/sdca_class.h
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 1/8] ASoC: SDCA: allow building without ACPI
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 2/8] ASoC: SDCA: export PM helpers keyed on sdca_class_drv Srinivas Kandagatla
` (6 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
The SDCA class driver is useful on non-ACPI platforms where the
topology is supplied statically via sdca_class_hw_ops.get_function_data.
Drop 'depends on ACPI' from SND_SOC_SDCA and guard the
ACPI-parsing implementations in sdca_device.c and sdca_functions.c
with IS_ENABLED(CONFIG_ACPI), providing empty stubs when ACPI is off.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
include/sound/sdca.h | 23 ++++++++++++++++++-----
sound/soc/sdca/Kconfig | 6 ++++--
sound/soc/sdca/sdca_device.c | 4 ++++
sound/soc/sdca/sdca_functions.c | 2 ++
4 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/include/sound/sdca.h b/include/sound/sdca.h
index 2bdf4e333e04..db2672b3aae2 100644
--- a/include/sound/sdca.h
+++ b/include/sound/sdca.h
@@ -60,20 +60,16 @@ enum sdca_quirk {
SDCA_QUIRKS_SKIP_FUNC_TYPE_PATCHING,
};
-#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
+#if IS_ENABLED(CONFIG_SND_SOC_SDCA)
-void sdca_lookup_functions(struct sdw_slave *slave);
void sdca_lookup_swft(struct sdw_slave *slave);
-void sdca_lookup_interface_revision(struct sdw_slave *slave);
bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk);
int sdca_dev_register_functions(struct sdw_slave *slave);
void sdca_dev_unregister_functions(struct sdw_slave *slave);
#else
-static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
static inline void sdca_lookup_swft(struct sdw_slave *slave) {}
-static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk)
{
return false;
@@ -88,4 +84,21 @@ static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {}
#endif
+/*
+ * Called from the SoundWire bus during peripheral enumeration; gated on
+ * ACPI to avoid a soundwire_bus <-> snd_soc_sdca module cycle on DT builds
+ * (where the bodies are stubs anyway).
+ */
+#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
+
+void sdca_lookup_functions(struct sdw_slave *slave);
+void sdca_lookup_interface_revision(struct sdw_slave *slave);
+
+#else
+
+static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
+static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
+
+#endif
+
#endif
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index 4c0dcb9ff3b9..72c02943c49e 100644
--- a/sound/soc/sdca/Kconfig
+++ b/sound/soc/sdca/Kconfig
@@ -3,11 +3,13 @@ menu "SoundWire (SDCA)"
config SND_SOC_SDCA
tristate "SDCA core support"
- depends on ACPI
select AUXILIARY_BUS
help
This option enables support for the MIPI SoundWire Device
- Class for Audio (SDCA).
+ Class for Audio (SDCA). The ACPI/DisCo topology parser is
+ only built when CONFIG_ACPI is enabled; on non-ACPI platforms
+ (e.g. ARM/DT) codec drivers supply the SDCA function data
+ via sdca_class_hw_ops.populate_function.
config SND_SOC_SDCA_HID
bool "SDCA HID support"
diff --git a/sound/soc/sdca/sdca_device.c b/sound/soc/sdca/sdca_device.c
index 4bcd8d1fdff8..3f302bfa5457 100644
--- a/sound/soc/sdca/sdca_device.c
+++ b/sound/soc/sdca/sdca_device.c
@@ -15,6 +15,7 @@
#include <sound/sdca.h>
#include <sound/sdca_function.h>
+#if IS_ENABLED(CONFIG_ACPI)
void sdca_lookup_interface_revision(struct sdw_slave *slave)
{
struct fwnode_handle *fwnode = slave->dev.fwnode;
@@ -45,6 +46,9 @@ void sdca_lookup_swft(struct sdw_slave *slave)
devm_add_action_or_reset(&slave->dev, devm_acpi_table_put,
slave->sdca_data.swft);
}
+#else
+void sdca_lookup_swft(struct sdw_slave *slave) { }
+#endif
EXPORT_SYMBOL_NS(sdca_lookup_swft, "SND_SOC_SDCA");
static bool sdca_device_quirk_rt712_vb(struct sdw_slave *slave)
diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
index f39008ac0e7d..1eb6418e132d 100644
--- a/sound/soc/sdca/sdca_functions.c
+++ b/sound/soc/sdca/sdca_functions.c
@@ -25,6 +25,7 @@
*/
#define SDCA_PROPERTY_LENGTH 64
+#if IS_ENABLED(CONFIG_ACPI)
static int patch_sdca_function_type(u32 interface_revision, u32 *function_type)
{
/*
@@ -201,6 +202,7 @@ void sdca_lookup_functions(struct sdw_slave *slave)
acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data);
}
EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA");
+#endif
struct raw_init_write {
__le32 addr;
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 2/8] ASoC: SDCA: export PM helpers keyed on sdca_class_drv
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 1/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 3/8] ASoC: SDCA: expose class SoundWire probe/remove as library Srinivas Kandagatla
` (5 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
The class PM callbacks pull sdca_class_drv out of drvdata, so the
built-in class_sdw_driver owns the drvdata slot. That works for the
generic case but blocks codec drivers that want to embed
sdca_class_drv in their own private struct -- they need drvdata for
their codec priv.
Split the four callbacks into exported helpers that take a
struct sdca_class_drv * directly:
sdca_class_system_suspend()
sdca_class_system_resume()
sdca_class_runtime_suspend()
sdca_class_runtime_resume()
Codec drivers can now compose these into their own dev_pm_ops without
going through drvdata.
For the built-in class_sdw_driver, add small dev_pm_ops wrappers that
fetch drv from drvdata, wire them into sdca_class_pm_ops, and export
the ops so any generic SDCA slave can pick them up as-is.
No functional change: the built-in class_sdw_driver keeps the same PM
semantics; only the internal plumbing shifts to operate on
sdca_class_drv instead of struct device *dev.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
.../soc/sdca => include/sound}/sdca_class.h | 15 ++++
sound/soc/sdca/sdca_class.c | 81 ++++++++++++++-----
sound/soc/sdca/sdca_class_function.c | 2 +-
3 files changed, 79 insertions(+), 19 deletions(-)
rename {sound/soc/sdca => include/sound}/sdca_class.h (57%)
diff --git a/sound/soc/sdca/sdca_class.h b/include/sound/sdca_class.h
similarity index 57%
rename from sound/soc/sdca/sdca_class.h
rename to include/sound/sdca_class.h
index 57f7f8d08f49..ce2ed5f33e7a 100644
--- a/sound/soc/sdca/sdca_class.h
+++ b/include/sound/sdca_class.h
@@ -15,6 +15,7 @@
#include <linux/workqueue.h>
struct device;
+struct dev_pm_ops;
struct regmap;
struct sdw_slave;
struct sdca_function_data;
@@ -32,4 +33,18 @@ struct sdca_class_drv {
struct work_struct boot_work;
};
+/*
+ * PM helpers. Codec drivers embed sdca_class_drv in their own priv,
+ * own dev_set_drvdata(), and compose these into their own dev_pm_ops:
+ *
+ * static int wcd_runtime_suspend(struct device *dev) {
+ * struct wcd_priv *priv = dev_get_drvdata(dev);
+ * return sdca_class_runtime_suspend(&priv->class);
+ * }
+ */
+int sdca_class_runtime_suspend(struct sdca_class_drv *drv);
+int sdca_class_runtime_resume(struct sdca_class_drv *drv);
+int sdca_class_system_suspend(struct sdca_class_drv *drv);
+int sdca_class_system_resume(struct sdca_class_drv *drv);
+
#endif /* __SDCA_CLASS_H__ */
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
index d7444f442c71..a092ebaa620a 100644
--- a/sound/soc/sdca/sdca_class.c
+++ b/sound/soc/sdca/sdca_class.c
@@ -20,7 +20,7 @@
#include <sound/sdca_function.h>
#include <sound/sdca_interrupts.h>
#include <sound/sdca_regmap.h>
-#include "sdca_class.h"
+#include <sound/sdca_class.h>
#define CLASS_SDW_ATTACH_TIMEOUT_MS 5000
@@ -194,30 +194,41 @@ static void class_sdw_remove(struct sdw_slave *sdw)
cancel_work_sync(&drv->boot_work);
}
-static int class_suspend(struct device *dev)
+/**
+ * sdca_class_system_suspend - SDCA class system suspend helper
+ * @drv: caller-owned sdca_class_drv.
+ *
+ * Codec drivers compose this into their own dev_pm_ops. Disables the
+ * SoundWire interrupt and forces runtime suspend of the underlying
+ * class regmap.
+ */
+int sdca_class_system_suspend(struct sdca_class_drv *drv)
{
- struct sdca_class_drv *drv = dev_get_drvdata(dev);
int ret;
disable_irq(drv->sdw->irq);
- ret = pm_runtime_force_suspend(dev);
+ ret = pm_runtime_force_suspend(drv->dev);
if (ret) {
- dev_err(dev, "failed to force suspend: %d\n", ret);
+ dev_err(drv->dev, "failed to force suspend: %d\n", ret);
return ret;
}
return 0;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_system_suspend, "SND_SOC_SDCA_CLASS");
-static int class_resume(struct device *dev)
+/**
+ * sdca_class_system_resume - SDCA class system resume helper
+ * @drv: caller-owned sdca_class_drv.
+ */
+int sdca_class_system_resume(struct sdca_class_drv *drv)
{
- struct sdca_class_drv *drv = dev_get_drvdata(dev);
int ret;
- ret = pm_runtime_force_resume(dev);
+ ret = pm_runtime_force_resume(drv->dev);
if (ret) {
- dev_err(dev, "failed to force resume: %d\n", ret);
+ dev_err(drv->dev, "failed to force resume: %d\n", ret);
return ret;
}
@@ -225,11 +236,14 @@ static int class_resume(struct device *dev)
return 0;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_system_resume, "SND_SOC_SDCA_CLASS");
-static int class_runtime_suspend(struct device *dev)
+/**
+ * sdca_class_runtime_suspend - SDCA class runtime suspend helper
+ * @drv: caller-owned sdca_class_drv.
+ */
+int sdca_class_runtime_suspend(struct sdca_class_drv *drv)
{
- struct sdca_class_drv *drv = dev_get_drvdata(dev);
-
/*
* Whilst the driver doesn't power the chip down here, going into runtime
* suspend lets the SoundWire bus power down, which means the driver
@@ -239,10 +253,14 @@ static int class_runtime_suspend(struct device *dev)
return 0;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_suspend, "SND_SOC_SDCA_CLASS");
-static int class_runtime_resume(struct device *dev)
+/**
+ * sdca_class_runtime_resume - SDCA class runtime resume helper
+ * @drv: caller-owned sdca_class_drv.
+ */
+int sdca_class_runtime_resume(struct sdca_class_drv *drv)
{
- struct sdca_class_drv *drv = dev_get_drvdata(dev);
int ret;
ret = sdw_slave_wait_for_init(drv->sdw, CLASS_SDW_ATTACH_TIMEOUT_MS);
@@ -265,10 +283,37 @@ static int class_runtime_resume(struct device *dev)
return ret;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_runtime_resume, "SND_SOC_SDCA_CLASS");
+
+/*
+ * Convenience dev_pm_ops used by the built-in class_sdw_driver, which
+ * stashes its sdca_class_drv in drvdata directly. Codec drivers that
+ * embed sdca_class_drv in their own priv compose their own dev_pm_ops
+ * using the sdca_class_*_suspend/resume helpers above.
+ */
+static int class_pm_system_suspend(struct device *dev)
+{
+ return sdca_class_system_suspend(dev_get_drvdata(dev));
+}
+
+static int class_pm_system_resume(struct device *dev)
+{
+ return sdca_class_system_resume(dev_get_drvdata(dev));
+}
+
+static int class_pm_runtime_suspend(struct device *dev)
+{
+ return sdca_class_runtime_suspend(dev_get_drvdata(dev));
+}
+
+static int class_pm_runtime_resume(struct device *dev)
+{
+ return sdca_class_runtime_resume(dev_get_drvdata(dev));
+}
-static const struct dev_pm_ops class_pm_ops = {
- SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume)
- RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL)
+static const struct dev_pm_ops sdca_class_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(class_pm_system_suspend, class_pm_system_resume)
+ RUNTIME_PM_OPS(class_pm_runtime_suspend, class_pm_runtime_resume, NULL)
};
static const struct sdw_device_id class_sdw_id[] = {
@@ -282,7 +327,7 @@ MODULE_DEVICE_TABLE(sdw, class_sdw_id);
static struct sdw_driver class_sdw_driver = {
.driver = {
.name = "sdca_class",
- .pm = pm_ptr(&class_pm_ops),
+ .pm = pm_ptr(&sdca_class_pm_ops),
},
.probe = class_sdw_probe,
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index 1d7fd6603882..411e3d717bb6 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -26,7 +26,7 @@
#include <sound/soc-component.h>
#include <sound/soc-dai.h>
#include <sound/soc.h>
-#include "sdca_class.h"
+#include <sound/sdca_class.h>
#include "sdca_function_device.h"
struct class_function_drv {
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 3/8] ASoC: SDCA: expose class SoundWire probe/remove as library
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 1/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 2/8] ASoC: SDCA: export PM helpers keyed on sdca_class_drv Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function Srinivas Kandagatla
` (4 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
Split the internal class_sdw_probe/class_sdw_remove functions into
caller-friendly library helpers:
sdca_class_probe(sdw, drv)
sdca_class_remove(drv)
The class_sdw_probe/class_sdw_remove callbacks of the built-in
class_sdw_driver are now thin wrappers that allocate a bare
sdca_class_drv, stash it in drvdata, and defer to the exported
helpers.
The exported sdca_class_probe() takes a caller-owned struct
sdca_class_drv * so codec-specific SoundWire drivers can embed the
class state in their own priv struct, own dev_set_drvdata() themselves,
and avoid a second allocation. No functional change for the built-in
driver.
This lays the groundwork for codec-specific SDCA SoundWire drivers
that want to compose the class-side probe with their own quirks; the
next patches add the class_ops mechanism on top.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
include/sound/sdca_class.h | 4 +++
sound/soc/sdca/sdca_class.c | 62 +++++++++++++++++++++++++++++++------
2 files changed, 57 insertions(+), 9 deletions(-)
diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h
index ce2ed5f33e7a..9e6b0eab8a0e 100644
--- a/include/sound/sdca_class.h
+++ b/include/sound/sdca_class.h
@@ -33,6 +33,10 @@ struct sdca_class_drv {
struct work_struct boot_work;
};
+/* Library helpers used by codec-specific SDCA SoundWire drivers. */
+int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv);
+void sdca_class_remove(struct sdca_class_drv *drv);
+
/*
* PM helpers. Codec drivers embed sdca_class_drv in their own priv,
* own dev_set_drvdata(), and compose these into their own dev_pm_ops:
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
index a092ebaa620a..0e6dead41495 100644
--- a/sound/soc/sdca/sdca_class.c
+++ b/sound/soc/sdca/sdca_class.c
@@ -136,18 +136,31 @@ static void class_boot_work(struct work_struct *work)
pm_runtime_put_sync(drv->dev);
}
-static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
+/**
+ * sdca_class_probe - SDCA class SoundWire slave probe helper
+ * @sdw: SoundWire slave
+ * @drv: caller-allocated sdca_class_drv storage. The caller (a codec
+ * driver, or the built-in class_sdw_driver in this file) owns the
+ * allocation and sets its own dev_set_drvdata() -- the framework
+ * does not touch drvdata. Typically embedded in the codec's own
+ * priv struct so codec drivers can keep per-slave state.
+ *
+ * Codec-specific SoundWire drivers call this from their .probe after
+ * allocating a struct sdca_class_drv (usually embedded in their own
+ * priv) and setting drvdata to their priv. The framework fills in the
+ * sdca_class_drv fields, sets up the class regmap, and queues the
+ * deferred boot work.
+ */
+int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv)
{
struct device *dev = &sdw->dev;
struct regmap_config *dev_config;
- struct sdca_class_drv *drv;
int ret;
sdca_lookup_swft(sdw);
- drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
if (!drv)
- return -ENOMEM;
+ return -EINVAL;
dev_config = devm_kmemdup(dev, &class_dev_regmap_config,
sizeof(*dev_config), GFP_KERNEL);
@@ -159,8 +172,6 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
mutex_init(&drv->regmap_lock);
mutex_init(&drv->init_lock);
- dev_set_drvdata(drv->dev, drv);
-
INIT_WORK(&drv->boot_work, class_boot_work);
dev_config->lock_arg = &drv->regmap_lock;
@@ -185,14 +196,47 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
return 0;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_probe, "SND_SOC_SDCA_CLASS");
-static void class_sdw_remove(struct sdw_slave *sdw)
+static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
{
- struct device *dev = &sdw->dev;
- struct sdca_class_drv *drv = dev_get_drvdata(dev);
+ struct sdca_class_drv *drv;
+
+ /*
+ * Pure-generic SDCA parts: no codec priv to embed, so allocate a
+ * bare sdca_class_drv here and stash it in drvdata for the
+ * built-in PM ops to fetch.
+ */
+ drv = devm_kzalloc(&sdw->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+ dev_set_drvdata(&sdw->dev, drv);
+
+ return sdca_class_probe(sdw, drv);
+}
+
+/**
+ * sdca_class_remove - SDCA class SoundWire slave remove helper
+ * @drv: caller-owned sdca_class_drv (the one handed to sdca_class_probe()).
+ *
+ * Cancels the deferred boot work so devres can safely free @drv and the
+ * embedding codec priv without racing class_boot_work. Codec-specific
+ * SoundWire drivers that call sdca_class_probe() must call this from
+ * their .remove with the same drv pointer they passed to probe.
+ */
+void sdca_class_remove(struct sdca_class_drv *drv)
+{
cancel_work_sync(&drv->boot_work);
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_remove, "SND_SOC_SDCA_CLASS");
+
+static void class_sdw_remove(struct sdw_slave *sdw)
+{
+ struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev);
+
+ sdca_class_remove(drv);
+}
/**
* sdca_class_system_suspend - SDCA class system suspend helper
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
` (2 preceding siblings ...)
2026-09-18 13:19 ` [PATCH v4 3/8] ASoC: SDCA: expose class SoundWire probe/remove as library Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 14:15 ` Charles Keepax
2026-09-18 13:19 ` [PATCH v4 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
` (3 subsequent siblings)
7 siblings, 1 reply; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
Add struct sdca_class_ops with a populate_function callback that lets
codec drivers supply the SDCA topology (entities, clusters,
init_table, ...) from static tables in place of sdca_parse_function()
on DT/non-DisCo platforms. The callback is a pure data source and
performs no bus I/O.
Codec drivers embed sdca_class_drv in their own priv and register
their populate_function through class_ops passed to
sdca_class_probe().
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
include/sound/sdca_class.h | 18 +++++++++++++++++-
sound/soc/sdca/sdca_class.c | 9 +++++++--
sound/soc/sdca/sdca_class_function.c | 9 ++++++++-
3 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/include/sound/sdca_class.h b/include/sound/sdca_class.h
index 9e6b0eab8a0e..90af5b914ba3 100644
--- a/include/sound/sdca_class.h
+++ b/include/sound/sdca_class.h
@@ -20,6 +20,18 @@ struct regmap;
struct sdw_slave;
struct sdca_function_data;
+/**
+ * struct sdca_class_ops - optional codec-provided class callbacks
+ * @populate_function: fill @function (entities, clusters, init_table, ...)
+ * from static tables in place of sdca_parse_function() on
+ * DT/non-DisCo platforms. Pure data source; performs no
+ * bus I/O. Return 0 on success or a negative errno.
+ */
+struct sdca_class_ops {
+ int (*populate_function)(struct sdw_slave *slave,
+ struct sdca_function_data *function);
+};
+
struct sdca_class_drv {
struct device *dev;
struct regmap *dev_regmap;
@@ -27,6 +39,8 @@ struct sdca_class_drv {
struct sdca_interrupt_info *irq_info;
+ const struct sdca_class_ops *ops;
+
struct mutex regmap_lock;
/* Serialise function initialisations */
struct mutex init_lock;
@@ -34,7 +48,9 @@ struct sdca_class_drv {
};
/* Library helpers used by codec-specific SDCA SoundWire drivers. */
-int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv);
+int sdca_class_probe(struct sdw_slave *sdw,
+ struct sdca_class_drv *drv,
+ const struct sdca_class_ops *ops);
void sdca_class_remove(struct sdca_class_drv *drv);
/*
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
index 0e6dead41495..79fdb672be1d 100644
--- a/sound/soc/sdca/sdca_class.c
+++ b/sound/soc/sdca/sdca_class.c
@@ -144,6 +144,8 @@ static void class_boot_work(struct work_struct *work)
* allocation and sets its own dev_set_drvdata() -- the framework
* does not touch drvdata. Typically embedded in the codec's own
* priv struct so codec drivers can keep per-slave state.
+ * @ops: optional codec-provided class callbacks (may be NULL for
+ * pure-generic SDCA parts that need no quirks)
*
* Codec-specific SoundWire drivers call this from their .probe after
* allocating a struct sdca_class_drv (usually embedded in their own
@@ -151,7 +153,9 @@ static void class_boot_work(struct work_struct *work)
* sdca_class_drv fields, sets up the class regmap, and queues the
* deferred boot work.
*/
-int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv)
+int sdca_class_probe(struct sdw_slave *sdw,
+ struct sdca_class_drv *drv,
+ const struct sdca_class_ops *ops)
{
struct device *dev = &sdw->dev;
struct regmap_config *dev_config;
@@ -169,6 +173,7 @@ int sdca_class_probe(struct sdw_slave *sdw, struct sdca_class_drv *drv)
drv->dev = dev;
drv->sdw = sdw;
+ drv->ops = ops;
mutex_init(&drv->regmap_lock);
mutex_init(&drv->init_lock);
@@ -213,7 +218,7 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
dev_set_drvdata(&sdw->dev, drv);
- return sdca_class_probe(sdw, drv);
+ return sdca_class_probe(sdw, drv, NULL);
}
/**
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index 411e3d717bb6..d83be8474e7b 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -329,7 +329,14 @@ static int class_function_probe(struct auxiliary_device *auxdev,
drv->core = core;
drv->function = &sdev->function;
- ret = sdca_parse_function(dev, drv->function);
+ if (core->ops && core->ops->populate_function) {
+ ret = core->ops->populate_function(core->sdw, drv->function);
+ } else if (drv->function->desc->node) {
+ ret = sdca_parse_function(dev, drv->function);
+ } else {
+ dev_err(dev, "no firmware node and no populate_function hook\n");
+ return -ENOENT;
+ }
if (ret)
return ret;
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
` (3 preceding siblings ...)
2026-09-18 13:19 ` [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper Srinivas Kandagatla
` (2 subsequent siblings)
7 siblings, 0 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
sdca_asoc_populate_dais() sets each DAI id from the SDCA Function's
entity array index. ASoC's default of_xlate_dai_name treats the
phandle cell as a positional index into the DAI list, so on a Function
with non-DAI entries between dataport entities a sound-dai phandle
resolves to the wrong DAI.
Register a custom of_xlate_dai_name that walks the entity array and
returns the dataport entity whose array index matches the cell value.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_class_function.c | 34 ++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index d83be8474e7b..328c667edfd9 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -216,9 +216,43 @@ static int class_function_set_jack(struct snd_soc_component *component,
return sdca_jack_set_jack(core->irq_info, jack);
}
+/*
+ * DT phandle cell is the SDCA entity index (matches dais[].id), not the
+ * positional DAI index the default xlate assumes.
+ */
+static int class_function_of_xlate_dai_name(struct snd_soc_component *component,
+ const struct of_phandle_args *args,
+ const char **dai_name)
+{
+ struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
+ struct sdca_function_data *function = drv->function;
+ struct sdca_entity *entity;
+ u32 target;
+
+ if (args->args_count != 1)
+ return -EINVAL;
+
+ target = args->args[0];
+ if (target >= function->num_entities)
+ goto err;
+
+ entity = &function->entities[target];
+ if ((entity->type != SDCA_ENTITY_TYPE_IT &&
+ entity->type != SDCA_ENTITY_TYPE_OT) || !entity->iot.is_dataport)
+ goto err;
+
+ *dai_name = entity->label;
+ return 0;
+err:
+ dev_err(component->dev, "xlate: no dataport entity at index %u (num_entities=%d)\n",
+ target, function->num_entities);
+ return -EINVAL;
+}
+
static const struct snd_soc_component_driver class_function_component_drv = {
.fixup_controls = class_function_component_fixup_controls,
.remove = class_function_component_remove,
+ .of_xlate_dai_name = class_function_of_xlate_dai_name,
.endianness = 1,
};
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
` (4 preceding siblings ...)
2026-09-18 13:19 ` [PATCH v4 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 14:16 ` Charles Keepax
2026-09-18 13:19 ` [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
7 siblings, 1 reply; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
sdca_parse_function() derives is_volatile (see c7b6c6b60594) and the
spec-defined reset value (see 02d851b46b36) from each Control's
(entity type, selector, access mode). Codecs that ship static SDCA
tables via populate_function skip that path, so RW1S action Controls
end up with is_volatile=0 and get replayed on every regcache_sync.
Add sdca_apply_default_control_classifiers() to re-run those two
classifiers over a Function. Elevate-only, so codec-set flags win.
Call from the tail of populate_function.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
include/sound/sdca_function.h | 2 ++
sound/soc/sdca/sdca_functions.c | 29 +++++++++++++++++++++++++++++
2 files changed, 31 insertions(+)
diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h
index e580201d69d5..d3d77d8bb7cd 100644
--- a/include/sound/sdca_function.h
+++ b/include/sound/sdca_function.h
@@ -1464,6 +1464,8 @@ static inline u32 sdca_range_search(struct sdca_control_range *range,
int sdca_parse_function(struct device *dev, struct sdca_function_data *function);
+void sdca_apply_default_control_classifiers(struct sdca_function_data *function);
+
const char *sdca_find_terminal_name(enum sdca_terminal_type type);
struct sdca_control *sdca_selector_find_control(struct device *dev,
diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
index 1eb6418e132d..52ce9355a22d 100644
--- a/sound/soc/sdca/sdca_functions.c
+++ b/sound/soc/sdca/sdca_functions.c
@@ -2266,6 +2266,35 @@ int sdca_parse_function(struct device *dev, struct sdca_function_data *function)
}
EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA");
+/**
+ * sdca_apply_default_control_classifiers - apply framework-known Control fields
+ * @function: Function populated via static tables (populate_function path).
+ *
+ * Runs the same per-Control classifiers as sdca_parse_function() so both
+ * entry paths land on identical @is_volatile and @has_reset/@reset fields.
+ * Elevate-only: codec-set flags/values are preserved.
+ */
+void sdca_apply_default_control_classifiers(struct sdca_function_data *function)
+{
+ int i, j;
+
+ for (i = 0; i < function->num_entities; i++) {
+ struct sdca_entity *entity = &function->entities[i];
+
+ for (j = 0; j < entity->num_controls; j++) {
+ struct sdca_control *control = &entity->controls[j];
+
+ if (!control->is_volatile)
+ control->is_volatile =
+ find_sdca_control_volatile(entity, control);
+
+ if (!control->has_reset)
+ find_sdca_control_reset(entity, control);
+ }
+ }
+}
+EXPORT_SYMBOL_NS(sdca_apply_default_control_classifiers, "SND_SOC_SDCA");
+
const char *sdca_find_terminal_name(enum sdca_terminal_type type)
{
switch (type) {
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
` (5 preceding siblings ...)
2026-09-18 13:19 ` [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 13:51 ` Mark Brown
2026-09-18 13:19 ` [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
7 siblings, 1 reply; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
Describe the WCD9378 SoundWire peripheral which is SDCA compatible
soundwire device. It provides SDCA SimpleJack for headphone playback
headset mic capture and MBHC jack detection.
The same silicon is integrated in two ways and enumerates with the
same SoundWire class ID:
mobile mode: two slaves (tx, rx) on separate SoundWire masters.
SDCA / compute mode: one aggregated slave on a multi-lane
master.
Note: both of them have different bus toplogies 1 device vs 2 devices.
Currently only compute mode is supported by this bindings, mobile mode
will extend this binding as required.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
.../bindings/sound/qcom,wcd9378-sdw.yaml | 102 ++++++++++++++++++
1 file changed, 102 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
diff --git a/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
new file mode 100644
index 000000000000..33fcd0f191ec
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/qcom,wcd9378-sdw.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm WCD9378 (Tambora) SoundWire codec
+
+maintainers:
+ - Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+
+description:
+ Qualcomm WCD9378 (Tambora) SoundWire codec. The same silicon can be
+ fused for a mobile (non-SDCA) mode or an SDCA-compliant mode; the
+ qcom,sdca-compliant property selects the latter and is required by
+ this driver.
+
+properties:
+ compatible:
+ const: sdw20217011000
+
+ reg:
+ maxItems: 1
+
+ qcom,sdca-compliant:
+ description:
+ Present when the codec is fused as SDCA-compliant. The mobile
+ (non-SDCA) variant carries the same class-ID compatible and is
+ handled by a different driver.
+ type: boolean
+
+ qcom,port-mapping:
+ description:
+ Each entry maps a slave data port to a master data port.
+ Entries are in order starting from slave port 1; slave port 0
+ is reserved and not represented. SDCA-compliant mode uses 8
+ entries covering DP1..DP8.
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 8
+ maxItems: 8
+
+ reset-gpios:
+ description: GPIO used to release the codec from reset.
+ maxItems: 1
+
+ vdd-buck-supply:
+ description: 1.8 V analog buck supply.
+
+ vdd-rxtx-supply:
+ description: 1.8 V RX/TX supply.
+
+ vdd-io-supply:
+ description: Digital I/O supply.
+
+ vdd-mic-bias-supply:
+ description: Mic bias supply.
+
+ '#sound-dai-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - qcom,sdca-compliant
+ - qcom,port-mapping
+ - reset-gpios
+ - vdd-buck-supply
+ - vdd-rxtx-supply
+ - vdd-io-supply
+ - vdd-mic-bias-supply
+ - '#sound-dai-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ soundwire@7630000 {
+ reg = <0x07630000 0x10000>;
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ wcd9378c_sdw: audio-codec@0,3 {
+ compatible = "sdw20217011000";
+ reg = <0 3>;
+
+ qcom,sdca-compliant;
+
+ qcom,port-mapping = <2 2 3 4 5 6 7 8>;
+
+ reset-gpios = <&tlmm 191 GPIO_ACTIVE_LOW>;
+
+ vdd-buck-supply = <&vreg_l15b_1p8>;
+ vdd-rxtx-supply = <&vreg_l15b_1p8>;
+ vdd-io-supply = <&vreg_l18b_1p2>;
+ vdd-mic-bias-supply = <&vreg_bob1>;
+
+ #sound-dai-cells = <1>;
+ };
+ };
+...
--
2.53.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
` (6 preceding siblings ...)
2026-09-18 13:19 ` [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
@ 2026-09-18 13:19 ` Srinivas Kandagatla
2026-09-18 14:28 ` Charles Keepax
7 siblings, 1 reply; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 13:19 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek
Cc: linux-sound, linux-arm-msm, devicetree, linux-kernel, patches,
Srinivas Kandagatla
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 derived from DT
(qcom,micbias2-microvolt).
- MBHC-based headset jack detection.
Implements:
- sdw_slave_ops.read_prop: SoundWire slave properties and dpn caps
for the compute-mode dataports.
- sdca_class_hw_ops.populate_function: fills the SDCA Function data
(entities, clusters, init_table) from static tables and patches
the IT 33 MIC_BIAS default with the DT-derived per-slave value.
Binds SoundWire slave id 0x0217:0x0110 when qcom,compute-mode is set
on the DT node.
Assisted-by: Claude:claude-opus-4-7
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 | 1058 +++++++++++++++++++++++++++++++
sound/soc/codecs/wcd9378-sdca.h | 20 +
sound/soc/codecs/wcd9378-sdw.c | 51 ++
5 files changed, 1142 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 e78d725e61e2..4191fe244151 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2540,6 +2540,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 9287a602d41e..2a6b3168a410 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -368,6 +368,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
@@ -823,6 +824,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..f1f94c13cba5
--- /dev/null
+++ b/sound/soc/codecs/wcd9378-sdca.c
@@ -0,0 +1,1058 @@
+// 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 default: 2.75V (patched per-slave in populate_function). */
+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,
+ .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,
+ .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,
+ .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,
+ .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,
+ .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,
+ .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 = 0x1, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .interrupt_position = SDCA_NO_INTERRUPT,
+ .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 = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .interrupt_position = SDCA_NO_INTERRUPT,
+ .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 },
+};
+
+/* 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 = 0x1, .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,
+ .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 = 0x1, .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 },
+ .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 = 0x1, .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,
+ .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,
+ .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] };
+
+static struct sdca_control entity_su35_controls[] = {
+ { .sel = 0x1, .mode = SDCA_ACCESS_MODE_RO, .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 = 0x1, .mode = SDCA_ACCESS_MODE_RW, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .interrupt_position = SDCA_NO_INTERRUPT,
+ .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 = 0x1, .mode = SDCA_ACCESS_MODE_DC, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .interrupt_position = SDCA_NO_INTERRUPT,
+ .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 = 100000, /* 100ms — 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;
+
+ ret = sdw_slave_read_prop(slave);
+ if (ret)
+ return ret;
+
+ prop->use_domain_irq = true;
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY |
+ SDW_SCP_INT1_IMPL_DEF;
+
+ /* Compute-mode fixed SoundWire slave properties (not described in DT) */
+ 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 sdw_slave *slave,
+ 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;
+
+ /* Elevate is_volatile / has_reset to match the DisCo/ACPI path. */
+ sdca_apply_default_control_classifiers(function);
+
+ 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
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-09-18 13:19 ` [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
@ 2026-09-18 13:51 ` Mark Brown
0 siblings, 0 replies; 14+ messages in thread
From: Mark Brown @ 2026-09-18 13:51 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Liam Girdwood, Jaroslav Kysela, Takashi Iwai, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Charles Keepax, Maciej Strozek,
linux-sound, linux-arm-msm, devicetree, linux-kernel, patches
[-- Attachment #1: Type: text/plain, Size: 587 bytes --]
On Fri, Sep 18, 2026 at 02:19:12PM +0100, Srinivas Kandagatla wrote:
> Describe the WCD9378 SoundWire peripheral which is SDCA compatible
> soundwire device. It provides SDCA SimpleJack for headphone playback
> headset mic capture and MBHC jack detection.
Please submit patches using subject lines reflecting the style for the
subsystem, this makes it easier for people to identify relevant patches.
Look at what existing commits in the area you're changing are doing and
make sure your subject lines visually resemble what they're doing.
There's no need to resubmit to fix this alone.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function
2026-09-18 13:19 ` [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function Srinivas Kandagatla
@ 2026-09-18 14:15 ` Charles Keepax
0 siblings, 0 replies; 14+ messages in thread
From: Charles Keepax @ 2026-09-18 14:15 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Maciej Strozek, linux-sound, linux-arm-msm,
devicetree, linux-kernel, patches
On Fri, Sep 18, 2026 at 02:19:09PM +0100, Srinivas Kandagatla wrote:
> Add struct sdca_class_ops with a populate_function callback that lets
> codec drivers supply the SDCA topology (entities, clusters,
> init_table, ...) from static tables in place of sdca_parse_function()
> on DT/non-DisCo platforms. The callback is a pure data source and
> performs no bus I/O.
>
> Codec drivers embed sdca_class_drv in their own priv and register
> their populate_function through class_ops passed to
> sdca_class_probe().
>
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
> ---
> +/**
> + * struct sdca_class_ops - optional codec-provided class callbacks
> + * @populate_function: fill @function (entities, clusters, init_table, ...)
> + * from static tables in place of sdca_parse_function() on
> + * DT/non-DisCo platforms. Pure data source; performs no
> + * bus I/O. Return 0 on success or a negative errno.
> + */
> +struct sdca_class_ops {
> + int (*populate_function)(struct sdw_slave *slave,
> + struct sdca_function_data *function);
> +};
> - ret = sdca_parse_function(dev, drv->function);
> + if (core->ops && core->ops->populate_function) {
> + ret = core->ops->populate_function(core->sdw, drv->function);
We should be passing the function device not the soundwire device
here, this is a function callback.
Thanks,
Charles
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper
2026-09-18 13:19 ` [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper Srinivas Kandagatla
@ 2026-09-18 14:16 ` Charles Keepax
2026-09-18 14:20 ` Srinivas Kandagatla
0 siblings, 1 reply; 14+ messages in thread
From: Charles Keepax @ 2026-09-18 14:16 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Maciej Strozek, linux-sound, linux-arm-msm,
devicetree, linux-kernel, patches
On Fri, Sep 18, 2026 at 02:19:11PM +0100, Srinivas Kandagatla wrote:
> sdca_parse_function() derives is_volatile (see c7b6c6b60594) and the
> spec-defined reset value (see 02d851b46b36) from each Control's
> (entity type, selector, access mode). Codecs that ship static SDCA
> tables via populate_function skip that path, so RW1S action Controls
> end up with is_volatile=0 and get replayed on every regcache_sync.
>
> Add sdca_apply_default_control_classifiers() to re-run those two
> classifiers over a Function. Elevate-only, so codec-set flags win.
> Call from the tail of populate_function.
>
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
> ---
> +/**
> + * sdca_apply_default_control_classifiers - apply framework-known Control fields
> + * @function: Function populated via static tables (populate_function path).
> + *
> + * Runs the same per-Control classifiers as sdca_parse_function() so both
> + * entry paths land on identical @is_volatile and @has_reset/@reset fields.
> + * Elevate-only: codec-set flags/values are preserved.
> + */
> +void sdca_apply_default_control_classifiers(struct sdca_function_data *function)
> +{
> + int i, j;
> +
> + for (i = 0; i < function->num_entities; i++) {
> + struct sdca_entity *entity = &function->entities[i];
> +
> + for (j = 0; j < entity->num_controls; j++) {
> + struct sdca_control *control = &entity->controls[j];
> +
> + if (!control->is_volatile)
> + control->is_volatile =
> + find_sdca_control_volatile(entity, control);
> +
> + if (!control->has_reset)
> + find_sdca_control_reset(entity, control);
> + }
> + }
> +}
> +EXPORT_SYMBOL_NS(sdca_apply_default_control_classifiers, "SND_SOC_SDCA");
Just set the values correctly in the tables in the driver, I
don't really like this going back and fixing the tables up leaves
the driver tables looking slightly confusing.
Thanks,
Charles
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper
2026-09-18 14:16 ` Charles Keepax
@ 2026-09-18 14:20 ` Srinivas Kandagatla
0 siblings, 0 replies; 14+ messages in thread
From: Srinivas Kandagatla @ 2026-09-18 14:20 UTC (permalink / raw)
To: Charles Keepax
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Maciej Strozek, linux-sound, linux-arm-msm,
devicetree, linux-kernel, patches
On 9/18/26 3:16 PM, Charles Keepax wrote:
> On Fri, Sep 18, 2026 at 02:19:11PM +0100, Srinivas Kandagatla wrote:
>> sdca_parse_function() derives is_volatile (see c7b6c6b60594) and the
>> spec-defined reset value (see 02d851b46b36) from each Control's
>> (entity type, selector, access mode). Codecs that ship static SDCA
>> tables via populate_function skip that path, so RW1S action Controls
>> end up with is_volatile=0 and get replayed on every regcache_sync.
>>
>> Add sdca_apply_default_control_classifiers() to re-run those two
>> classifiers over a Function. Elevate-only, so codec-set flags win.
>> Call from the tail of populate_function.
>>
>> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
>> ---
>> +/**
>> + * sdca_apply_default_control_classifiers - apply framework-known Control fields
>> + * @function: Function populated via static tables (populate_function path).
>> + *
>> + * Runs the same per-Control classifiers as sdca_parse_function() so both
>> + * entry paths land on identical @is_volatile and @has_reset/@reset fields.
>> + * Elevate-only: codec-set flags/values are preserved.
>> + */
>> +void sdca_apply_default_control_classifiers(struct sdca_function_data *function)
>> +{
>> + int i, j;
>> +
>> + for (i = 0; i < function->num_entities; i++) {
>> + struct sdca_entity *entity = &function->entities[i];
>> +
>> + for (j = 0; j < entity->num_controls; j++) {
>> + struct sdca_control *control = &entity->controls[j];
>> +
>> + if (!control->is_volatile)
>> + control->is_volatile =
>> + find_sdca_control_volatile(entity, control);
>> +
>> + if (!control->has_reset)
>> + find_sdca_control_reset(entity, control);
>> + }
>> + }
>> +}
>> +EXPORT_SYMBOL_NS(sdca_apply_default_control_classifiers, "SND_SOC_SDCA");
>
> Just set the values correctly in the tables in the driver, I
> don't really like this going back and fixing the tables up leaves
> the driver tables looking slightly confusing.
That is one way to do it, I was trying to be more align with what was
getting done after parsing the acpi tables. But I dont mind updating the
tables directly aswell.
I will try to document this so that in future if someone is converting
any acpi tables directly and then they need to mash up few things before
passing to sdca core.
--srini
>
> Thanks,
> Charles
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
2026-09-18 13:19 ` [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
@ 2026-09-18 14:28 ` Charles Keepax
0 siblings, 0 replies; 14+ messages in thread
From: Charles Keepax @ 2026-09-18 14:28 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Bard Liao,
Pierre-Louis Bossart, Maciej Strozek, linux-sound, linux-arm-msm,
devicetree, linux-kernel, patches
On Fri, Sep 18, 2026 at 02:19:13PM +0100, Srinivas Kandagatla wrote:
> 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 derived from DT
> (qcom,micbias2-microvolt).
> - MBHC-based headset jack detection.
>
> Implements:
> - sdw_slave_ops.read_prop: SoundWire slave properties and dpn caps
> for the compute-mode dataports.
> - sdca_class_hw_ops.populate_function: fills the SDCA Function data
> (entities, clusters, init_table) from static tables and patches
> the IT 33 MIC_BIAS default with the DT-derived per-slave value.
>
> Binds SoundWire slave id 0x0217:0x0110 when qcom,compute-mode is set
> on the DT node.
>
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
> ---
Both of these are pretty minor comments, more suggestions really
so feel free to ignore if you prefer way it is.
> +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;
> +
> + ret = sdw_slave_read_prop(slave);
> + if (ret)
> + return ret;
Do you want to call this? It is just reading a bunch of DT
properties you don't have, and if you are using some of them
they should probably go in the binding docs.
> +
> + prop->use_domain_irq = true;
> + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY |
> + SDW_SCP_INT1_IMPL_DEF;
> +
> + /* Compute-mode fixed SoundWire slave properties (not described in DT) */
> + 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;
I would still be tempted to kmemdup static arrays for these
rather than kcallocing and filling in manually.
Thanks,
Charles
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-09-18 14:29 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-18 13:19 [PATCH v4 0/8] ASoC: Qualcomm Tambora (WCD9378) SDCA codec Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 1/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 2/8] ASoC: SDCA: export PM helpers keyed on sdca_class_drv Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 3/8] ASoC: SDCA: expose class SoundWire probe/remove as library Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 4/8] ASoC: SDCA: add class_ops with populate_function Srinivas Kandagatla
2026-09-18 14:15 ` Charles Keepax
2026-09-18 13:19 ` [PATCH v4 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 6/8] ASoC: SDCA: add sdca_apply_default_control_classifiers() helper Srinivas Kandagatla
2026-09-18 14:16 ` Charles Keepax
2026-09-18 14:20 ` Srinivas Kandagatla
2026-09-18 13:19 ` [PATCH v4 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
2026-09-18 13:51 ` Mark Brown
2026-09-18 13:19 ` [PATCH v4 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
2026-09-18 14:28 ` Charles Keepax
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®