* [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; 20+ 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] 20+ 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:54 ` sashiko-bot 2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla ` (7 subsequent siblings) 8 siblings, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI 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:54 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:54 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: - [High] The recursive graph traversal in `set_input_terminal_sources()` lacks cycle detection, allowing malformed firmware topologies to cause infinite recursion and a kernel stack overflow. - [High] Output Terminal format parameters are blindly propagated to all upstream Input Terminals, breaking topologies with intermediate processing units (e.g., Sample Rate Converters or Mixers). - [High] Unconditional traversal of the static source graph configures inactive alternative paths and clobbers shared hardware nodes without mutual exclusion. -- --- Patch [1]: [RFC PATCH 1/8] ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI --- commit e1c19ca1fbb78240fd7df2f80b2ad3f08d85052a Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: hw_params: program upstream Input Terminals for OT DAI 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. > diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c > index b4dedba719dc6..ac688cd629706 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]; [Severity: High] Does this code inadvertently configure inactive alternative paths? The loop iterates over all statically defined sources in the firmware graph, which might ignore active ALSA/DAPM routing state like MUX selections. Additionally, if an IT is actively shared between multiple OTs, could starting a stream on the second OT dynamically overwrite the active IT's registers without locking and corrupt the first stream? > + > + 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); [Severity: High] Can this break topologies that use intermediate processing units like a Sample Rate Converter or channel mixer? This propagates the Output Terminal's format parameters (channels, rate, width) directly to all upstream Input Terminals. If the IT does not natively support the exact same hardware parameters as the OT, won't set_usage() or set_cluster() fail with -EINVAL? > + if (ret) > + return ret; > + } > + > + ret = set_input_terminal_sources(dev, regmap, function, src, > + channels, rate, width); [Severity: High] Is it possible for a malformed firmware topology to cause an infinite loop here? The recursive graph traversal in set_input_terminal_sources() doesn't appear to have cycle detection. If entity->sources contains a cycle, could this lead to a kernel stack overflow? > + if (ret) > + return ret; > + } > + > + return 0; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=1 ^ permalink raw reply [flat|nested] 20+ 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:59 ` sashiko-bot 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, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI 2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla @ 2026-07-22 23:59 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:59 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [High] Unbounded recursion in `set_input_terminal_sources()` can lead to a kernel stack overflow if the firmware-provided SDCA topology graph contains a cycle. - [Medium] The commit message and `SND_SOC_SDCA` Kconfig help text claim non-ACPI SDCA topology is supplied via `sdca_class_hw_ops.get_function_data`, but this API does not exist. -- --- Patch [2]: [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI --- commit b04522a00403ca00a5d4ee2768eab7c24cb31da7 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: allow building without ACPI This commit drops the ACPI dependency for the SDCA class driver to allow building on non-ACPI platforms. It also guards ACPI-parsing implementations with IS_ENABLED(CONFIG_ACPI), providing empty stubs when ACPI is disabled. > The SDCA class driver is useful on non-ACPI platforms where the > topology is supplied statically via sdca_class_hw_ops.get_function_data. [Severity: Medium] Does this API actually exist in the kernel tree? Searching for sdca_class_hw_ops or get_function_data yields no definitions. Is there a missing patch that introduces this mechanism for codec drivers to supply topology data on non-ACPI platforms? > diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig > index 4c0dcb9ff3b94..dc4cba1e3e0ed 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. [Severity: Medium] Similar to the commit message, this help text points developers to sdca_class_hw_ops.get_function_data. Since this interface doesn't seem to be defined anywhere, should this documentation be updated to reflect the actual mechanism used by codec drivers? While reviewing this code, I also noticed a potential regression in the topology parsing logic: sound/soc/sdca/sdca_asoc.c:set_input_terminal_sources() { ... ret = set_input_terminal_sources(dev, regmap, function, src, channels, rate, width); ... } [Severity: High] Can this recursive call cause a kernel stack overflow if the firmware-provided SDCA topology graph contains a cycle? The graph of SDCA entities is populated directly from external firmware data, such as ACPI _DSD. The traversal appears to lack cycle detection (like a visited bitmask) or a recursion depth limit. If a malformed or malicious firmware blob provides a cyclic connection, such as Entity A connecting to Entity B and back to Entity A, would this function recurse infinitely and exhaust the stack during device probing? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=2 ^ permalink raw reply [flat|nested] 20+ 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:58 ` sashiko-bot 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, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms 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:58 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:58 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: New issues: - [High] Pointer aliasing in `class_function_probe()` overwrites the dynamically registered function descriptor, breaking devices with multiple functions of the same type. - [High] `sdca_class_probe()` unconditionally claims the slave device's driver data, preventing caller codec drivers from maintaining their own state and causing type confusion crashes. - [High] Exported `sdca_class_probe()` queues asynchronous work but provides no cleanup mechanism, leading to a Use-After-Free. - [High] The `duplicate` flag is not preserved when injecting static function descriptors, breaking ALSA controls for duplicate functions. Pre-existing issues: - [High] `sdca_class_read_prop()` ignores memory allocation failures from `sdw_slave_read_prop()`, leading to invalid property accesses. -- --- Patch [3]: [RFC PATCH 3/8] ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms --- commit bcbc6a4337e2eb8e1a4a49f6313a6ae24f542dc0 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: expose class helpers with hw_ops for non-DisCo platforms This patch modifies the SDCA class driver to expose helpers for non-DisCo (DT/ARM) platforms. It introduces a hw_ops structure allowing codec drivers to supply static function data and initialization routines when ACPI tables are absent. > diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c > --- 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) [Severity: High] This isn't a bug introduced by this patch, but since this function is now being exported as a public API, does this safely handle allocation failures from sdw_slave_read_prop()? If sdw_slave_read_prop() encounters memory allocation failures, it returns -ENOMEM. Because this function ignores the return value, the SoundWire core might attempt to access partially initialized or NULL property structures later on. Should we propagate the return value? [ ... ] > @@ -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); [Severity: High] When codec drivers call this newly exported sdca_class_probe() helper, will they still be able to maintain their own private state? Because this helper unconditionally assigns its internal sdca_class_drv struct to the device's driver data, any calling driver that later sets its own drvdata will overwrite it. If the codec driver sets its own drvdata, could this cause type confusion when the exported PM callbacks execute? sound/soc/sdca/sdca_class.c:class_runtime_resume() { struct sdca_class_drv *drv = dev_get_drvdata(dev); ... ret = sdw_slave_wait_for_init(drv->sdw, ...); } This cast would result in an invalid pointer dereference. Could the allocated drv pointer be returned to the caller, or embedded in a public structure, so codec drivers can safely use this API? > + /* > + * 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; [Severity: High] Does this structure copy miss the duplicate flag from the static descriptor? In SDCA, the duplicate flag is necessary for ensuring ALSA controls for duplicate functions (like Left and Right SmartAmps) are properly prefixed with their address. Without this flag, won't the dynamically registered functions have duplicate = false, leading to ALSA control name collisions? [ ... ] > @@ -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"); [Severity: High] Could exposing this probe function lead to a Use-After-Free if a caller subsequently unbinds or fails probe? The sdca_class_probe() function queues an asynchronous boot_work, but its teardown (cancel_work_sync) is only present in the unexported class_sdw_remove(). The work lifecycle doesn't appear to be managed by devres. If a codec driver uses this helper and later unbinds, devres will free the drv structure while the work remains queued, leading to a dereference of the freed drv pointer when the work eventually executes. Should the work cancellation be tied to devres or exposed via an un-probe helper? [ ... ] > diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c > --- 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) { [ ... ] > + 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; [Severity: High] Can this structure assignment corrupt the dynamically registered function descriptor? Earlier in this function, drv->function is set as an alias to &sdev->function: drv->function = &sdev->function; When *drv->function = fdata[i] executes, it completely overwrites sdev->function with the static template, which destroys the original desc pointer. The next line, drv->function->desc = sdev->function.desc, acts as a self-assignment and fails to restore the original dynamic descriptor. Consequently, the dynamically injected SoundWire address is lost. If a device has multiple functions of the same type (like dual SmartAmps), they will both blindly use the same address from the first template. Could this cause SoundWire control collisions? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=3 ^ permalink raw reply [flat|nested] 20+ 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:56 ` sashiko-bot 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, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops 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:56 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:56 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Errors returned from the new `pde_pre_pmu` and `pde_post_pmu` hooks are incorrectly swallowed or shadowed, allowing DAPM transitions to proceed with invalid hardware state. -- --- Patch [4]: [RFC PATCH 4/8] ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops --- commit 9ebbdf1f4165266d78edf14f3e10a54fa79025c5 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: add PDE pre/post-pmu hooks to hw_ops This patch adds pde_pre_pmu and pde_post_pmu callbacks to struct sdca_class_hw_ops. These hooks allow codec drivers to stage state and commit pending writes before and after DAPM PDE power-domain transitions. > diff --git a/sound/soc/sdca/sdca_asoc.c b/sound/soc/sdca/sdca_asoc.c > index ac688cd629706..795498b9a314b 100644 > --- a/sound/soc/sdca/sdca_asoc.c > +++ b/sound/soc/sdca/sdca_asoc.c [ ... ] > @@ -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; [Severity: Medium] Should this return ret instead of 0 when pde_pre_pmu() fails? Returning 0 allows the DAPM power sequence to proceed even though the prerequisite state might be invalid. > 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), [Severity: Medium] Does this overwrite the error from pde_post_pmu()? If pde_post_pmu() fails, ret is logged in the warning above, but then immediately overwritten by the return value of sdca_asoc_pde_poll_actual_ps(), losing the original failure context. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=4 ^ permalink raw reply [flat|nested] 20+ 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:57 ` sashiko-bot 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, 1 reply; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index 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:57 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:57 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Missing validation of `args->args_count` before accessing `args->args[0]` leads to reading uninitialized stack memory, causing arbitrary DAI matching or information leaks, and breaking the fallback for zero-cell sound-dai references. -- --- Patch [5]: [RFC PATCH 5/8] ASoC: SDCA: class_function: xlate sound-dai cell by entity index --- commit 1d17bfb5417d13a6da7e95f412ebc5f6ec81d329 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: class_function: xlate sound-dai cell by entity index This commit registers a custom of_xlate_dai_name for SDCA functions. It maps a "sound-dai" phandle cell to a DAI by matching the cell value against the SDCA entity array index instead of relying on default positional mapping. > diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c > index 905c96ed6172f..b0fbe243d732c 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, [ ... ] > +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]; [Severity: Medium] Could this read uninitialized stack memory if args->args_count is 0? When a devicetree defines #sound-dai-cells = <0> for the SDCA function node, the core of_parse_phandle_with_args() successfully returns with args_count = 0. Because the of_phandle_args structure is often allocated on the stack in callers like snd_soc_of_get_dlc(), the args->args array is left uninitialized. Does this code need to verify args->args_count >= 1 before accessing args->args[0] to prevent matching against random stack values? If args_count is 0, should it return -ENOTSUPP to allow ALSA's default 0-cell handling to proceed instead of returning -EINVAL? > + int i; > + > + for (i = 0; i < function->num_entities - 1; i++) { [ ... ] > + 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); [Severity: Medium] If target is populated from an uninitialized args->args[0] due to a 0-cell reference, does printing it here leak kernel stack memory to the log? > + return -EINVAL; > +} -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=5 ^ permalink raw reply [flat|nested] 20+ 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-22 23:56 ` sashiko-bot 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, 2 replies; 20+ 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] 20+ 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-22 23:56 ` sashiko-bot 2026-07-23 10:55 ` Charles Keepax 1 sibling, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:56 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: robh, devicetree, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] Missing string mapping for SDCA_FUNCTION_TYPE_SIMPLE_JACK in get_sdca_function_name() causes ACPI enumeration failure. -- --- Patch [6]: [RFC PATCH 6/8] ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver --- commit 9826e21715fdea11e3c55eac5f9ed509d841b1d2 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: SDCA: register SDCA_FUNCTION_TYPE_SIMPLE_JACK in class function driver This commit adds 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. It also wires up set_jack for this function type so the machine driver can register an ASoC jack against it. > diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c > --- a/sound/soc/sdca/sdca_class_function.c > +++ b/sound/soc/sdca/sdca_class_function.c [ ... ] > @@ -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, > + }, [Severity: High] Does this code also require a string mapping for SDCA_FUNCTION_TYPE_SIMPLE_JACK in get_sdca_function_name()? Looking at sound/soc/sdca/sdca_functions.c, the switch statement in get_sdca_function_name() lacks a case for SDCA_FUNCTION_TYPE_SIMPLE_JACK, causing it to return NULL. When ACPI enumeration calls find_sdca_function() and encounters this function type, the NULL return value causes it to fail: sound/soc/sdca/sdca_functions.c:find_sdca_function() { ... function_name = get_sdca_function_name(function_type); if (!function_name) { dev_err(dev, "invalid SDCA function type %d\n", function_type); return -EINVAL; } ... } Because acpi_dev_for_each_child() stops iterating when a callback returns a non-zero value, doesn't this error prevent the SimpleJack auxiliary device from being registered and silently abort the enumeration of any subsequent SDCA functions on the same device? > {}, > }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=6 ^ permalink raw reply [flat|nested] 20+ 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-22 23:56 ` sashiko-bot @ 2026-07-23 10:55 ` Charles Keepax 1 sibling, 0 replies; 20+ 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] 20+ 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-22 23:51 ` sashiko-bot 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, 2 replies; 20+ 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] 20+ 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-22 23:51 ` sashiko-bot 2026-07-23 9:14 ` Konrad Dybcio 1 sibling, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-22 23:51 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] The unit-address in the example node name `audio-codec@3,0` does not match its `reg` property `<0 3>`. -- --- Patch [7]: [RFC PATCH 7/8] dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec --- commit 5fe69f3b063a8e89646196eb389830ac9c6473c6 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> dt-bindings: sound: qcom: add Tambora WCD9378 SDCA codec This commit introduces the device tree binding for the Qualcomm WCD9378 SoundWire slave devices. It defines properties for both mobile mode and SDCA compute mode configurations. > 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 0000000000000..2ed4ad92958ed > --- /dev/null > +++ b/Documentation/devicetree/bindings/sound/qcom,wcd9378-sdw.yaml [ ... ] > + # 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>; [Severity: Low] Should the unit address in the node name be audio-codec@0,3 to properly match the reg property <0 3>? The device tree compiler will likely emit a unit_address_vs_reg warning during make dt_binding_check when parsing this example. > + > + qcom,compute-mode; > + qcom,port-mapping = <2 2 3 4 5 6 7 8>; [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=7 ^ permalink raw reply [flat|nested] 20+ 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-22 23:51 ` sashiko-bot @ 2026-07-23 9:14 ` Konrad Dybcio 1 sibling, 0 replies; 20+ 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] 20+ 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 0:03 ` sashiko-bot 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; 20+ 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] 20+ messages in thread
* Re: [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec 2026-07-22 23:42 ` [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) " Srinivas Kandagatla @ 2026-07-23 0:03 ` sashiko-bot 0 siblings, 0 replies; 20+ messages in thread From: sashiko-bot @ 2026-07-23 0:03 UTC (permalink / raw) To: Srinivas Kandagatla; +Cc: conor+dt, devicetree, robh Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] Hardware reset causes immediate SoundWire I/O failure due to lost enumeration. - [High] Mismatch between declared SoundWire sink ports and WCD9378_SDCA_MAX_PORTS leaves DP8 unmapped. - [High] Missing `.remove` callback leaves the asynchronous `boot_work` running or queued, leading to a Use-After-Free. - [Medium] Device-specific configuration parsed from DT mutates a shared static global array. - [Medium] The register staging and commit sequence in `wcd9378_sdca_pde_post_pmu` lacks locking, allowing concurrent operations to interleave. -- --- Patch [8]: [RFC PATCH 8/8] ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec --- commit e171d84f19c198b9a986581a4ba450c4b632c883 Author: Srinivas Kandagatla <srinivas.kandagatla@oss.qualcomm.com> ASoC: codecs: add Qualcomm Tambora (WCD9378) SDCA codec This commit adds support for the Qualcomm Tambora (WCD9378) headset codec in SDCA mode over SoundWire. It integrates static SDCA topology and port properties transcribed from the factory ACPI tables for ARM platforms. > diff --git a/sound/soc/codecs/wcd9378-sdca.c b/sound/soc/codecs/wcd9378-sdca.c > new file mode 100644 > --- /dev/null > +++ b/sound/soc/codecs/wcd9378-sdca.c > @@ -0,0 +1,1202 @@ [ ... ] > +#define WCD9378_SDCA_MAX_PORTS 7 [ ... ] > +static int wcd9378_sdca_sdw_read_prop(struct sdw_slave *slave) > +{ [ ... ] > + /* Sink ports: DP6 (HPH audio), DP7 (HPH envelope), DP8 (optimisation RX). */ > + prop->sink_ports = BIT(6) | BIT(7) | BIT(8); [ ... ] > + if (device_property_read_u32_array(dev, "qcom,port-mapping", > + &slave->m_port_map[1], > + WCD9378_SDCA_MAX_PORTS)) [Severity: High] Does this array read leave DP8 unmapped? The driver declares support for sink port 8 above, but WCD9378_SDCA_MAX_PORTS is 7. Will m_port_map[8] remain uninitialized and cause the SoundWire master to map DP8 to the control port instead of a valid data port? > + dev_dbg(dev, "qcom,port-mapping not found, using default\n"); > + > + return 0; > +} [ ... ] > +static int wcd9378_sdca_hw_init(struct sdw_slave *slave) > +{ [ ... ] > + 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); [ ... ] > + 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; [Severity: Medium] Does writing the device-specific mic bias voltage into ctrl_it33_micbias_vals[0] mutate a shared static global array? If multiple instances of this driver probe, could they race to overwrite this array, causing the last probed device to dictate the mic bias voltage for all instances? > + > + /* > + * 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); [Severity: High] Does this immediate write fail due to lost enumeration? Hard-resetting the slave clears its MIPI registers, reverting it to the unattached state. The master needs time to asynchronously detect the unattached device and re-enumerate it. Will sdw_write_no_pm() (using the old dev_num) fail with a NACK or timeout because it does not wait for re-enumeration to complete? > + if (ret) > + return dev_err_probe(dev, ret, > + "HOST_CLK_DIV2_CTL_B1: %d\n", ret); [ ... ] > +static int wcd9378_sdca_pde_post_pmu(struct sdw_slave *slave, > + struct regmap *regmap, > + unsigned int function_id, > + unsigned int entity_id) > +{ [ ... ] > + 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); [ ... ] > + 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); [Severity: Medium] Can concurrent operations interleave with this sequence? wcd9378_sdca_pde_post_pmu() executes multiple regmap_write() calls followed by an unprotected sdw_write_no_pm() call. Since regmap_write() releases the regmap lock internally between calls, could an ALSA mixer control using the same commit mechanism race with a DAPM event and commit partially staged FU42 writes prematurely? > + if (ret) > + dev_err(&slave->dev, "FU42 SCP_COMMIT: %d\n", ret); > + > + return 0; > +} [ ... ] > +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, > +}; [Severity: High] Could this cause a use-after-free on driver unbind? wcd9378_sdca_sdw_probe() delegates to sdca_class_probe(), which allocates drv and queues drv->boot_work. Because there is no .remove callback in wcd9378_sdca_sdw_driver to explicitly cancel this work, won't devres free the drv object while class_boot_work remains pending or executing? > +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"); -- Sashiko AI review · https://sashiko.dev/#/patchset/20260722234221.884765-1-srinivas.kandagatla@oss.qualcomm.com?part=8 ^ permalink raw reply [flat|nested] 20+ 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 8 siblings, 0 replies; 20+ 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] 20+ messages in thread
end of thread, other threads:[~2026-07-23 10:55 UTC | newest] Thread overview: 20+ 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:54 ` sashiko-bot 2026-07-22 23:42 ` [RFC PATCH 2/8] ASoC: SDCA: allow building without ACPI Srinivas Kandagatla 2026-07-22 23:59 ` sashiko-bot 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:58 ` sashiko-bot 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:56 ` sashiko-bot 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:57 ` sashiko-bot 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:56 ` sashiko-bot 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-22 23:51 ` sashiko-bot 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 0:03 ` sashiko-bot 2026-07-23 10:17 ` [RFC PATCH 0/8] ASoC: SDCA: enable on DT platforms and add Qualcomm WCD9378 (Tambora) codec Charles Keepax
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.