* [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec
@ 2026-07-22 23:42 Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI Srinivas Kandagatla
` (8 more replies)
0 siblings, 9 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
Hi all,
At Linux Plumbers 2025 in the Devicetree MC I raised the "SDCA and the
classic ACPI-DT problem" [1]: MIPI SDCA is built around ACPI/DisCo
firmware descriptors, and the in-tree ASoC SDCA framework
(sound/soc/sdca/) enumerates functions, entities, controls and PDEs by
walking those tables. On ARM64 DT platforms there is no DisCo, so the
framework is unreachable and sdca codecs will not be able to use the
generic sdca drivers and duplicating.
The direction from that discussion was to let DT platforms reuse the
same class/function auxiliary driver plumbing and have codec drivers
supply the small amount of static function/entity metadata that DisCo
would otherwise carry, plus hooks for device-specific bring-up. This
series is a first cut at that, with the Qualcomm WCD9378 ("Tambora")
SDCA codec on the Glymur CRD as the first consumer. Static table that in
part of this codec is generated from acpi tables.
About WCD9378: the same silicon ships in two very different platform
modes. In "mobile mode" (Qualcomm phone/tablet SoCs) is enumerated as
as tx and rx device, each of which has dedicated control and data lines.
mobile mode is addressed by Jorijn's posting [2].
In "compute mode" (Qualcomm compute platforms such as Glymur / X2 Elite)
the same part is enumerated as a single standard MIPI SDCA class device
on SoundWire and is driven purely through the SDCA framework, which
is what this series covers. Both modes share the same MIPI class ID
and, in DT, the same compatible; qcom,compute-mode selects which
driver path is taken.
Framework enablement (patches 1-6):
* 1: hw_params fix for OT DAI upstream Input Terminals (found while
bringing up Tambora capture).
* 2: allow sound/soc/sdca/ to build without ACPI.
* 3: turn sdca_class into an exported library and introduce
struct sdca_class_hw_ops with hw_init + get_function_data, so a
codec driver registers its own sdw_driver, supplies static function
descriptors when there is no DisCo node, and owns regulators/reset
at probe time. The built-in class_sdw_driver stays for
pure-generic parts (hw_ops = NULL).
* 4: pde_pre_pmu / pde_post_pmu hooks in hw_ops so vendor drivers
can stage/commit register writes around PDE PS0 transitions.
* 5: of_xlate_dai_name that resolves sound-dai cells by entity index
rather than positional DAI order.
* 6: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in the class function
driver.
First consumer (patches 7-8):
* 7: WCD9378 SoundWire slave binding. Compatible is shared with
Jorijn's mobile WCD9378 posting [2]; qcom,compute-mode selects the
SDCA/compute-mode probe path and gates the compute-only properties.
* 8: Tambora codec driver - registers its own sdw_driver, calls into
the class library, supplies SimpleJack + HID functions via
get_function_data, owns supplies/reset/mic-bias in hw_init, uses
pde_*_pmu for the FU + IT_USAGE writes the PDE sequencer needs.
RFC because patches 1-6 touch the SDCA framework, is what I
would most like feedback on. Tested on Qualcomm Glymur CRD:
headphone playback, capture work end-to-end via the Tambora
codec over the mulit master SoundWire bus.
Platform bits (LPASS clock controller, SoundWire aggregate
controller, machine-driver DT, defconfig, ...) are held back for a
separate posting once this shape is agreed.
Next steps
==========
The most obvious follow-up is figuring out how the mobile-mode and
compute-mode WCD9378 drivers coexist under one compatible. Jorijn's
mobile driver [2] and this compute-mode driver target the same silicon
and register the same MIPI class ID on the SoundWire bus, and today
they discriminate purely on the qcom,compute-mode DT property. We
need to agree on a cleaner arbitration. Probably a shared
sdw_driver stub in wcd9378-common that reads the mode and dispatches
to the mobile or compute code path - so that both drivers can live in
tree without racing to bind the same slave. I would like to line this
uip with Jorijn as a follow-up to this series. Cleanup some of the
static volume setting to a proper volume control.
Links
=====
[1] LPC 2025, Devicetree MC:
"DeviceTrees - MIPI SoundWire Device Class for Audio (SDCA) and
classic ACPI-DT problem"
https://lpc.events/event/19/contributions/2024/
[2] Jorijn Rikhof, mobile-mode WCD9378 posting:
https://lwn.net/Articles/1081648/
Thanks,
Srini
Srinivas Kandagatla (8):
ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI
ASoC: SDCA: allow building without ACPI
ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms
ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops
ASoC: SDCA: class_function: xlate sound-dai cell by entity index
ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function
driver
dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
.../bindings/sound/qcom,wcd9378-sdw.yaml | 201 +++
include/sound/sdca.h | 2 +-
sound/soc/codecs/Kconfig | 12 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wcd9378-sdca.c | 1202 +++++++++++++++++
sound/soc/sdca/Kconfig | 6 +-
sound/soc/sdca/sdca_asoc.c | 84 +-
sound/soc/sdca/sdca_class.c | 83 +-
sound/soc/sdca/sdca_class.h | 41 +
sound/soc/sdca/sdca_class_function.c | 80 +-
sound/soc/sdca/sdca_device.c | 4 +
sound/soc/sdca/sdca_function_device.c | 2 +-
sound/soc/sdca/sdca_functions.c | 4 +
13 files changed, 1710 insertions(+), 13 deletions(-)
create mode 100644 Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
create mode 100644 sound/soc/codecs/wcd9378-sdca.c
--
2.53.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
` (7 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
For a capture stream the DAI is the Output Terminal so
sdca_asoc_hw_params() only programs the OT itself: cluster, clock
and Usage. The upstream Input Terminals that actually source the
audio never get their Usage, ClusterIndex or clock programmed, so
the power domain comes up but no samples are produced.
Walk the source graph from the OT and program every Input Terminal
reachable through it.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_asoc.c | 51 ++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index b4dedba719dc..ac688cd62970 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -1726,6 +1726,51 @@ static int set_usage(struct device *dev, struct regmap *regmap,
return -EINVAL;
}
+/*
+ * For a capture stream the ASoC DAI is the Output Terminal, so hw_params
+ * only programs the OT itself. The upstream Input Terminal(s) that
+ * actually source the audio (e.g. a mic input behind a power-domain entity)
+ * never get their Usage / ClusterIndex / clock programmed and stay inactive,
+ * so the power domain comes up but no samples are produced. Walk the source
+ * graph from the OT and program every Input Terminal we reach.
+ */
+static int set_input_terminal_sources(struct device *dev, struct regmap *regmap,
+ struct sdca_function_data *function,
+ struct sdca_entity *entity,
+ int channels, int rate, int width)
+{
+ int i, ret;
+
+ for (i = 0; i < entity->num_sources; i++) {
+ struct sdca_entity *src = entity->sources[i];
+
+ if (src->type == SDCA_ENTITY_TYPE_IT) {
+ ret = set_cluster(dev, regmap, function, src, channels);
+ if (ret)
+ return ret;
+
+ if (src->iot.clock) {
+ ret = set_clock(dev, regmap, function,
+ src->iot.clock, rate);
+ if (ret)
+ return ret;
+ }
+
+ ret = set_usage(dev, regmap, function, src,
+ SDCA_CTL_IT_USAGE, rate, width);
+ if (ret)
+ return ret;
+ }
+
+ ret = set_input_terminal_sources(dev, regmap, function, src,
+ channels, rate, width);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* sdca_asoc_hw_params - set SDCA channels, sample rate and bit depth
* @dev: Pointer to the device, used for error messages.
@@ -1762,6 +1807,12 @@ int sdca_asoc_hw_params(struct device *dev, struct regmap *regmap,
break;
case SDCA_ENTITY_TYPE_OT:
usage_sel = SDCA_CTL_OT_USAGE;
+
+ /* Program the upstream Input Terminal(s) that source this OT. */
+ ret = set_input_terminal_sources(dev, regmap, function, entity,
+ channels, rate, width);
+ if (ret)
+ return ret;
break;
default:
dev_err(dev, "%s: hw_params on non-terminal entity\n", entity->label);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms Srinivas Kandagatla
` (6 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, 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.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
include/sound/sdca.h | 2 +-
sound/soc/sdca/Kconfig | 6 ++++--
sound/soc/sdca/sdca_device.c | 4 ++++
sound/soc/sdca/sdca_functions.c | 4 ++++
4 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/include/sound/sdca.h b/include/sound/sdca.h
index 2bdf4e333e04..c449b10ef893 100644
--- a/include/sound/sdca.h
+++ b/include/sound/sdca.h
@@ -60,7 +60,7 @@ 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);
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index 4c0dcb9ff3b9..dc4cba1e3e0e 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.get_function_data.
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 405e80b979de..59d6c7466d91 100644
--- a/sound/soc/sdca/sdca_device.c
+++ b/sound/soc/sdca/sdca_device.c
@@ -28,6 +28,7 @@ void sdca_lookup_interface_revision(struct sdw_slave *slave)
}
EXPORT_SYMBOL_NS(sdca_lookup_interface_revision, "SND_SOC_SDCA");
+#if IS_ENABLED(CONFIG_ACPI)
static void devm_acpi_table_put(void *ptr)
{
acpi_put_table((struct acpi_table_header *)ptr);
@@ -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 77940bd6b33c..8814adc05f04 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)
{
/*
@@ -198,6 +199,9 @@ void sdca_lookup_functions(struct sdw_slave *slave)
acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data);
}
+#else
+void sdca_lookup_functions(struct sdw_slave *slave) { }
+#endif
EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA");
struct raw_init_write {
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops Srinivas Kandagatla
` (5 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
On ARM/DT platforms without ACPI/DisCo, sdca_lookup_functions() is a
no-op and num_functions stays 0. Introduce struct sdca_class_hw_ops
with hw_init and get_function_data callbacks so codec drivers can
supply pre-populated sdca_function_data and toggle supplies/reset at
probe time.
Convert the class SoundWire probe into an exported library helper
sdca_class_probe(sdw, hw_ops) and export sdca_class_read_prop() and
sdca_class_pm_ops. Codec-specific SoundWire drivers can register their
own sdw_driver and call these helpers from their probe. The built-in
class_sdw_driver stays for generic SDCA parts that need no per-device
quirks (hw_ops = NULL).
For non-DisCo boots, the class helper injects the driver-supplied
function descriptors into sdca_device_data so sdca_dev_register_functions()
can create the auxiliary devices, and the auxiliary function driver uses
get_function_data() as a fallback source of per-entity data when the
firmware node is absent.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_class.c | 83 +++++++++++++++++++++++++--
sound/soc/sdca/sdca_class.h | 30 ++++++++++
sound/soc/sdca/sdca_class_function.c | 40 ++++++++++++-
sound/soc/sdca/sdca_function_device.c | 2 +-
4 files changed, 146 insertions(+), 9 deletions(-)
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
index 8d7b007a068f..c9a4cce77557 100644
--- a/sound/soc/sdca/sdca_class.c
+++ b/sound/soc/sdca/sdca_class.c
@@ -24,7 +24,15 @@
#define CLASS_SDW_ATTACH_TIMEOUT_MS 5000
-static int class_read_prop(struct sdw_slave *sdw)
+/**
+ * sdca_class_read_prop - fill SDCA-common SoundWire slave properties
+ * @sdw: SoundWire slave
+ *
+ * Exported so codec-specific SoundWire drivers can invoke the SDCA
+ * common property setup from their own sdw_slave_ops.read_prop, and
+ * then apply codec-specific overrides inline.
+ */
+int sdca_class_read_prop(struct sdw_slave *sdw)
{
struct sdw_slave_prop *prop = &sdw->prop;
@@ -36,9 +44,10 @@ static int class_read_prop(struct sdw_slave *sdw)
return 0;
}
+EXPORT_SYMBOL_NS_GPL(sdca_class_read_prop, "SND_SOC_SDCA_CLASS");
static const struct sdw_slave_ops class_sdw_ops = {
- .read_prop = class_read_prop,
+ .read_prop = sdca_class_read_prop,
};
static void class_regmap_lock(void *data)
@@ -136,9 +145,23 @@ 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
+ * @hw_ops: optional device-specific hw_ops (may be NULL for pure-generic
+ * SDCA parts that need no quirks)
+ *
+ * Exported so codec-specific SoundWire drivers can call this from their
+ * own sdw_driver.probe. For codecs with quirks, pass the codec's
+ * sdca_class_hw_ops so hw_init, DT function injection, and PDE hooks
+ * are wired up. For pure-generic SDCA parts (used by the built-in
+ * class_sdw_driver in this file), pass NULL.
+ */
+int sdca_class_probe(struct sdw_slave *sdw,
+ const struct sdca_class_hw_ops *hw_ops)
{
struct device *dev = &sdw->dev;
+ struct sdca_device_data *data = &sdw->sdca_data;
struct regmap_config *dev_config;
struct sdca_class_drv *drv;
int ret;
@@ -156,11 +179,47 @@ static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id
drv->dev = dev;
drv->sdw = sdw;
+ drv->hw_ops = hw_ops;
mutex_init(&drv->regmap_lock);
mutex_init(&drv->init_lock);
dev_set_drvdata(drv->dev, drv);
+ /*
+ * On ARM platforms without ACPI/DisCo tables, sdca_lookup_functions()
+ * is a no-op and num_functions stays 0. Inject the function descriptors
+ * from the device-specific static data so sdca_dev_register_functions()
+ * can create the auxiliary devices.
+ */
+ if (data->num_functions == 0 && hw_ops && hw_ops->get_function_data) {
+ struct sdca_function_data *fdata;
+ unsigned int num = 0;
+ unsigned int i;
+
+ fdata = hw_ops->get_function_data(&num);
+ if (!fdata || num == 0 || num > SDCA_MAX_FUNCTION_COUNT)
+ return -EINVAL;
+
+ for (i = 0; i < num; i++) {
+ if (!fdata[i].desc)
+ return -EINVAL;
+ data->function[i].type = fdata[i].desc->type;
+ data->function[i].adr = fdata[i].desc->adr;
+ data->function[i].name = fdata[i].desc->name;
+ data->function[i].node = NULL;
+ }
+ data->num_functions = num;
+
+ dev_info(dev, "no DisCo/ACPI functions found, injected %u descriptor(s)\n",
+ num);
+ }
+
+ if (hw_ops && hw_ops->hw_init) {
+ ret = hw_ops->hw_init(sdw);
+ if (ret)
+ return dev_err_probe(dev, ret, "hw_init failed\n");
+ }
+
INIT_WORK(&drv->boot_work, class_boot_work);
dev_config->lock_arg = &drv->regmap_lock;
@@ -185,6 +244,13 @@ 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 int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
+{
+ /* Pure-generic SDCA parts: no per-device hw_ops. */
+ return sdca_class_probe(sdw, NULL);
+}
static void class_sdw_remove(struct sdw_slave *sdw)
{
@@ -266,11 +332,18 @@ static int class_runtime_resume(struct device *dev)
return ret;
}
-static const struct dev_pm_ops class_pm_ops = {
+const struct dev_pm_ops sdca_class_pm_ops = {
SYSTEM_SLEEP_PM_OPS(class_suspend, class_resume)
RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL)
};
+EXPORT_SYMBOL_NS_GPL(sdca_class_pm_ops, "SND_SOC_SDCA_CLASS");
+/*
+ * Built-in class driver for SDCA parts that need no per-device quirks.
+ * Codecs that need hw_ops (hw_init, DT function injection, PDE / jack
+ * hooks, etc.) register their own sdw_driver and call
+ * sdca_class_probe(slave, &their_hw_ops) from their .probe.
+ */
static const struct sdw_device_id class_sdw_id[] = {
SDW_SLAVE_ENTRY(0x01FA, 0x4245, 0),
SDW_SLAVE_ENTRY(0x01FA, 0x4249, 0),
@@ -282,7 +355,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.h b/sound/soc/sdca/sdca_class.h
index 57f7f8d08f49..74db9d4ce4a2 100644
--- a/sound/soc/sdca/sdca_class.h
+++ b/sound/soc/sdca/sdca_class.h
@@ -15,10 +15,32 @@
#include <linux/workqueue.h>
struct device;
+struct dev_pm_ops;
struct regmap;
struct sdw_slave;
struct sdca_function_data;
+/**
+ * struct sdca_class_hw_ops - optional device-specific hardware callbacks
+ * @hw_init: called during probe to enable supplies, toggle reset GPIO, etc.
+ * @get_function_data: called when no DisCo/ACPI firmware node is available
+ * (e.g. DT/ARM platforms) to supply pre-populated static function
+ * data in place of sdca_parse_function(). Returns a pointer to
+ * an array of struct sdca_function_data and stores the number
+ * of entries in @num (must be > 0 and <= SDCA_MAX_FUNCTION_COUNT).
+ * May be NULL.
+ *
+ * Codec-specific SoundWire drivers pass a pointer to this struct to
+ * sdca_class_probe() from their sdw_driver.probe. Codec-specific
+ * SoundWire slave property overrides live directly in the codec's
+ * sdw_slave_ops.read_prop, which should call sdca_class_read_prop() to
+ * fill the SDCA-common bits first.
+ */
+struct sdca_class_hw_ops {
+ int (*hw_init)(struct sdw_slave *slave);
+ struct sdca_function_data *(*get_function_data)(unsigned int *num);
+};
+
struct sdca_class_drv {
struct device *dev;
struct regmap *dev_regmap;
@@ -26,10 +48,18 @@ struct sdca_class_drv {
struct sdca_interrupt_info *irq_info;
+ const struct sdca_class_hw_ops *hw_ops;
+
struct mutex regmap_lock;
/* Serialise function initialisations */
struct mutex init_lock;
struct work_struct boot_work;
};
+/* Library helpers used by codec-specific SDCA SoundWire drivers. */
+int sdca_class_read_prop(struct sdw_slave *sdw);
+int sdca_class_probe(struct sdw_slave *sdw,
+ const struct sdca_class_hw_ops *hw_ops);
+extern const struct dev_pm_ops sdca_class_pm_ops;
+
#endif /* __SDCA_CLASS_H__ */
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index 1496a15f7d2a..905c96ed6172 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -329,9 +329,43 @@ static int class_function_probe(struct auxiliary_device *auxdev,
drv->core = core;
drv->function = &sdev->function;
- ret = sdca_parse_function(dev, core->sdw, drv->function);
- if (ret)
- return ret;
+ if (drv->function->desc->node) {
+ ret = sdca_parse_function(dev, core->sdw, drv->function);
+ if (ret)
+ return ret;
+ } else if (core->hw_ops && core->hw_ops->get_function_data) {
+ /*
+ * No DisCo/ACPI firmware node available (e.g. DT/ARM platform).
+ * Use pre-populated static function data supplied by the
+ * device-specific hw_ops instead of sdca_parse_function().
+ * The callback returns an array of function_data entries;
+ * pick the one matching this auxdev's function type.
+ */
+ struct sdca_function_data *fdata;
+ unsigned int num = 0;
+ unsigned int i;
+
+ fdata = core->hw_ops->get_function_data(&num);
+ if (!fdata || num == 0)
+ return -EINVAL;
+
+ for (i = 0; i < num; i++) {
+ if (fdata[i].desc &&
+ fdata[i].desc->type == sdev->function.desc->type) {
+ *drv->function = fdata[i];
+ drv->function->desc = sdev->function.desc;
+ break;
+ }
+ }
+ if (i == num) {
+ dev_err(dev, "no matching function data for type 0x%x\n",
+ sdev->function.desc->type);
+ return -EINVAL;
+ }
+ } else {
+ dev_err(dev, "no firmware node and no get_function_data hook\n");
+ return -EINVAL;
+ }
ndefaults = sdca_regmap_count_constants(dev, drv->function);
if (ndefaults < 0)
diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c
index b5ca98283a88..9fecfcfdbb6d 100644
--- a/sound/soc/sdca/sdca_function_device.c
+++ b/sound/soc/sdca/sdca_function_device.c
@@ -46,7 +46,7 @@ static struct sdca_dev *sdca_dev_register(struct device *parent,
auxdev = &sdev->auxdev;
auxdev->name = function_desc->name;
auxdev->dev.parent = parent;
- auxdev->dev.fwnode = function_desc->node;
+ auxdev->dev.fwnode = function_desc->node ?: dev_fwnode(parent);
auxdev->dev.release = sdca_dev_release;
sdev->function.desc = function_desc;
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (2 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
` (4 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
Add pde_pre_pmu and pde_post_pmu callbacks to struct sdca_class_hw_ops
so codec drivers can hook into DAPM PDE power-domain transitions.
pde_pre_pmu is called before REQUESTED_PS=PS0 is written, allowing
the driver to stage state (e.g. IT_USAGE, MIC_BIAS) that must be valid
before the PDE sequencer runs.
pde_post_pmu is called after REQUESTED_PS=PS0 and before ACTUAL_PS
polling begins, allowing the driver to commit pending writes (e.g.
FU mute/volume via SCP_COMMIT) that the sequencer needs to see.
Both callbacks receive function_id and entity_id so the driver can act
selectively per power domain.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_asoc.c | 33 ++++++++++++++++++++++++++++++++-
sound/soc/sdca/sdca_class.h | 11 +++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c
index ac688cd62970..795498b9a314 100644
--- a/sound/soc/sdca/sdca_asoc.c
+++ b/sound/soc/sdca/sdca_asoc.c
@@ -32,6 +32,7 @@
#include <sound/soc-dai.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
+#include "sdca_class.h"
static bool exported_control(struct sdca_entity *entity, struct sdca_control *control)
{
@@ -432,6 +433,8 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget,
{
struct snd_soc_component *component = snd_soc_dapm_to_component(widget->dapm);
struct sdca_entity *entity = widget->priv;
+ struct sdca_class_drv *core;
+ unsigned int fn;
int from, to;
int ret;
@@ -439,6 +442,19 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget,
return -EIO;
switch (event) {
+ case SND_SOC_DAPM_PRE_PMU:
+ core = dev_get_drvdata(component->dev->parent);
+ if (core && core->hw_ops && core->hw_ops->pde_pre_pmu) {
+ fn = SDW_SDCA_CTL_FUNC(widget->reg);
+ ret = core->hw_ops->pde_pre_pmu(core->sdw,
+ component->regmap, fn,
+ SDW_SDCA_CTL_ENT(widget->reg));
+ if (ret)
+ dev_warn(component->dev,
+ "%s: pde_pre_pmu failed: %d\n",
+ entity->label, ret);
+ }
+ return 0;
case SND_SOC_DAPM_POST_PMD:
from = widget->on_val;
to = widget->off_val;
@@ -451,6 +467,20 @@ static int entity_pde_event(struct snd_soc_dapm_widget *widget,
return 0;
}
+ if (event == SND_SOC_DAPM_POST_PMU) {
+ core = dev_get_drvdata(component->dev->parent);
+ if (core && core->hw_ops && core->hw_ops->pde_post_pmu) {
+ fn = SDW_SDCA_CTL_FUNC(widget->reg);
+ ret = core->hw_ops->pde_post_pmu(core->sdw,
+ component->regmap, fn,
+ SDW_SDCA_CTL_ENT(widget->reg));
+ if (ret)
+ dev_warn(component->dev,
+ "%s: pde_post_pmu failed: %d\n",
+ entity->label, ret);
+ }
+ }
+
ret = sdca_asoc_pde_poll_actual_ps(component->dev, component->regmap,
SDW_SDCA_CTL_FUNC(widget->reg),
SDW_SDCA_CTL_ENT(widget->reg),
@@ -502,7 +532,8 @@ static int entity_parse_pde(struct device *dev,
(*widget)->mask = GENMASK(control->nbits - 1, 0);
(*widget)->on_val = SDCA_PDE_PS0;
(*widget)->off_val = SDCA_PDE_PS3;
- (*widget)->event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD;
+ (*widget)->event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU |
+ SND_SOC_DAPM_POST_PMD;
(*widget)->event = entity_pde_event;
(*widget)->priv = entity;
(*widget)++;
diff --git a/sound/soc/sdca/sdca_class.h b/sound/soc/sdca/sdca_class.h
index 74db9d4ce4a2..1278159ea61b 100644
--- a/sound/soc/sdca/sdca_class.h
+++ b/sound/soc/sdca/sdca_class.h
@@ -29,6 +29,13 @@ struct sdca_function_data;
* an array of struct sdca_function_data and stores the number
* of entries in @num (must be > 0 and <= SDCA_MAX_FUNCTION_COUNT).
* May be NULL.
+ * @pde_pre_pmu: called before DAPM writes REQUESTED_PS=PS0; use to
+ * prepare device state that must be valid before the PDE
+ * sequencer runs (e.g. IT_USAGE); may be NULL
+ * @pde_post_pmu: called after DAPM writes REQUESTED_PS=PS0 and before
+ * ACTUAL_PS polling begins; use to commit pending register
+ * writes (e.g. FUNCTION_ACTION) on devices that require an
+ * explicit commit trigger for PDE power-up; may be NULL
*
* Codec-specific SoundWire drivers pass a pointer to this struct to
* sdca_class_probe() from their sdw_driver.probe. Codec-specific
@@ -39,6 +46,10 @@ struct sdca_function_data;
struct sdca_class_hw_ops {
int (*hw_init)(struct sdw_slave *slave);
struct sdca_function_data *(*get_function_data)(unsigned int *num);
+ int (*pde_pre_pmu)(struct sdw_slave *slave, struct regmap *regmap,
+ unsigned int function_id, unsigned int entity_id);
+ int (*pde_post_pmu)(struct sdw_slave *slave, struct regmap *regmap,
+ unsigned int function_id, unsigned int entity_id);
};
struct sdca_class_drv {
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (3 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver Srinivas Kandagatla
` (3 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, 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.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_class_function.c | 35 ++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index 905c96ed6172..b0fbe243d732 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -216,9 +216,44 @@ static int class_function_set_jack(struct snd_soc_component *component,
return sdca_jack_set_jack(core->irq_info, jack);
}
+/*
+ * Map a "sound-dai" phandle cell to a DAI. The cell value is the SDCA entity
+ * array index (matching dais[].id assigned in sdca_asoc_populate_dais()), not
+ * the positional order of the dataport DAIs, so the default positional xlate
+ * cannot be used.
+ */
+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;
+ int target = args->args[0];
+ int i;
+
+ for (i = 0; i < function->num_entities - 1; i++) {
+ struct sdca_entity *entity = &function->entities[i];
+
+ if (entity->type != SDCA_ENTITY_TYPE_IT &&
+ entity->type != SDCA_ENTITY_TYPE_OT)
+ continue;
+ if (!entity->iot.is_dataport)
+ continue;
+ if (i == target) {
+ *dai_name = entity->label;
+ return 0;
+ }
+ }
+
+ dev_err(component->dev, "xlate: no dataport entity at index %d (num_entities=%d)\n",
+ target, function->num_entities);
+ return -EINVAL;
+}
+
static const struct snd_soc_component_driver class_function_component_drv = {
.probe = class_function_component_probe,
.remove = class_function_component_remove,
+ .of_xlate_dai_name = class_function_of_xlate_dai_name,
.endianness = 1,
};
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (4 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-23 10:55 ` Charles Keepax
2026-07-22 23:42 ` [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
` (2 subsequent siblings)
8 siblings, 1 reply; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
Add SDCA_FUNCTION_TYPE_SIMPLE_JACK to the class function driver's
auxiliary id table so a SimpleJack auxdev is bound and enumerated as
an ASoC component. Wire set_jack for this function type so the machine
driver can register an ASoC jack against it.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/sdca/sdca_class_function.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
index b0fbe243d732..ed00bc1eeafa 100644
--- a/sound/soc/sdca/sdca_class_function.c
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -436,6 +436,7 @@ static int class_function_probe(struct auxiliary_device *auxdev,
switch (drv->function->desc->type) {
case SDCA_FUNCTION_TYPE_UAJ:
case SDCA_FUNCTION_TYPE_RJ:
+ case SDCA_FUNCTION_TYPE_SIMPLE_JACK:
cmp_drv->set_jack = class_function_set_jack;
break;
default:
@@ -621,6 +622,10 @@ static const struct auxiliary_device_id class_function_id_table[] = {
.name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_RJ_NAME,
.driver_data = SDCA_FUNCTION_TYPE_RJ,
},
+ {
+ .name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SIMPLE_NAME,
+ .driver_data = SDCA_FUNCTION_TYPE_SIMPLE_JACK,
+ },
{},
};
MODULE_DEVICE_TABLE(auxiliary, class_function_id_table);
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (5 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-23 9:14 ` Konrad Dybcio
2026-07-22 23:42 ` [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
2026-07-23 10:17 ` [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Charles Keepax
8 siblings, 1 reply; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
Describe the WCD9378 SDCA peripheral node (compatible sdw20217011000)
driven by the wcd9378-sdca codec driver for headphone playback, headset
mic capture and jack detection via the SimpleJack SDCA function type.
WCD9378 codec can be wired up in 2 different modes.
"mobile mode" on phone/tablet SoCs is enumerated as tx and rx device,
each of which has dedicated control and data lines.
"compute mode" on compute platforms such as Glymur is enumerated as
single standard MIPI SDCA class device.
Both modes share same Device ID, compatible; qcom,compute-mode selects
which driver path is taken. Supplies, reset GPIO and mic-bias voltages
live on the SoundWire slave node in compute mode and are forbidden in
mobile mode (owned by the top-level codec parent there).
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
.../bindings/sound/qcom,wcd9378-sdw.yaml | 200 ++++++++++++++++++
1 file changed, 200 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..2ed4ad92958e
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml
@@ -0,0 +1,200 @@
+# 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 SoundWire Slave devices on WCD9378
+
+maintainers:
+ - Jorijn van der Graaf <jorijnvdgraaf@catcrafts.net>
+ - Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
+
+description: |
+ The Qualcomm WCD9378 codec presents its SoundWire slave devices with
+ class ID sdw20217011000 in both operating modes:
+
+ * mobile mode -- two slave instances sit on separate SoundWire
+ masters carrying data only. A separate top-level codec node
+ owns the codec's supplies, mic-bias voltages and reset GPIO.
+
+ * SDCA / compute mode -- one aggregated slave sits on a multi-lane
+ master and carries both control and data. There is no separate
+ top-level codec node, so the SoundWire slave node itself owns
+ supplies, reset GPIO and mic-bias voltage configuration. It is
+ marked with qcom,compute-mode.
+
+ Codec drivers that match sdw20217011000 use qcom,compute-mode to
+ decide which mode to serve; the presence or absence of this property
+ also gates whether the supply and mic-bias properties on this node
+ are required or forbidden.
+
+properties:
+ compatible:
+ const: sdw20217011000
+
+ reg:
+ maxItems: 1
+
+ qcom,compute-mode:
+ description: |
+ Marks this SoundWire slave as the single aggregated slave that
+ implements the codec in SDCA / compute mode. Only present when
+ the codec has no separate top-level codec parent; in that case
+ this slave node also owns the codec's supplies, reset GPIO and
+ mic-bias voltage configuration. Absent in mobile mode.
+ type: boolean
+
+ qcom,port-mapping:
+ description: |
+ Static mapping between codec SoundWire peripheral ports and master
+ ports on the SoundWire bus. Length and interpretation depend on
+ the slave's role as fixed by the parent codec node:
+
+ mobile (5 entries): the mobile-mode driver derives direction
+ (RX / TX) from its own context (parent phandle from the
+ top-level codec node, or the presence of
+ qcom,tx-port-mapping / qcom,rx-port-mapping on other slaves
+ in that binding).
+ SDCA (7 entries): DP1..DP7 aggregated on the single slave
+ (index 0 unused).
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ minItems: 5
+ maxItems: 8
+
+ reset-gpios:
+ description: |
+ SDCA / compute mode only. GPIO used to release the codec from
+ reset. Active-low.
+ maxItems: 1
+
+ vdd-buck-supply:
+ description: SDCA / compute mode only. 1.8 V analog buck supply.
+
+ vdd-rxtx-supply:
+ description: SDCA / compute mode only. 1.8 V RX/TX supply.
+
+ vdd-io-supply:
+ description: SDCA / compute mode only. Digital I/O supply.
+
+ vdd-mic-bias-supply:
+ description: SDCA / compute mode only. Mic bias supply.
+
+ qcom,micbias1-microvolt:
+ description: SDCA / compute mode only. MICB1 rail voltage in microvolts.
+ minimum: 1800000
+ maximum: 2850000
+
+ qcom,micbias2-microvolt:
+ description: |
+ SDCA / compute mode only. MICB2 rail voltage in microvolts.
+ Used by the SDCA SimpleJack headset-mic input (IT33 / AMIC2).
+ minimum: 1800000
+ maximum: 2850000
+
+ qcom,micbias3-microvolt:
+ description: SDCA / compute mode only. MICB3 rail voltage in microvolts.
+ minimum: 1800000
+ maximum: 2850000
+
+ '#sound-dai-cells':
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - qcom,port-mapping
+ - '#sound-dai-cells'
+
+allOf:
+ # qcom,compute-mode gates the standalone-slave shape: supplies, reset
+ # and mic-bias voltages live on this node in compute mode and are
+ # forbidden in mobile mode (owned by the top-level codec parent there).
+ - if:
+ required:
+ - qcom,compute-mode
+ then:
+ required:
+ - vdd-buck-supply
+ - vdd-rxtx-supply
+ - vdd-io-supply
+ - vdd-mic-bias-supply
+ - qcom,micbias1-microvolt
+ - qcom,micbias2-microvolt
+ - qcom,micbias3-microvolt
+ else:
+ properties:
+ vdd-buck-supply: false
+ vdd-rxtx-supply: false
+ vdd-io-supply: false
+ vdd-mic-bias-supply: false
+ qcom,micbias1-microvolt: false
+ qcom,micbias2-microvolt: false
+ qcom,micbias3-microvolt: false
+ reset-gpios: false
+
+additionalProperties: false
+
+examples:
+ # Mobile mode: two SoundWire slaves on separate masters. Neither
+ # carries supplies or mic-bias -- the top-level codec parent owns
+ # those.
+ - |
+ soundwire@3210000 {
+ reg = <0x03210000 0x2000>;
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ wcd9378_rx: audio-codec@0,4 {
+ compatible = "sdw20217011000";
+ reg = <0 4>;
+ qcom,port-mapping = <1 2 3 4 5>;
+ #sound-dai-cells = <1>;
+ };
+ };
+
+ soundwire@33b0000 {
+ reg = <0x033b0000 0x2000>;
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ wcd9378_tx: audio-codec@0,3 {
+ compatible = "sdw20217011000";
+ reg = <0 3>;
+ qcom,port-mapping = <1 1 1 2 3>;
+ #sound-dai-cells = <1>;
+ };
+ };
+
+ # SDCA / compute mode: single aggregated slave -- owns supplies,
+ # reset GPIO and mic-bias voltages.
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ soundwire@7630000 {
+ reg = <0x07630000 0x10000>;
+ #address-cells = <2>;
+ #size-cells = <0>;
+
+ wcd9378c_sdw: audio-codec@3,0 {
+ compatible = "sdw20217011000";
+ reg = <0 3>;
+
+ qcom,compute-mode;
+ 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>;
+
+ qcom,micbias1-microvolt = <1800000>;
+ qcom,micbias2-microvolt = <2750000>;
+ qcom,micbias3-microvolt = <1800000>;
+
+ #sound-dai-cells = <1>;
+ };
+ };
+...
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (6 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
@ 2026-07-22 23:42 ` Srinivas Kandagatla
2026-07-23 10:17 ` [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Charles Keepax
8 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-22 23:42 UTC (permalink / raw)
To: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Charles Keepax, Maciej Strozek, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel, Srinivas Kandagatla
Add support for the Qualcomm Tambora (WCD9378) headset codec in SDCA
mode over SoundWire.
WCD9378 ("Tambora") is a low-power stereo audio codec used across
Qualcomm mobile and compute platforms. It integrates a headphone
amplifier, mono earpiece output, three analogue microphone inputs
with independent programmable mic-bias supplies, jack and button
detection, and SoundWire dataports for both playback and capture.
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
---
sound/soc/codecs/Kconfig | 12 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/wcd9378-sdca.c | 1202 +++++++++++++++++++++++++++++++
3 files changed, 1216 insertions(+)
create mode 100644 sound/soc/codecs/wcd9378-sdca.c
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 76e90144ea91..372d2e134f05 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -2770,6 +2770,18 @@ config SND_SOC_WSA884X
This enables support for Qualcomm WSA8840/WSA8845/WSA8845H Class-D
Smart Speaker Amplifier.
+config SND_SOC_WCD9378_SDCA
+ tristate "Qualcomm Tambora (WCD9378) SDCA codec"
+ depends on SOUNDWIRE
+ depends on SND_SOC_SDCA_CLASS
+ default m if ARCH_QCOM
+ 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_ZL38060
tristate "Microsemi ZL38060 Connected Home Audio Processor"
depends on SPI_MASTER
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index aa0396e5b575..6dcf28b88972 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -426,6 +426,7 @@ snd-soc-wm-hubs-y := wm_hubs.o
snd-soc-wsa881x-y := wsa881x.o
snd-soc-wsa883x-y := wsa883x.o
snd-soc-wsa884x-y := wsa884x.o
+snd-soc-wcd9378-sdca-y := wcd9378-sdca.o
snd-soc-zl38060-y := zl38060.o
# Amp
snd-soc-max9877-y := max9877.o
@@ -876,6 +877,7 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o
obj-$(CONFIG_SND_SOC_WSA881X) += snd-soc-wsa881x.o
obj-$(CONFIG_SND_SOC_WSA883X) += snd-soc-wsa883x.o
obj-$(CONFIG_SND_SOC_WSA884X) += snd-soc-wsa884x.o
+obj-$(CONFIG_SND_SOC_WCD9378_SDCA) += snd-soc-wcd9378-sdca.o
obj-$(CONFIG_SND_SOC_ZL38060) += snd-soc-zl38060.o
# Amp
diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c
new file mode 100644
index 000000000000..46a2b5728d2c
--- /dev/null
+++ b/sound/soc/codecs/wcd9378-sdca.c
@@ -0,0 +1,1202 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright (c) 2025 Qualcomm Technologies, Inc. All rights reserved.
+
+/*
+ * WCD9378 SDCA hardware quirk for the SDCA class driver.
+ *
+ * sdca_class.c is the SoundWire slave driver; it calls wcd9378_sdca_hw_ops
+ * during probe to enable supplies, toggle the reset GPIO, and apply
+ * SoundWire slave properties and static SDCA function data that are specific
+ * to this device (the Tambora / WCD9378 codec). On ARM/DT platforms there is
+ * no ACPI/DisCo firmware node, so the topology transcribed from the factory
+ * ASL is supplied via get_function_data.
+ */
+
+#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/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_function.h>
+#include <sound/soc.h>
+#include <sound/soc-dai.h>
+#include "../sdca/sdca_class.h"
+
+/* Forward declaration of entities array */
+static struct sdca_entity wcd9378_sdca_entities[];
+/*
+ * Entity array index map (see wcd9378_sdca_entities[] below).
+ * The entities are stored in the same order as the ASL entity-id-list,
+ * with Entity 0 (Function) last.
+ *
+ * [0] E001 IT 41 (0x1) [11] E00F IT 33 (0xF)
+ * [1] E002 CS 41 (0x2) [12] E010 PDE 34 (0x10)
+ * [2] E003 MFPU 21 (0x3) [13] E011 FU 33 (0x11)
+ * [3] E004 XU 42 (0x4) [14] E012 SU 35 (0x12)
+ * [4] E007 SU 43 (0x7) [15] E013 XU 36 (0x13)
+ * [5] E008 SU 45 (0x8) [16] E015 CS 36 (0x15)
+ * [6] E009 PDE 47 (0x9) [17] E016 OT 36 (0x16)
+ * [7] E00A OT 43 (0xA) [18] E017 MFPU 236 (0x17)
+ * [8] E00B OT 45 (0xB) [19] E018 CS 236 (0x18)
+ * [9] E00C GE 35 (0xC) [20] E019 OT 236 (0x19)
+ * [10] E00D IT 131 (0xD) [21] 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
+
+/*
+ * Range Data Structures
+ *
+ * Range layout is { cols, rows, data[cols*rows] }. The raw values mirror
+ * the little-endian buffers from the ASL.
+ */
+
+/* Entity 1 (IT 41) - Usage range: 1 septuple (cols=7, rows=1)
+ * PDM render stream: SoundWire carries 2-bit PDM at 4.8MHz internally, but
+ * the host-visible PCM rate/width is 48kHz/16-bit (msft-sdca-pdm-pcm map).
+ * Usage value = 0x2 (HIFI); value 0x3 (ULP) does not route audio to the
+ * HPH analog driver on WCD9378.
+ */
+static u32 range_it41_usage_data[] = {
+ /* usage, CBN, sample_rate, sample_width, full_scale, noise_floor, tag */
+ 0x2, 0x2D0, 0xBB80, 0x10, 0x0, 0x0, 0x0, /* HIFI, 48kHz PCM, 16-bit */
+};
+
+/* Entity 1 (IT 41) - ClusterIndex range: index -> cluster id */
+static u32 range_it41_cluster_data[] = { 0x1, 0x1 };
+
+/* Entity 1 (IT 41) - DataPort selector range (16 cols x 4 rows)
+ * DC value = 6; row A col 6 = 0x06 (DP6 / HPH render port).
+ */
+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
+};
+
+/* Entity 2 (CS 41) - SampleRateIndex range: index 1 -> 48kHz PCM
+ * Register DC value = 1. PCM rate is 48kHz (PDM-to-PCM mapped internally).
+ */
+static u32 range_cs41_sr_data[] = { 0x1, 0xBB80 };
+
+/* Selector unit range: disconnected / connected (shared layout) */
+static u32 range_su_sel_data[] = { 0x0, 0x1 };
+
+/* PDE Requested_PS range: PS0 / PS3 (shared layout) */
+static u32 range_pde_req_ps_data[] = { 0x0, 0x3 };
+
+/* Entity 0xC (GE 35) - SelectedMode range: mode -> terminal type
+ *
+ * WORKAROUND: modes 0 (Jack Unplugged) and 1 (Jack Unknown) are added
+ * and mapped to the same "Headphone on jack" terminal type as mode 4.
+ * The Tambora firmware on this board does the mechanical MBHC (fires
+ * SDCA_4 with DETECTED_MODE=1) but cannot complete headphone-vs-headset
+ * ADC discrimination on its own, so the state machine gets stuck at
+ * Jack Unknown and sdca_jack_process falls back to SELECTED_MODE=0.
+ * Without these mode 0/1 entries, the GE MUX DAPM widget has no route
+ * for those values and drops SU45's selector, muting all HPH playback.
+ * Aliasing both to Headphone keeps the IT41->OT43 (HPH) DAPM path alive
+ * so the analog HP jack works while proper HS discrimination lands in
+ * a future wcd-mbhc-v2 integration.
+ */
+static u32 range_ge35_mode_data[] = {
+ 0x0, 0x6C0, /* Mode 0: Jack Unplugged (workaround: routes to HPH) */
+ 0x1, 0x6C0, /* Mode 1: Jack Unknown (workaround: routes to HPH) */
+ 0x3, 0x6D0, /* Mode 3: Headset output on jack */
+ 0x4, 0x6C0, /* Mode 4: Headphone on jack */
+};
+
+/* Entity 0xD (IT 131) - Usage range: optimization render stream at 192kHz */
+static u32 range_it131_usage_data[] = {
+ 0x3, 0x334, 0x2EE00, 0x8, 0x0, 0x0, 0x0, /* ULP, 192kHz, 8-bit */
+};
+
+/* Entity 0xD (IT 131) - ClusterIndex range: index -> cluster id 3 */
+static u32 range_it131_cluster_data[] = { 0x1, 0x3 };
+
+/* Entity 0xD (IT 131) - DataPort selector range (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
+};
+
+/* Entity 0xE (CS 131) - SampleRateIndex range: index -> 192kHz */
+static u32 range_cs131_sr_data[] = { 0x1, 0x2EE00 };
+
+/* Entity 0xF (IT 33) - Mic bias range: 2.75V */
+static u32 range_it33_micbias_data[] = { 0x5 };
+
+/* Entity 0xF (IT 33) - Usage range */
+static u32 range_it33_usage_data[] = {
+ 0x1, 0x2C6, 0x0, 0x0, 0x0, 0x0, 0x0, /* HIFI */
+};
+
+/* Entity 0xF (IT 33) - ClusterIndex range: index -> cluster id 2 */
+static u32 range_it33_cluster_data[] = { 0x1, 0x2 };
+
+/* Entity 0x15 (CS 36) - SampleRateIndex range: index 1 -> 48kHz PCM */
+static u32 range_cs36_sr_data[] = { 0x1, 0xBB80 };
+
+/* Entity 0x16 (OT 36) - Usage range
+ * PDM capture: 1-bit PDM at 4.8MHz internally; host sees 48kHz/16-bit PCM.
+ */
+static u32 range_ot36_usage_data[] = {
+ 0x1, 0x2C6, 0xBB80, 0x10, 0x0, 0x0, 0x0, /* HIFI, 48kHz PCM, 16-bit */
+};
+
+/* Entity 0x16 (OT 36) - DataPort selector range (DP2 / TX1) */
+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
+};
+
+/* Entity 0x18 (CS 236) - SampleRateIndex range: index -> 48kHz */
+static u32 range_cs236_sr_data[] = { 0x1, 0xBB80 };
+
+/* Entity 0x19 (OT 236) - Usage range
+ * Optimization capture; clock domain is CS 131 (192 kHz), so PCM rate = 192 kHz.
+ */
+static u32 range_ot236_usage_data[] = {
+ 0x1, 0x334, 0x2EE00, 0x8, 0x0, 0x0, 0x0, /* HIFI, 192kHz, 8-bit */
+};
+
+/* Entity 0x19 (OT 236) - DataPort selector range (DP5 / TX4) */
+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
+};
+
+/*
+ * Control Value Arrays
+ */
+
+/* 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 };
+
+/* Entity 0xF (IT 33) Control Values */
+static int ctrl_it33_micbias_vals[] = { 0x5 }; /* 2.75V mic bias */
+static int ctrl_it33_latency_vals[] = { 0x0 };
+static int ctrl_it33_cluster_vals[] = { 0x1 };
+
+/* Entity 0x13 (XU 36) Control Values */
+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 = 0x4, .cn_list = 0x1, .label = SDCA_CTL_COMMIT_GROUP_MASK_NAME },
+ { .sel = 0x4, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_fun_sdca_ver_vals, .has_fixed = true, .label = SDCA_CTL_FUNCTION_SDCA_VERSION_NAME },
+ { .sel = 0x5, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_fun_type_vals, .has_fixed = true, .label = SDCA_CTL_FUNCTION_TYPE_NAME },
+ { .sel = 0x6, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_fun_man_id_vals, .has_fixed = true, .label = SDCA_CTL_FUNCTION_MANUFACTURER_ID_NAME },
+ { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_fun_id_vals, .has_fixed = true, .label = SDCA_CTL_FUNCTION_ID_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_fun_ver_vals, .has_fixed = true, .label = SDCA_CTL_FUNCTION_VERSION_NAME },
+ { .sel = 0x9, .mode = SDCA_ACCESS_MODE_RO, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_FUNCTION_EXTENSION_ID_NAME },
+ { .sel = 0xA, .mode = SDCA_ACCESS_MODE_RO, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_FUNCTION_EXTENSION_VERSION_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RW1C, .layers = 0x4, .cn_list = 0x1, .is_volatile = true, .label = SDCA_CTL_FUNCTION_STATUS_NAME },
+ { .sel = 0x11, .mode = SDCA_ACCESS_MODE_RW1S, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_FUNCTION_ACTION_NAME },
+ { .sel = 0x2C, .mode = SDCA_ACCESS_MODE_RO, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_DEVICE_MANUFACTURER_ID_NAME },
+ { .sel = 0x2D, .mode = SDCA_ACCESS_MODE_RO, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_DEVICE_PART_ID_NAME },
+ { .sel = 0x2E, .mode = SDCA_ACCESS_MODE_RO, .layers = 0x4, .cn_list = 0x1, .label = SDCA_CTL_DEVICE_VERSION_NAME },
+ { .sel = 0x2F, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1, .values = ctrl_dev_sdca_ver_vals, .has_fixed = true, .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 = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x7, .rows = 0x1, .data = range_it41_usage_data },
+ .label = SDCA_CTL_USAGE_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it41_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it41_cluster_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_it41_cluster_data },
+ .label = SDCA_CTL_CLUSTERINDEX_NAME },
+ { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it41_dp_vals, .has_fixed = true,
+ .range = { .cols = 0x10, .rows = 0x4, .data = range_it41_dp_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_cs41_sr_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_cs41_sr_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .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 = 0x4, .cn_list = 0x1, .label = SDCA_CTL_BYPASS_NAME },
+ { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_xu42_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .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 },
+ .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 },
+ .label = SDCA_CTL_SELECTOR_NAME },
+};
+
+/* Entity 9 (PDE 47) - manages OT 43 and OT 45 */
+static struct sdca_entity *entity_pde47_managed[] = {
+ &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 },
+ .label = SDCA_CTL_REQUESTED_PS_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_NAME },
+ /*
+ * Fake interrupt anchors for the WCD9378 HPH protection interrupts
+ * (INTMASK_1 bits 5/6/7 = HPH OCP/CNP, INTMASK_2 bit 0 = SDCA_8
+ * HPHL_CNP, INTMASK_3 bits 2/3 = SDCA_18/19 HPH surge det). These
+ * SDCA_N positions have no matching ASL controls, so use invented
+ * selectors on PDE47 (which owns the HPH power domain) so the SDCA
+ * framework registers regmap-irq virqs for them.
+ *
+ * With virqs registered, regmap-irq's mask_buf_def includes these
+ * bits and enable_irq() unmasks them in SDW_SCP_SDCA_INTMASK1..3.
+ * When a bit fires the base_handler in sdca_interrupts.c logs the
+ * name ("PDE 47 HPHR OCP" etc.) and regmap-irq acks via INT1..4.
+ *
+ * sel values must be unique within the entity but are otherwise
+ * unused since the base_handler only logs the name. Using values
+ * >0x30 to avoid collision with real SDCA controls.
+ */
+ { .sel = 0x30, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 5, .is_volatile = true,
+ .label = "HPHR OCP" },
+ { .sel = 0x31, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 6, .is_volatile = true,
+ .label = "HPHR CNP" },
+ { .sel = 0x32, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 7, .is_volatile = true,
+ .label = "HPHL OCP" },
+ { .sel = 0x33, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 8, .is_volatile = true,
+ .label = "HPHL CNP" },
+ { .sel = 0x34, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 18, .is_volatile = true,
+ .label = "HPHR SURGE" },
+ { .sel = 0x35, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS,
+ .cn_list = 0x1, .interrupt_position = 19, .is_volatile = true,
+ .label = "HPHL SURGE" },
+};
+
+/* Entity 0xA (OT 43) - Headphone, no controls */
+static struct sdca_entity *entity_ot43_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] };
+
+/* Entity 0xB (OT 45) - Headset, no controls */
+static struct sdca_entity *entity_ot45_sources[] = { &wcd9378_sdca_entities[QSJ_IT41] };
+
+/* Entity 0xC (GE 35) - jack detection group entity */
+
+/*
+ * WORKAROUND: modes 0 (Jack Unplugged) and 1 (Jack Unknown) route SU 45
+ * the same as mode 4 (Headphone) so the DAPM path IT41 -> SU45 -> OT43
+ * stays connected even when the Tambora firmware cannot complete
+ * HP/HS discrimination and leaves DETECTED_MODE at 1. See the
+ * range_ge35_mode_data comment above.
+ */
+static struct sdca_ge_control ge35_mode0_controls[] = {
+ { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, /* SU 45 selector = source 1 (XU 42) */
+};
+
+static struct sdca_ge_control ge35_mode1_controls[] = {
+ { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, /* SU 45 selector = source 1 (XU 42) */
+};
+
+static struct sdca_ge_control ge35_mode3_controls[] = {
+ { .id = 0x7, .sel = 0x1, .cn = 0x0, .val = 0x1 }, /* SU 43 selector = source 1 (XU 42) */
+};
+
+static struct sdca_ge_control ge35_mode4_controls[] = {
+ { .id = 0x8, .sel = 0x1, .cn = 0x0, .val = 0x1 }, /* SU 45 selector = source 1 (XU 42) */
+};
+
+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 = 0x4, .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 = 0x4, .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 },
+};
+
+/* Entity 0xD (IT 131) - optimization stream input */
+static struct sdca_control entity_it131_controls[] = {
+ { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x7, .rows = 0x1, .data = range_it131_usage_data },
+ .label = SDCA_CTL_USAGE_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it131_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it131_cluster_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_it131_cluster_data },
+ .label = SDCA_CTL_CLUSTERINDEX_NAME },
+ { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it131_dp_vals, .has_fixed = true,
+ .range = { .cols = 0x10, .rows = 0x4, .data = range_it131_dp_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_cs131_sr_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_cs131_sr_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_it33_micbias_vals, .has_default = true,
+ .range = { .cols = 0x1, .rows = 0x1, .data = range_it33_micbias_data },
+ .label = SDCA_CTL_MIC_BIAS_NAME },
+ { .sel = 0x4, .mode = SDCA_ACCESS_MODE_RW, .layers = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x7, .rows = 0x1, .data = range_it33_usage_data },
+ .has_reset = true,
+ .label = SDCA_CTL_USAGE_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it33_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_it33_cluster_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_it33_cluster_data },
+ .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 },
+ .label = SDCA_CTL_REQUESTED_PS_NAME },
+ { .sel = 0x10, .mode = SDCA_ACCESS_MODE_RO, .layers = SDCA_ACCESS_LAYER_CLASS, .cn_list = 0x1,
+ .is_volatile = true, .label = SDCA_CTL_ACTUAL_PS_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 = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x1, .rows = 0x2, .data = range_su_sel_data },
+ .label = SDCA_CTL_SELECTOR_NAME },
+};
+
+/* Entity 0x13 (XU 36) Controls */
+static struct sdca_entity *entity_xu36_sources[] = { &wcd9378_sdca_entities[QSJ_SU35] };
+
+static int ctrl_xu36_bypass_vals[] = { 0x1 };
+
+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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_xu36_bypass_vals, .has_default = true, .label = SDCA_CTL_BYPASS_NAME },
+ { .sel = 0x7, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_xu36_id_vals, .has_fixed = true, .label = SDCA_CTL_XU_ID_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_cs36_sr_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_cs36_sr_data },
+ .label = SDCA_CTL_SAMPLERATEINDEX_NAME },
+};
+
+/* Entity 0x16 (OT 36) - mic capture output; signal chain: 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 = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x7, .rows = 0x1, .data = range_ot36_usage_data },
+ .label = SDCA_CTL_USAGE_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_ot36_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+ { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_ot36_dp_vals, .has_fixed = true,
+ .range = { .cols = 0x10, .rows = 0x4, .data = range_ot36_dp_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .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 = 0x4, .cn_list = 0x1,
+ .values = ctrl_cs236_sr_vals, .has_fixed = true,
+ .range = { .cols = 0x2, .rows = 0x1, .data = range_cs236_sr_data },
+ .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 = 0x4, .cn_list = 0x1,
+ .range = { .cols = 0x7, .rows = 0x1, .data = range_ot236_usage_data },
+ .label = SDCA_CTL_USAGE_NAME },
+ { .sel = 0x8, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_ot236_latency_vals, .has_fixed = true, .label = SDCA_CTL_LATENCY_NAME },
+ { .sel = 0x11, .mode = SDCA_ACCESS_MODE_DC, .layers = 0x4, .cn_list = 0x1,
+ .values = ctrl_ot236_dp_vals, .has_fixed = true,
+ .range = { .cols = 0x10, .rows = 0x4, .data = range_ot236_dp_data },
+ .label = SDCA_CTL_DATAPORT_SELECTOR_NAME },
+};
+
+/* Entities Array (see index map at top of file) */
+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 },
+ /* 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 (from QcSimpleJack.asl BUF0).
+ *
+ * 32-bit register addresses with single-byte values, transcribed from the
+ * factory ACPI initialization table.
+ */
+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) — validated spec */
+ { .addr = 0x401800A5, .val = 0xE0 }, /* FLYBACK_VNEG_CTRL_1 */
+ { .addr = 0x401800A8, .val = 0x70 }, /* FLYBACK_VNEG_CTRL_4 */
+ { .addr = 0x4018013A, .val = 0xFC }, /* HPH_NEW_INT_HPH_TIMER1 */
+ { .addr = 0x401801C5, .val = 0x13 }, /* PDM_WD_CTL0 (HPHR) */
+ { .addr = 0x401801C6, .val = 0x13 }, /* PDM_WD_CTL1 (HPHL) */
+ { .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 */
+ /*
+ * DEVICE_DET (0x40180601): re-added. The validation team's
+ * tambora_reg_writes.xlsx does NOT write this register, but the
+ * original ASL init table (QcSimpleJack.asl BUF0 line 131) does.
+ * With DEVICE_DET=0x01 and the firmware in SDCA-mode MBHC reporting,
+ * physical jack insertion should assert SDCA_4 (GE_DETECTED_MODE).
+ * Now that the SDCA interrupt dispatch chain works (post MCLK +
+ * use_domain_irq fixes), test whether this write is what enables the
+ * firmware's MBHC state machine on our part.
+ */
+ { .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 */
+ { .addr = 0x000000f4, .val = 0xff }, /* SWRS_SCP_SDCA_INTRTYPE_1 */
+ { .addr = 0x000000f8, .val = 0x0b }, /* SWRS_SCP_SDCA_INTRTYPE_2 */
+ { .addr = 0x000000fc, .val = 0xff }, /* SWRS_SCP_SDCA_INTRTYPE_3 */
+ /*
+ * INTMASK: only SDCA_4 (GE_DETECTED_MODE) unmasked. Validation dump
+ * enables SDCA_5-8/18/19 too (HPH OCP/CNP/surge) but they cause an
+ * interrupt storm during HPH power-up: the SoundWire alert cascade
+ * fires but regmap_irq_thread never dispatches (INTSTAT_1..4 at
+ * 0x58-0x5B are never read on our platform, possibly due to the
+ * "no bus MCLK, cannot set SDW_SCP_BUS_CLOCK_BASE" bus-init failure
+ * that returns -22 from sdw_initialize_slave). Without a working
+ * dispatch path, unmasking these bits means the cascade never
+ * clears -> MAX_RETRY spam. Keep them masked until the underlying
+ * bus-init/PM issue is resolved.
+ */
+ { .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 */
+ /* ANA_MBHC_MECH must NOT be written here: enabling L_DET_EN before the
+ * machine card registers causes continuous SDCA_4 interrupts that block
+ * 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 */
+};
+
+/*
+ * IT33 (headset mic input terminal) SDCA control for the AMIC2 bias
+ * rail. The framework's write_defaults programs 2V75 into the cache at
+ * probe, but the codec's RESET_FUNCTION_NOW during class boot clears
+ * the hardware value while the cache remains valid, so no PDE-time
+ * write ever reaches the codec. Drop the cache and re-issue the write
+ * from pde_pre_pmu to ensure the bias rail is up before mic capture.
+ */
+#define WCD9378_IT33_ENTITY_ID 0x0F
+#define WCD9378_IT33_MICBIAS_SEL 0x03
+
+/* PDE34 (entity_id=0x10) manages the headset-mic (IT33/AMIC2) power domain */
+#define WCD9378_PDE34_ENTITY_ID 0x10
+
+/* PDE47 (entity_id=0x09) manages the headphone (HPH L/R) power domain */
+#define WCD9378_PDE47_ENTITY_ID 0x09
+
+/*
+ * FU42 vendor Feature Unit — mute (CH1/CH2) and 16-bit Q7.8 dB channel
+ * volume. Writes to the immediate FU42 alias are ignored by the codec;
+ * state changes only latch through the _CN (BIT(14)) alias followed by
+ * SoundWire SCP_COMMIT, and the codec re-mutes FU42 on every PDE47
+ * PS3->PS0 cycle so the sequence has to run from pde_post_pmu.
+ * TODO: model FU42 as a proper SDCA Feature Unit entity once the SDCA
+ * framework supports DUAL-mode writes via the NEXT alias + SCP_COMMIT
+ * and re-applies defaults on PDE power-up.
+ */
+#define WCD9378_FU42_MUTE_CH1_ADDR 0x40c04309
+#define WCD9378_FU42_MUTE_CH2_ADDR 0x40c0430a
+#define WCD9378_FU42_MUTE_UNMUTE 0x00
+#define WCD9378_FU42_CH_VOL_CH1_MSB_ADDR 0x40c06311
+#define WCD9378_FU42_CH_VOL_CH1_LSB_ADDR 0x40c04311
+#define WCD9378_FU42_CH_VOL_CH2_MSB_ADDR 0x40c06312
+#define WCD9378_FU42_CH_VOL_CH2_LSB_ADDR 0x40c04312
+#define WCD9378_FU42_CH_VOL_UNITY_BYTE 0x00
+
+/*
+ * Entity_0 Commit_Group_Count. Must be written before the staged FU42
+ * writes go on the bus so the codec latches the count first. Value 2
+ * groups the FU42 mute and volume writes into a single SCP_COMMIT.
+ */
+#define WCD9378_ENT0_COMMIT_GROUP_COUNT_ADDR 0x40c00008
+#define WCD9378_ENT0_COMMIT_GROUP_COUNT_TWO 0x02
+
+/*
+ * ANA_MBHC_MECH configures the mechanical jack-detect comparator. Value
+ * 0xB5 sets L_DET_EN, MECH_DET_TYP, HPHL_PLUG_TP,
+ * HS_L_DET_PULLUP_COMP_CTRL and SW_HPH_LP_100K_TO_GND — the standard
+ * enable pattern for a 3-pole/4-pole detect.
+ */
+#define WCD9378_ANA_MBHC_MECH_ADDR 0x40180014
+#define WCD9378_ANA_MBHC_MECH_ENABLE 0xB5
+
+/* Vendor-specific SCP register: enable host clock divide-by-2 (bank 1 shadow) */
+#define WCD9378_SCP_HOST_CLK_DIV2_CTL_B1 0xF0
+
+/* Slave ports 1..7 are mapped to master ports via qcom,port-mapping */
+#define WCD9378_SDCA_MAX_PORTS 7
+
+static const char * const wcd9378_sdca_supplies[] = {
+ "vdd-buck", "vdd-rxtx", "vdd-io", "vdd-mic-bias",
+};
+
+static int wcd9378_sdca_sdw_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 = sdca_class_read_prop(slave);
+ if (ret)
+ return ret;
+
+ /* Configure SoundWire slave properties missing from DT/DisCo data */
+ 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;
+
+ if (device_property_read_u32_array(dev, "qcom,port-mapping",
+ &slave->m_port_map[1],
+ WCD9378_SDCA_MAX_PORTS))
+ dev_dbg(dev, "qcom,port-mapping not found, using default\n");
+
+ return 0;
+}
+
+/*
+ * Convert a MICB rail voltage in microvolts to the SDCA MIC_BIAS
+ * usage enum understood by IT-family entities. WCD9378 supports a
+ * fixed set of bias voltages; any unsupported value is rejected so
+ * the DT stays honest.
+ */
+static int wcd9378_sdca_micbias_uv_to_sdca(u32 uv)
+{
+ switch (uv) {
+ case 1800000: return 0x03;
+ case 2500000: return 0x04;
+ case 2750000: return 0x05;
+ default: return -EINVAL;
+ }
+}
+
+static int wcd9378_sdca_hw_init(struct sdw_slave *slave)
+{
+ struct device *dev = &slave->dev;
+ struct gpio_desc *reset;
+ u32 micbias_uv;
+ int ret;
+
+ /*
+ * Compute-mode WCD9378 has no SPMI parent, so the supplies, reset
+ * GPIO and mic-bias voltages all sit on this SoundWire slave's own
+ * DT node (gated by qcom,compute-mode). Own them here.
+ */
+ 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);
+ /* 20us assert per WCD9378 reset sequence */
+ usleep_range(20, 30);
+ gpiod_set_value(reset, 0);
+ /* 20us settle after deassert */
+ usleep_range(20, 30);
+ }
+
+ ret = device_property_read_u32(dev, "qcom,micbias2-microvolt",
+ &micbias_uv);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "qcom,micbias2-microvolt missing\n");
+
+ ret = wcd9378_sdca_micbias_uv_to_sdca(micbias_uv);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "unsupported qcom,micbias2-microvolt %u\n",
+ micbias_uv);
+ ctrl_it33_micbias_vals[0] = ret;
+
+ /*
+ * Vendor SCP bank-1 shadow that clocks the TX PDM path. SCP
+ * registers survive PDE cycles, so the one-time write here is
+ * enough for every subsequent mic capture. The trailing
+ * SCP_COMMIT swaps the bank in — safe to do at hw_init since no
+ * stream is running that could underflow.
+ */
+ 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);
+
+ return 0;
+}
+
+static struct sdca_function_data *wcd9378_sdca_get_function_data(unsigned int *num)
+{
+ *num = 1;
+ return &wcd9378_sdca_data;
+}
+
+static int wcd9378_sdca_pde_pre_pmu(struct sdw_slave *slave,
+ struct regmap *regmap,
+ unsigned int function_id,
+ unsigned int entity_id)
+{
+ struct sdca_class_drv *core = dev_get_drvdata(&slave->dev);
+ int ret;
+
+ if (!core || !core->dev_regmap)
+ return 0;
+
+ if (entity_id == WCD9378_PDE34_ENTITY_ID) {
+ unsigned int mic_bias = SDW_SDCA_CTL(function_id,
+ WCD9378_IT33_ENTITY_ID,
+ WCD9378_IT33_MICBIAS_SEL, 0);
+
+ regcache_drop_region(regmap, mic_bias, mic_bias);
+ ret = regmap_write(regmap, mic_bias, ctrl_it33_micbias_vals[0]);
+ if (ret)
+ dev_err(&slave->dev, "IT33 MIC_BIAS: %d\n", ret);
+ } else if (entity_id == WCD9378_PDE47_ENTITY_ID) {
+ /* Announce commit-group count for the FU42 writes in pde_post_pmu. */
+ ret = regmap_write(core->dev_regmap,
+ WCD9378_ENT0_COMMIT_GROUP_COUNT_ADDR,
+ WCD9378_ENT0_COMMIT_GROUP_COUNT_TWO);
+ if (ret)
+ dev_err(&slave->dev, "Commit_Group_Count: %d\n", ret);
+
+ /* Let the master's WR FIFO drain before PDE47_REQ_PS=0. */
+ usleep_range(2000, 2500);
+ }
+
+ return 0;
+}
+
+static int wcd9378_sdca_pde_post_pmu(struct sdw_slave *slave,
+ struct regmap *regmap,
+ unsigned int function_id,
+ unsigned int entity_id)
+{
+ struct sdca_class_drv *core = dev_get_drvdata(&slave->dev);
+ int ret;
+
+ if (entity_id != WCD9378_PDE47_ENTITY_ID)
+ return 0;
+
+ if (!core || !core->dev_regmap)
+ return 0;
+
+ /* Wait for the SEQR sequencer to finish the HPH bring-up before
+ * re-applying FU42 state; the codec re-mutes FU42 on every PDE47
+ * PS3->PS0 cycle so the sequence has to wait for the transition. */
+ msleep(30);
+
+ /*
+ * Unmute FU42 CH1/CH2 and stage 0 dB volume via the _CN (NEXT) alias
+ * so all four staged writes activate together on the SCP_COMMIT
+ * below. Writes go to the vendor slave regmap because FU42
+ * addresses are outside the SDCA class regmap's routing.
+ */
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_MUTE_CH1_ADDR,
+ WCD9378_FU42_MUTE_UNMUTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 MUTE_CH1: %d\n", ret);
+
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_MUTE_CH2_ADDR,
+ WCD9378_FU42_MUTE_UNMUTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 MUTE_CH2: %d\n", ret);
+
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_CH_VOL_CH1_MSB_ADDR,
+ WCD9378_FU42_CH_VOL_UNITY_BYTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 CH1_MSB: %d\n", ret);
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_CH_VOL_CH1_LSB_ADDR,
+ WCD9378_FU42_CH_VOL_UNITY_BYTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 CH1_LSB: %d\n", ret);
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_CH_VOL_CH2_MSB_ADDR,
+ WCD9378_FU42_CH_VOL_UNITY_BYTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 CH2_MSB: %d\n", ret);
+ ret = regmap_write(core->dev_regmap, WCD9378_FU42_CH_VOL_CH2_LSB_ADDR,
+ WCD9378_FU42_CH_VOL_UNITY_BYTE);
+ if (ret)
+ dev_err(&slave->dev, "FU42 CH2_LSB: %d\n", ret);
+
+ ret = sdw_write_no_pm(slave, SDW_SCP_COMMIT, 0x02);
+ if (ret)
+ dev_err(&slave->dev, "FU42 SCP_COMMIT: %d\n", ret);
+
+ return 0;
+}
+
+static const struct sdca_class_hw_ops wcd9378_sdca_hw_ops = {
+ .hw_init = wcd9378_sdca_hw_init,
+ .get_function_data = wcd9378_sdca_get_function_data,
+ .pde_pre_pmu = wcd9378_sdca_pde_pre_pmu,
+ .pde_post_pmu = wcd9378_sdca_pde_post_pmu,
+};
+
+static int wcd9378_sdca_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct device *dev = &slave->dev;
+
+ /*
+ * The WCD9378 SoundWire class ID sdw20217011000 matches in both
+ * mobile and SDCA / compute modes. qcom,compute-mode on the slave
+ * DT node distinguishes them: this driver serves compute-mode
+ * peripherals only, so bail out with -ENODEV for mobile-mode
+ * slaves (a mobile-mode driver will pick them up).
+ */
+ if (!device_property_read_bool(dev, "qcom,compute-mode"))
+ return -ENODEV;
+
+ return sdca_class_probe(slave, &wcd9378_sdca_hw_ops);
+}
+
+static const struct sdw_slave_ops wcd9378_sdca_sdw_ops = {
+ .read_prop = wcd9378_sdca_sdw_read_prop,
+};
+
+static const struct sdw_device_id wcd9378_sdca_sdw_id[] = {
+ SDW_SLAVE_ENTRY(0x0217, 0x0110, 0),
+ {}
+};
+MODULE_DEVICE_TABLE(sdw, wcd9378_sdca_sdw_id);
+
+static struct sdw_driver wcd9378_sdca_sdw_driver = {
+ .driver = {
+ .name = "wcd9378-sdca",
+ .pm = pm_ptr(&sdca_class_pm_ops),
+ },
+ .probe = wcd9378_sdca_sdw_probe,
+ .id_table = wcd9378_sdca_sdw_id,
+ .ops = &wcd9378_sdca_sdw_ops,
+};
+module_sdw_driver(wcd9378_sdca_sdw_driver);
+
+MODULE_DESCRIPTION("Qualcomm SimpleJack (Tambora/WCD9378) SDCA codec");
+MODULE_AUTHOR("Qualcomm Technologies, Inc.");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS("SND_SOC_SDCA_CLASS");
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-07-22 23:42 ` [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
@ 2026-07-23 9:14 ` Konrad Dybcio
2026-07-23 13:28 ` Srinivas Kandagatla
0 siblings, 1 reply; 15+ messages in thread
From: Konrad Dybcio @ 2026-07-23 9:14 UTC (permalink / raw)
To: Srinivas Kandagatla, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Charles Keepax, Maciej Strozek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel
On 7/23/26 1:42 AM, Srinivas Kandagatla wrote:
> Describe the WCD9378 SDCA peripheral node (compatible sdw20217011000)
> driven by the wcd9378-sdca codec driver for headphone playback, headset
> mic capture and jack detection via the SimpleJack SDCA function type.
>
> WCD9378 codec can be wired up in 2 different modes.
> "mobile mode" on phone/tablet SoCs is enumerated as tx and rx device,
> each of which has dedicated control and data lines.
> "compute mode" on compute platforms such as Glymur is enumerated as
> single standard MIPI SDCA class device.
Does this require a different hardware wiring, or is that just a
difference for the driver? If the latter, can we choose whichever
mode we want (i.e. use compute mode on the Fairphone or use mobile
mode on a laptop, depending on what the OS supports)?
Konrad
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
` (7 preceding siblings ...)
2026-07-22 23:42 ` [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
@ 2026-07-23 10:17 ` Charles Keepax
2026-07-23 13:24 ` Srinivas Kandagatla
8 siblings, 1 reply; 15+ messages in thread
From: Charles Keepax @ 2026-07-23 10:17 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Maciej Strozek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Srinivas Kandagatla, Bard Liao, Pierre-Louis Bossart,
Richard Fitzgerald, Jorijn van der Graaf, linux-sound,
linux-arm-msm, devicetree, patches, linux-kernel
On Thu, Jul 23, 2026 at 12:42:10AM +0100, Srinivas Kandagatla wrote:
> At Linux Plumbers 2025 in the Devicetree MC I raised the "SDCA and the
> classic ACPI-DT problem" [1]: MIPI SDCA is built around ACPI/DisCo
> firmware descriptors, and the in-tree ASoC SDCA framework
> (sound/soc/sdca/) enumerates functions, entities, controls and PDEs by
> walking those tables. On ARM64 DT platforms there is no DisCo, so the
> framework is unreachable and sdca codecs will not be able to use the
> generic sdca drivers and duplicating.
>
> The direction from that discussion was to let DT platforms reuse the
> same class/function auxiliary driver plumbing and have codec drivers
> supply the small amount of static function/entity metadata that DisCo
> would otherwise carry, plus hooks for device-specific bring-up. This
> series is a first cut at that, with the Qualcomm WCD9378 ("Tambora")
> SDCA codec on the Glymur CRD as the first consumer. Static table that in
> part of this codec is generated from acpi tables.
Fascinating, a shame I missed the discussion. Main question
I have is what was the reasoning behind using static tables
rather than just putting the data in device tree? All the core
code uses the generic firmware parsing function so should be
perfectly capable of parsing the data out of device tree. The
only bit that is missing is really the sdca_lookup_functions
bit you ifdef out, but updating that to also support DT should
be pretty simple.
Looking through your presentation (thanks for linking that),
am I to guess this was mostly a device tree people didn't like
the stuff SDCA contains problem? I do see that some of the SDCA
stuff isn't an exact match for how Linux has traditionally liked
to handle DT but also really not sure I see any benefit to DT
and ACPI support being different.
I guess there is also a slightly risk the standard committee
eventually get round to device tree and adds support differently,
but an existing large user would be an excellent steer on them.
> * 1: hw_params fix for OT DAI upstream Input Terminals (found while
> bringing up Tambora capture).
Thanks that is indeed something that needs some work.
> * 2: allow sound/soc/sdca/ to build without ACPI.
> * 3: turn sdca_class into an exported library and introduce
> struct sdca_class_hw_ops with hw_init + get_function_data, so a
> codec driver registers its own sdw_driver, supplies static function
> descriptors when there is no DisCo node, and owns regulators/reset
> at probe time. The built-in class_sdw_driver stays for
> pure-generic parts (hw_ops = NULL).
Yeah, we have not really implemented any XU type stuff yet. Some
discussion about whether we want to proceed with adding custom
callbacks into the class driver or just call the core functions
from the end driver. The later was really the original intention
although I do grant that there has ended up being slightly more
boiler plate in the class driver than would have been ideal.
> * 4: pde_pre_pmu / pde_post_pmu hooks in hw_ops so vendor drivers
> can stage/commit register writes around PDE PS0 transitions.
> * 5: of_xlate_dai_name that resolves sound-dai cells by entity index
> rather than positional DAI order.
> * 6: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in the class function
> driver.
We can probably just merge the simple jack support as is. I will
have a look.
Going to likely take me a few days to work my way through things,
but please bear with me. Great to see the SDCA stuff getting some
proper usage outside of us :-)
Thanks,
Charles
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver
2026-07-22 23:42 ` [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver Srinivas Kandagatla
@ 2026-07-23 10:55 ` Charles Keepax
0 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2026-07-23 10:55 UTC (permalink / raw)
To: Srinivas Kandagatla
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Maciej Strozek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Srinivas Kandagatla, Bard Liao, Pierre-Louis Bossart,
Richard Fitzgerald, Jorijn van der Graaf, linux-sound,
linux-arm-msm, devicetree, patches, linux-kernel
On Thu, Jul 23, 2026 at 12:42:16AM +0100, Srinivas Kandagatla wrote:
> Add SDCA_FUNCTION_TYPE_SIMPLE_JACK to the class function driver's
> auxiliary id table so a SimpleJack auxdev is bound and enumerated as
> an ASoC component. Wire set_jack for this function type so the machine
> driver can register an ASoC jack against it.
>
> Assisted-by: Claude:claude-opus-4-7
> Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com>
> ---
Yeah this patch looks fine, we can merge this one now, whilst we
work through the rest.
Reviewed-by: Charles Keepax <ckeepax@opensource.cirrus.com>
Thanks,
Charles
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec
2026-07-23 10:17 ` [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Charles Keepax
@ 2026-07-23 13:24 ` Srinivas Kandagatla
0 siblings, 0 replies; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-23 13:24 UTC (permalink / raw)
To: Charles Keepax
Cc: Mark Brown, Liam Girdwood, Jaroslav Kysela, Takashi Iwai,
Maciej Strozek, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Srinivas Kandagatla, Bard Liao, Pierre-Louis Bossart,
Richard Fitzgerald, Jorijn van der Graaf, linux-sound,
linux-arm-msm, devicetree, patches, linux-kernel
Thanks Charles, for the feedback.
On 7/23/26 11:17 AM, Charles Keepax wrote:
> On Thu, Jul 23, 2026 at 12:42:10AM +0100, Srinivas Kandagatla wrote:
>> At Linux Plumbers 2025 in the Devicetree MC I raised the "SDCA and the
>> classic ACPI-DT problem" [1]: MIPI SDCA is built around ACPI/DisCo
>> firmware descriptors, and the in-tree ASoC SDCA framework
>> (sound/soc/sdca/) enumerates functions, entities, controls and PDEs by
>> walking those tables. On ARM64 DT platforms there is no DisCo, so the
>> framework is unreachable and sdca codecs will not be able to use the
>> generic sdca drivers and duplicating.
>>
>> The direction from that discussion was to let DT platforms reuse the
>> same class/function auxiliary driver plumbing and have codec drivers
>> supply the small amount of static function/entity metadata that DisCo
>> would otherwise carry, plus hooks for device-specific bring-up. This
>> series is a first cut at that, with the Qualcomm WCD9378 ("Tambora")
>> SDCA codec on the Glymur CRD as the first consumer. Static table that in
>> part of this codec is generated from acpi tables.
>
> Fascinating, a shame I missed the discussion. Main question
> I have is what was the reasoning behind using static tables
> rather than just putting the data in device tree? All the core
> code uses the generic firmware parsing function so should be
> perfectly capable of parsing the data out of device tree. The
> only bit that is missing is really the sdca_lookup_functions
> bit you ifdef out, but updating that to also support DT should
> be pretty simple.
Two reasons
1. This is laptop platform primarily works on windows with proper acpi
tables, These tables are directly derived for ACPI asl tables in
windows. Ideally I want to use it the way it is supposed to work.
2. replicating full Disco specs in DT is going to be challenge, Because
DT describes hardware but not necessarily at a level of register init
tables or to an extent where class drivers need it.
>
> Looking through your presentation (thanks for linking that),
> am I to guess this was mostly a device tree people didn't like
> the stuff SDCA contains problem? I do see that some of the SDCA
> stuff isn't an exact match for how Linux has traditionally liked
> to handle DT but also really not sure I see any benefit to DT
> and ACPI support being different.
I think there is fine line on what can go into DT, some of the hardware
init tables are big No No i guess, Power and other ACPI things are not
something DT does.
I will let DT maintainers comment on this.
>
> I guess there is also a slightly risk the standard committee
> eventually get round to device tree and adds support differently,
> but an existing large user would be an excellent steer on them.
>
I think whole Disco has not considered DT at all.
>> * 1: hw_params fix for OT DAI upstream Input Terminals (found while
>> bringing up Tambora capture).
>
> Thanks that is indeed something that needs some work.
>
>> * 2: allow sound/soc/sdca/ to build without ACPI.
>> * 3: turn sdca_class into an exported library and introduce
>> struct sdca_class_hw_ops with hw_init + get_function_data, so a
>> codec driver registers its own sdw_driver, supplies static function
>> descriptors when there is no DisCo node, and owns regulators/reset
>> at probe time. The built-in class_sdw_driver stays for
>> pure-generic parts (hw_ops = NULL).
>
> Yeah, we have not really implemented any XU type stuff yet. Some
> discussion about whether we want to proceed with adding custom
> callbacks into the class driver or just call the core functions
> from the end driver. The later was really the original intention
> although I do grant that there has ended up being slightly more
> boiler plate in the class driver than would have been ideal.
XU should be opaque to class drivers.
>
>> * 4: pde_pre_pmu / pde_post_pmu hooks in hw_ops so vendor drivers
>> can stage/commit register writes around PDE PS0 transitions.
>> * 5: of_xlate_dai_name that resolves sound-dai cells by entity index
>> rather than positional DAI order.
>> * 6: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in the class function
>> driver.
>
> We can probably just merge the simple jack support as is. I will
> have a look.
>
> Going to likely take me a few days to work my way through things,
> but please bear with me. Great to see the SDCA stuff getting some
> proper usage outside of us :-)
Thanks for the positive note.
Plan is to get all the Qualcomm SDCA codecs to use existing sdca drivers
than writing duplicate code.
We do have couple of more SDCA drivers, the one I could include in next
version is a Simple Amp driver which has pretty much in similar lines
WCD9378 codec, everything leveraged from generic drivers.
--srini
>
> Thanks,
> Charles
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-07-23 9:14 ` Konrad Dybcio
@ 2026-07-23 13:28 ` Srinivas Kandagatla
2026-07-23 13:31 ` Konrad Dybcio
0 siblings, 1 reply; 15+ messages in thread
From: Srinivas Kandagatla @ 2026-07-23 13:28 UTC (permalink / raw)
To: Konrad Dybcio, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Charles Keepax, Maciej Strozek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel
On 7/23/26 10:14 AM, Konrad Dybcio wrote:
> On 7/23/26 1:42 AM, Srinivas Kandagatla wrote:
>> Describe the WCD9378 SDCA peripheral node (compatible sdw20217011000)
>> driven by the wcd9378-sdca codec driver for headphone playback, headset
>> mic capture and jack detection via the SimpleJack SDCA function type.
>>
>> WCD9378 codec can be wired up in 2 different modes.
>> "mobile mode" on phone/tablet SoCs is enumerated as tx and rx device,
>> each of which has dedicated control and data lines.
>> "compute mode" on compute platforms such as Glymur is enumerated as
>> single standard MIPI SDCA class device.
>
> Does this require a different hardware wiring, or is that just a
> difference for the driver? If the latter, can we choose whichever
> mode we want (i.e. use compute mode on the Fairphone or use mobile
> mode on a laptop, depending on what the OS supports)?
AFAIU, Its not possible to change it because its fused and this also
brings in some physical bus level changes to soundwire aswell.
Ex: on compute we have one control lane for 2 soundwire masters driving
4 data lanes. on mobile every soundwire master has dedicated control and
data lanes.. Bottom line is its not possible.
However w.r.t to driver given that is same IP, I wanted to explore if we
can have a single driver for both.
--srini
>
> Konrad
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec
2026-07-23 13:28 ` Srinivas Kandagatla
@ 2026-07-23 13:31 ` Konrad Dybcio
0 siblings, 0 replies; 15+ messages in thread
From: Konrad Dybcio @ 2026-07-23 13:31 UTC (permalink / raw)
To: Srinivas Kandagatla, Mark Brown, Liam Girdwood, Jaroslav Kysela,
Takashi Iwai, Charles Keepax, Maciej Strozek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Srinivas Kandagatla
Cc: Bard Liao, Pierre-Louis Bossart, Richard Fitzgerald,
Jorijn van der Graaf, linux-sound, linux-arm-msm, devicetree,
patches, linux-kernel
On 7/23/26 3:28 PM, Srinivas Kandagatla wrote:
> On 7/23/26 10:14 AM, Konrad Dybcio wrote:
>> On 7/23/26 1:42 AM, Srinivas Kandagatla wrote:
>>> Describe the WCD9378 SDCA peripheral node (compatible sdw20217011000)
>>> driven by the wcd9378-sdca codec driver for headphone playback, headset
>>> mic capture and jack detection via the SimpleJack SDCA function type.
>>>
>>> WCD9378 codec can be wired up in 2 different modes.
>>> "mobile mode" on phone/tablet SoCs is enumerated as tx and rx device,
>>> each of which has dedicated control and data lines.
>>> "compute mode" on compute platforms such as Glymur is enumerated as
>>> single standard MIPI SDCA class device.
>>
>> Does this require a different hardware wiring, or is that just a
>> difference for the driver? If the latter, can we choose whichever
>> mode we want (i.e. use compute mode on the Fairphone or use mobile
>> mode on a laptop, depending on what the OS supports)?
>
> AFAIU, Its not possible to change it because its fused and this also
> brings in some physical bus level changes to soundwire aswell.
> Ex: on compute we have one control lane for 2 soundwire masters driving
> 4 data lanes. on mobile every soundwire master has dedicated control and
> data lanes.. Bottom line is its not possible.
Ah, sad. Thanks for explaining
Konrad
> However w.r.t to driver given that is same IP, I wanted to explore if we
> can have a single driver for both.
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-07-23 13:32 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22 23:42 [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index Srinivas Kandagatla
2026-07-22 23:42 ` [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver Srinivas Kandagatla
2026-07-23 10:55 ` Charles Keepax
2026-07-22 23:42 ` [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec Srinivas Kandagatla
2026-07-23 9:14 ` Konrad Dybcio
2026-07-23 13:28 ` Srinivas Kandagatla
2026-07-23 13:31 ` Konrad Dybcio
2026-07-22 23:42 ` [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla
2026-07-23 10:17 ` [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Charles Keepax
2026-07-23 13:24 ` Srinivas Kandagatla
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox