* [PATCH 0/3] ASoC: add Senary SN624x SoundWire SDCA support
@ 2026-08-12 6:59 qianghua wang
2026-08-12 6:59 ` [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver qianghua wang
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: qianghua wang @ 2026-08-12 6:59 UTC (permalink / raw)
To: linux-sound
Cc: broonie, pierre-louis.bossart, yung-chuan.liao, liam.r.girdwood,
peter.ujfalusi, kai.vehmanen, cezary.rojewski, bo.liu,
qianghua.wang
From: "qianghua.wang" <qianghua.wang@senarytech.com>
This series adds SoundWire SDCA support for Senary SN624x
multi-function codecs (jack, speaker amp, DMIC) on Intel Panther Lake.
Compared with the previous combined submission:
1. ACPI match changes use the ASoC: Intel subject prefix (not ASoC: codecs).
2. MAINTAINERS only lists Senary-owned files (sn624x*, soc_sdw_senary*);
soc-acpi-intel-*-match.c stays under INTEL ASoC DRIVERS.
3. Only PTL match entries are included for now (no ARL/LNL/MTL).
Patch summary:
1/3 codec driver + MAINTAINERS
2/3 sdw_utils helpers and codec_info
3/3 Intel PTL ACPI match + SOF mach select
Validated on LG Wildcat Lake / PTL with part_id 0x6247
(MODALIAS sdw:m0496p6247v03c01).
qianghua.wang (3):
ASoC: codecs: add SN624x SDCA SoundWire driver
ASoC: sdw_utils: add Senary SN624x helpers and codec_info
ASoC: Intel: soc-acpi: add SN624x entries for PTL
MAINTAINERS | 3 +
include/sound/soc_sdw_utils.h | 20 +
sound/soc/codecs/Kconfig | 12 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/sn624x-sdca-sdw.c | 1873 +++++++++++++++++
sound/soc/codecs/sn624x-sdca.h | 144 ++
sound/soc/intel/boards/Kconfig | 1 +
.../intel/common/soc-acpi-intel-ptl-match.c | 81 +
sound/soc/sdw_utils/Makefile | 4 +-
sound/soc/sdw_utils/soc_sdw_senary_amp.c | 80 +
sound/soc/sdw_utils/soc_sdw_senary_dmic.c | 49 +
sound/soc/sdw_utils/soc_sdw_senary_sdca.c | 63 +
.../soc_sdw_senary_sdca_jack_common.c | 197 ++
sound/soc/sdw_utils/soc_sdw_utils.c | 166 ++
14 files changed, 2694 insertions(+), 1 deletion(-)
create mode 100644 sound/soc/codecs/sn624x-sdca-sdw.c
create mode 100644 sound/soc/codecs/sn624x-sdca.h
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_amp.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_dmic.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver
2026-08-12 6:59 [PATCH 0/3] ASoC: add Senary SN624x SoundWire SDCA support qianghua wang
@ 2026-08-12 6:59 ` qianghua wang
2026-08-12 8:27 ` Uwe Kleine-König
2026-08-12 6:59 ` [PATCH 2/3] ASoC: sdw_utils: add Senary SN624x helpers and codec_info qianghua wang
2026-08-12 6:59 ` [PATCH 3/3] ASoC: Intel: soc-acpi: add SN624x entries for PTL qianghua wang
2 siblings, 1 reply; 5+ messages in thread
From: qianghua wang @ 2026-08-12 6:59 UTC (permalink / raw)
To: linux-sound
Cc: broonie, pierre-louis.bossart, yung-chuan.liao, liam.r.girdwood,
peter.ujfalusi, kai.vehmanen, cezary.rojewski, bo.liu,
qianghua.wang
From: "qianghua.wang" <qianghua.wang@senarytech.com>
Add SoundWire SDCA driver for Senary SN624x multi-function codecs
(jack, speaker amp, and DMIC). Extend the Senary MAINTAINERS entry
for the new codec files only; Intel ACPI match tables remain under
INTEL ASoC DRIVERS.
Signed-off-by: qianghua.wang <qianghua.wang@senarytech.com>
---
MAINTAINERS | 3 +
sound/soc/codecs/Kconfig | 12 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/sn624x-sdca-sdw.c | 1873 ++++++++++++++++++++++++++++
sound/soc/codecs/sn624x-sdca.h | 144 +++
5 files changed, 2034 insertions(+)
create mode 100644 sound/soc/codecs/sn624x-sdca-sdw.c
create mode 100644 sound/soc/codecs/sn624x-sdca.h
diff --git a/MAINTAINERS b/MAINTAINERS
index a21c79f3e436..f18e0ed039e9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29837,9 +29837,12 @@ F: tools/testing/selftests/cgroup/test_zswap.c
SENARYTECH AUDIO CODEC DRIVER
M: bo liu <bo.liu@senarytech.com>
+M: qianghua.wang <qianghua.wang@senarytech.com>
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
F: sound/hda/codecs/senarytech.c
+F: sound/soc/codecs/sn624x*
+F: sound/soc/sdw_utils/soc_sdw_senary*
THE REST
M: Linus Torvalds <torvalds@linux-foundation.org>
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 4761ff4afb59..06e4a02f0c58 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_RT715_SDCA_SDW
imply SND_SOC_RT721_SDCA_SDW
imply SND_SOC_RT722_SDCA_SDW
+ imply SND_SOC_SN624X_SDCA_SDW
imply SND_SOC_RT1308_SDW
imply SND_SOC_RT1316_SDW
imply SND_SOC_RT1318
@@ -1968,6 +1969,17 @@ config SND_SOC_RT722_SDCA_SDW
select REGMAP_SOUNDWIRE
select REGMAP_SOUNDWIRE_MBQ
+config SND_SOC_SN624X_SDCA_SDW
+ tristate "Senary SN624x SDCA Codec - SDW"
+ depends on SOUNDWIRE
+ select REGMAP_SOUNDWIRE
+ select REGMAP_SOUNDWIRE_MBQ
+ help
+ Enable support for Senary SN624x SDCA SoundWire codecs.
+ These multi-function devices provide headset jack, speaker
+ amplifier, and digital microphone endpoints on the SoundWire
+ bus.
+
config SND_SOC_RT715
tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index d2a689006d69..44b471d8b8c3 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o
snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o
snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o
snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o
+snd-soc-sn624x-sdca-y := sn624x-sdca-sdw.o
snd-soc-rt9120-y := rt9120.o
snd-soc-rt9123-y := rt9123.o
snd-soc-rt9123p-y := rt9123p.o
@@ -726,6 +727,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o
obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o
obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o
obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o
+obj-$(CONFIG_SND_SOC_SN624X_SDCA_SDW) += snd-soc-sn624x-sdca.o
obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o
obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o
obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o
diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c
new file mode 100644
index 000000000000..5129e883cddc
--- /dev/null
+++ b/sound/soc/codecs/sn624x-sdca-sdw.c
@@ -0,0 +1,1873 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// sn624x-sdw-sdca.c -- SN624X SDCA ALSA SoC SoundWire audio driver
+//
+// Copyright(c) 2021 Realtek Semiconductor Corp.
+// Copyright(c) 2025 Senary Semiconductor Corp.
+//
+
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/find.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/jiffies.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/sdca.h>
+#include <sound/sdca_asoc.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_regmap.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "sn624x-sdca.h"
+
+/* Resume wait after bus re-enumeration (rt722-style probe timeout) */
+#define SN624X_PROBE_TIMEOUT_MS 5000U
+#define SN624X_PWR_D0 SDCA_PDE_PS0
+#define SN624X_PWR_D3 SDCA_PDE_PS3
+#define SN624X_MUTE_ON 1
+/*
+ * Budget for ACTUAL_PS poll via sdca_asoc_pde_poll_actual_ps() (100 polls).
+ * Matches previous ~2s hand-rolled loop for slow PDE transitions.
+ */
+#define SN624X_PDE_PS_POLL_BUDGET_US 2000000U
+
+/*
+ * Trace helper: load with trace=1 or:
+ * echo 1 > /sys/module/snd_soc_sn624x_sdca/parameters/trace
+ * For finer logs without trace=1, enable dynamic_debug on this file.
+ */
+static bool sn624x_trace;
+module_param_named(trace, sn624x_trace, bool, 0644);
+MODULE_PARM_DESC(trace,
+ "extra dev_dbg for probe, jack, SDW status (default off)");
+
+/*
+ * Prefer ACPI mipi-sdca-function-initialization-table via
+ * sdca_regmap_write_init(). If absent or DisCo parse is incomplete,
+ * a temporary vendor-window OEM fallback is used until firmware tables land.
+ */
+static bool sn624x_use_sdca_init_table = true;
+module_param_named(use_sdca_init_table, sn624x_use_sdca_init_table, bool, 0644);
+MODULE_PARM_DESC(use_sdca_init_table,
+ "apply ACPI SDCA function-initialization-table when present (default Y)");
+
+static bool sn624x_mask_sdca_irqs = true;
+module_param_named(mask_sdca_irqs, sn624x_mask_sdca_irqs, bool, 0644);
+MODULE_PARM_DESC(mask_sdca_irqs,
+ "Mask SDCA INTMASK bits (default Y). Jack detect is poll-driven (UAJ pulse + GE35 read); set N to unmask jack SDCA_0/8 so interrupts can reschedule the poll earlier");
+
+struct sn624x_sdca_priv;
+
+static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x);
+static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev,
+ struct sn624x_sdca_priv *sn624x);
+#define SN624X_DBG(dev, fmt, ...) dev_dbg(dev, "sn624x: " fmt, ##__VA_ARGS__)
+#define SN624X_TRC(dev, fmt, ...) do { \
+ if (sn624x_trace) \
+ dev_dbg(dev, "sn624x: " fmt, ##__VA_ARGS__); \
+} while (0)
+
+/* SN624X_UAJ_CTL_INPUT_DELAY_TIME: vendor latch; once per boot avoids dmesg spam */
+static atomic_t sn624x_power_mode_logged =
+ ATOMIC_INIT(0);
+
+static void sn624x_log_power_mode_once(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ unsigned int pm;
+ int ret;
+
+ if (!sn624x || !sn624x->regmap)
+ return;
+ if (atomic_xchg(&sn624x_power_mode_logged, 1) != 0)
+ return;
+
+ ret = regmap_read_bypassed(sn624x->regmap,
+ SN624X_UAJ_CTL_INPUT_DELAY_TIME, &pm);
+ if (ret < 0)
+ dev_err(dev,
+ "sn624x: power_mode read failed reg %#x (%d)\n",
+ SN624X_UAJ_CTL_INPUT_DELAY_TIME, ret);
+ else
+ dev_dbg(dev,
+ "sn624x: power_mode=0x%x (reg %#x; e.g. 0xf powered)\n",
+ pm, SN624X_UAJ_CTL_INPUT_DELAY_TIME);
+}
+
+/*
+ * SDCA control addresses via SDW_SDCA_CTL(fun, ent, ctl, ch).
+ * Pass ch=0 for single-value controls (power/rate/status); use CH_L/CH_R
+ * only for multi-channel FU mute/volume. CH_ENABLE_* are not SDCA Control
+ * Prefix addresses — keep as raw windows.
+ */
+/* Speaker amp (function 4) — PDE/FU power/mute/vol; rate on CS01 */
+#define SN624X_REG_PWR_STATE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_ACTUAL_PS, 0)
+#define SN624X_REG_PWR_STATE_WRITE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_REQUESTED_PS, 0)
+#define SN624X_REG_RATE_SEL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_CS01, \
+ SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0)
+#define SN624X_REG_CH_ENABLE_A 0x00000220u
+#define SN624X_REG_CH_ENABLE_B 0x00000230u
+#define SN624X_REG_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L)
+#define SN624X_REG_CHR_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R)
+#define SN624X_REG_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L)
+#define SN624X_REG_RIGHT_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_SPEAKER_AMP, SN624X_SDCA_ENT_USER_FU10, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R)
+
+/* Jack playback (function 1) */
+#define SN624X_REG_JACK_OUT_PWR_STATE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_ACTUAL_PS, 0)
+#define SN624X_REG_JACK_OUT_PWR_STATE_WRITE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_REQUESTED_PS, 0)
+#define SN624X_REG_JACK_OUT_RATE_SEL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS01, \
+ SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0)
+#define SN624X_REG_JACK_OUT_CH_ENABLE_A 0x00000120u
+#define SN624X_REG_JACK_OUT_CH_ENABLE_B 0x00000130u
+#define SN624X_REG_JACK_OUT_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L)
+#define SN624X_REG_JACK_OUT_CHR_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R)
+#define SN624X_REG_JACK_OUT_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L)
+#define SN624X_REG_JACK_OUT_CLUSTER_INDEX \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CRU05, \
+ SN624X_SDCA_CTL_CLUSTER_INDEX, 0)
+
+/* Jack headset capture (function 1) */
+#define SN624X_REG_JACK_CAP_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L)
+#define SN624X_REG_JACK_CAP_CHR_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R)
+#define SN624X_REG_JACK_CAP_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_USER_FU1E, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L)
+#define SN624X_REG_JACK_CAP_PWR_STATE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \
+ SDCA_CTL_PDE_ACTUAL_PS, 0)
+#define SN624X_REG_JACK_CAP_PWR_STATE_WRITE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_PDE04, \
+ SDCA_CTL_PDE_REQUESTED_PS, 0)
+#define SN624X_REG_JACK_CAP_RATE_SEL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_CS02, \
+ SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0)
+#define SN624X_REG_JACK_CAP_CH_ENABLE_A 0x00000420u /* low bits: 0x3=both */
+#define SN624X_REG_JACK_CAP_CH_ENABLE_B 0x00000430u
+
+#define SN624X_REG_JACK_FUN_STATUS_CTL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_ENTITY0, \
+ SN624X_SDCA_CTL_FUNCTION_STATUS, 0)
+#define SN624X_JACK_FUN_NEEDS_INIT BIT(5)
+#define SN624X_JACK_FUN_HAS_BEEN_RESET BIT(6)
+
+/* DMIC capture (function 2) */
+#define SN624X_REG_DMIC_CAP_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_L)
+#define SN624X_REG_DMIC_CAP_CHR_VOL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_VOLUME, SN624X_SDCA_CH_R)
+/* Q7.8 channel volume: integer in bits 15:8, fraction in bits 7:0 */
+#define SN624X_VOL_Q78(int_part, frac_part) \
+ ((((unsigned int)(int_part) & 0xff) << 8) | ((unsigned int)(frac_part) & 0xff))
+#define SN624X_REG_DMIC_CAP_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_L)
+#define SN624X_REG_DMIC_CAP_CHR_MUTE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_USER_FU14, \
+ SN624X_SDCA_CTL_FU_MUTE, SN624X_SDCA_CH_R)
+#define SN624X_REG_DMIC_CAP_RATE_SEL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_CS02, \
+ SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX, 0)
+/* OEM sheet used ctl=0; SDCA Actual PS is 0x10 — poll the standard selector */
+#define SN624X_REG_DMIC_CAP_PWR_STATE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_ACTUAL_PS, 0)
+#define SN624X_REG_DMIC_CAP_PWR_STATE_WRITE \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_PDE03, \
+ SDCA_CTL_PDE_REQUESTED_PS, 0)
+#define SN624X_REG_DMIC_CAP_CH_ENABLE_A 0x00000520u /* low bits: 0x3=both */
+#define SN624X_REG_DMIC_CAP_CH_ENABLE_B 0x00000530u
+
+#define SN624X_REG_DMIC_FUN_STATUS_CTL \
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_MIC_ARRAY, SN624X_SDCA_ENT_ENTITY0, \
+ SN624X_SDCA_CTL_FUNCTION_STATUS, 0)
+
+/*
+ * Write REQUESTED_PS then poll ACTUAL_PS with the shared SDCA helper
+ * (sdca_asoc_pde_poll_actual_ps). Caller must not hand-roll ACTUAL_PS waits.
+ */
+static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap,
+ unsigned int fun, unsigned int ent,
+ unsigned int ps);
+
+/*
+ * DAPM PDE supplies (rt711/rt712 style): write REQUESTED_PS then poll ACTUAL_PS.
+ * Mute-before-D3 is handled by FU widget PRE_PMD, which runs before SUPPLY PRE_PMD
+ * when the FU lists the PDE as a dependency.
+ */
+static int sn624x_dapm_pde_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event,
+ unsigned int fun, unsigned int ent)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct sn624x_sdca_priv *sn624x =
+ snd_soc_component_get_drvdata(component);
+ struct device *dev = component->dev;
+ int ret;
+
+ if (!sn624x || !sn624x->regmap)
+ return -ENODEV;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent,
+ SN624X_PWR_D0);
+ if (ret < 0)
+ dev_warn(dev,
+ "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS0 failed (%d)\n",
+ w->name, fun, ent, ret);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ ret = sn624x_pde_request_ps(dev, sn624x->regmap, fun, ent,
+ SN624X_PWR_D3);
+ if (ret < 0)
+ dev_warn(dev,
+ "sn624x: DAPM %s PDE fun=%u ent=0x%x -> PS3 failed (%d)\n",
+ w->name, fun, ent, ret);
+ break;
+ default:
+ return 0;
+ }
+ return ret;
+}
+
+static int sn624x_pde_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_dapm_pde_event(w, kcontrol, event,
+ SN624X_FUNC_NUM_JACK_CODEC,
+ SN624X_SDCA_ENT_PDE03);
+}
+
+static int sn624x_pde_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_dapm_pde_event(w, kcontrol, event,
+ SN624X_FUNC_NUM_SPEAKER_AMP,
+ SN624X_SDCA_ENT_PDE03);
+}
+
+static int sn624x_pde_mic2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_dapm_pde_event(w, kcontrol, event,
+ SN624X_FUNC_NUM_JACK_CODEC,
+ SN624X_SDCA_ENT_PDE04);
+}
+
+static int sn624x_pde_dmic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_dapm_pde_event(w, kcontrol, event,
+ SN624X_FUNC_NUM_MIC_ARRAY,
+ SN624X_SDCA_ENT_PDE03);
+}
+
+static int sn624x_fu_mute_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event,
+ unsigned int mute_l, unsigned int mute_r,
+ unsigned int vol_l, unsigned int vol_r,
+ unsigned int vol_val)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct sn624x_sdca_priv *sn624x =
+ snd_soc_component_get_drvdata(component);
+ struct device *dev = component->dev;
+ int ret;
+
+ if (!sn624x || !sn624x->regmap)
+ return -ENODEV;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ if (vol_l) {
+ ret = regmap_write(sn624x->regmap, vol_l, vol_val);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s vol(L) failed (%d)\n",
+ w->name, ret);
+ }
+ if (vol_r) {
+ ret = regmap_write(sn624x->regmap, vol_r, vol_val);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s vol(R) failed (%d)\n",
+ w->name, ret);
+ }
+ ret = regmap_write(sn624x->regmap, mute_l, 0);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s unmute(L) failed (%d)\n",
+ w->name, ret);
+ if (mute_r) {
+ ret = regmap_write(sn624x->regmap, mute_r, 0);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s unmute(R) failed (%d)\n",
+ w->name, ret);
+ }
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ /* Mute and settle before PDE SUPPLY PRE_PMD takes the domain to PS3 */
+ ret = regmap_write(sn624x->regmap, mute_l, SN624X_MUTE_ON);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s mute(L) failed (%d)\n",
+ w->name, ret);
+ if (mute_r) {
+ ret = regmap_write(sn624x->regmap, mute_r, SN624X_MUTE_ON);
+ if (ret < 0)
+ dev_warn(dev, "sn624x: DAPM %s mute(R) failed (%d)\n",
+ w->name, ret);
+ }
+ usleep_range(SN624X_ROUTE_AFTER_MUTE_US_MIN,
+ SN624X_ROUTE_AFTER_MUTE_US_MAX);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int sn624x_fu_hp_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_fu_mute_event(w, kcontrol, event,
+ SN624X_REG_JACK_OUT_MUTE, 0,
+ SN624X_REG_JACK_OUT_VOL,
+ SN624X_REG_JACK_OUT_CHR_VOL, 0);
+}
+
+static int sn624x_fu_spk_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_fu_mute_event(w, kcontrol, event,
+ SN624X_REG_MUTE, SN624X_REG_RIGHT_MUTE,
+ SN624X_REG_VOL, SN624X_REG_CHR_VOL, 0);
+}
+
+static int sn624x_fu_mic2_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(w->dapm);
+ struct sn624x_sdca_priv *sn624x =
+ snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (event == SND_SOC_DAPM_POST_PMU && sn624x)
+ sn624x_uaj_ge35_apply_detected_mode(component->dev, sn624x);
+
+ ret = sn624x_fu_mute_event(w, kcontrol, event,
+ SN624X_REG_JACK_CAP_MUTE, 0,
+ SN624X_REG_JACK_CAP_VOL,
+ SN624X_REG_JACK_CAP_CHR_VOL,
+ SN624X_VOL_Q78(6, 0));
+ return ret;
+}
+
+static int sn624x_fu_dmic_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ return sn624x_fu_mute_event(w, kcontrol, event,
+ SN624X_REG_DMIC_CAP_MUTE,
+ SN624X_REG_DMIC_CAP_CHR_MUTE,
+ SN624X_REG_DMIC_CAP_VOL,
+ SN624X_REG_DMIC_CAP_CHR_VOL,
+ SN624X_VOL_Q78(6, 0));
+}
+
+static int sn624x_pde_request_ps(struct device *dev, struct regmap *regmap,
+ unsigned int fun, unsigned int ent,
+ unsigned int ps)
+{
+ static const struct sdca_pde_delay delays[] = {
+ { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS3,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS0,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ { .from_ps = SDCA_PDE_PS1, .to_ps = SDCA_PDE_PS0,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ { .from_ps = SDCA_PDE_PS2, .to_ps = SDCA_PDE_PS0,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ { .from_ps = SDCA_PDE_PS0, .to_ps = SDCA_PDE_PS0,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ { .from_ps = SDCA_PDE_PS3, .to_ps = SDCA_PDE_PS3,
+ .us = SN624X_PDE_PS_POLL_BUDGET_US },
+ };
+ unsigned int req = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_REQUESTED_PS, 0);
+ unsigned int act = SDW_SDCA_CTL(fun, ent, SDCA_CTL_PDE_ACTUAL_PS, 0);
+ unsigned int from_ps = SDCA_PDE_PS3;
+ int ret;
+
+ ret = regmap_read(regmap, act, &from_ps);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS read failed (%d)\n",
+ fun, ent, ret);
+ from_ps = (ps == SN624X_PWR_D0) ? SN624X_PWR_D3 : SN624X_PWR_D0;
+ } else {
+ from_ps &= 0xffu;
+ }
+
+ ret = regmap_write(regmap, req, ps);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: PDE fun=%u ent=0x%x REQUESTED_PS=%u write failed (%d)\n",
+ fun, ent, ps, ret);
+ return ret;
+ }
+
+ ret = sdca_asoc_pde_poll_actual_ps(dev, regmap, fun, ent,
+ from_ps, ps,
+ delays, ARRAY_SIZE(delays));
+ if (ret < 0)
+ dev_warn(dev,
+ "sn624x: PDE fun=%u ent=0x%x ACTUAL_PS poll failed (%d; want %u)\n",
+ fun, ent, ret, ps);
+ return ret;
+}
+
+static void sn624x_jack_type_from_det_mode(struct sn624x_sdca_priv *sn624x,
+ unsigned int det_mode)
+{
+ switch (det_mode) {
+ case 0x00:
+ sn624x->jack_type = 0;
+ break;
+ case 0x03:
+ sn624x->jack_type = SND_JACK_HEADPHONE;
+ break;
+ case 0x05:
+ sn624x->jack_type = SND_JACK_HEADSET;
+ break;
+ default:
+ SN624X_DBG(&sn624x->slave->dev,
+ "unknown GE35 detected_mode 0x%x\n", det_mode);
+ sn624x->jack_type = 0;
+ break;
+ }
+}
+
+static int sn624x_uaj_ge35_select_mode(struct device *dev,
+ struct sn624x_sdca_priv *sn624x,
+ unsigned int mode)
+{
+ int ret;
+
+ ret = regmap_write(sn624x->regmap,
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC,
+ SN624X_SDCA_ENT_GE35,
+ SN624X_SDCA_CTL_SELECTED_MODE, 0),
+ mode);
+ if (ret < 0)
+ dev_warn(dev,
+ "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x failed (%d)\n",
+ mode, ret);
+ else
+ dev_dbg(dev,
+ "sn624x: jack_cap: GE35 SELECTED_MODE=0x%x\n",
+ mode);
+ return ret;
+}
+
+/*
+ * Follow DETECTED_MODE → SELECTED_MODE (rt711/rt722 pattern). Never force
+ * headset: mic path is only valid when the jack hardware reports one.
+ * Also updates sn624x->jack_type from DETECTED_MODE.
+ */
+static int sn624x_uaj_ge35_apply_detected_mode(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ unsigned int det_mode = 0;
+ int ret;
+
+ ret = regmap_read(sn624x->regmap,
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC,
+ SN624X_SDCA_ENT_GE35,
+ SN624X_SDCA_CTL_DETECTED_MODE, 0),
+ &det_mode);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: jack_cap: GE35 DETECTED_MODE read failed (%d)\n",
+ ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "sn624x: jack_cap: GE35 DETECTED_MODE=0x%x\n", det_mode);
+ sn624x_jack_type_from_det_mode(sn624x, det_mode);
+
+ if (!det_mode) {
+ dev_dbg(dev,
+ "sn624x: jack_cap: no jack detected — skip SELECTED_MODE\n");
+ return 0;
+ }
+
+ return sn624x_uaj_ge35_select_mode(dev, sn624x, det_mode);
+}
+
+static const char *sn624x_status_str(enum sdw_slave_status status)
+{
+ switch (status) {
+ case SDW_SLAVE_UNATTACHED:
+ return "UNATTACHED";
+ case SDW_SLAVE_ATTACHED:
+ return "ATTACHED";
+ case SDW_SLAVE_ALERT:
+ return "ALERT";
+ case SDW_SLAVE_RESERVED:
+ return "RESERVED";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static int sn624x_sdca_mbq_size(struct device *dev, unsigned int reg)
+{
+ if (!SDW_SDCA_VALID_CTL(reg))
+ return 1;
+
+ /*
+ * FU_VOLUME and GE35 DETECTED_MODE share control selector 0x02.
+ * Only list known FU volume addresses as 16-bit MBQ — matching
+ * DETECTED_MODE by csel alone makes GE35 use SDW_SDCA_MBQ_CTL and
+ * fails with -ENODATA (-61), which breaks jack plug/unplug.
+ */
+ switch (reg) {
+ case SN624X_REG_VOL:
+ case SN624X_REG_CHR_VOL:
+ case SN624X_REG_JACK_OUT_VOL:
+ case SN624X_REG_JACK_OUT_CHR_VOL:
+ case SN624X_REG_JACK_CAP_VOL:
+ case SN624X_REG_JACK_CAP_CHR_VOL:
+ case SN624X_REG_DMIC_CAP_VOL:
+ case SN624X_REG_DMIC_CAP_CHR_VOL:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+static bool sn624x_sdca_readable_register(struct device *dev, unsigned int reg)
+{
+ return sn624x_sdca_mbq_size(dev, reg) > 0;
+}
+
+static bool sn624x_sdca_volatile_register(struct device *dev, unsigned int reg)
+{
+ if (!SDW_SDCA_VALID_CTL(reg))
+ return true;
+
+ switch (reg) {
+ case SN624X_REG_PWR_STATE:
+ case SN624X_REG_JACK_OUT_PWR_STATE:
+ case SN624X_REG_JACK_CAP_PWR_STATE:
+ case SN624X_REG_DMIC_CAP_PWR_STATE:
+ case SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_SDCA_ENT_GE35,
+ SN624X_SDCA_CTL_DETECTED_MODE, 0):
+ return true;
+ default:
+ if (SDW_SDCA_CTL_CSEL(reg) == SDCA_CTL_PDE_ACTUAL_PS)
+ return true;
+ return false;
+ }
+}
+
+static const struct regmap_sdw_mbq_cfg sn624x_sdca_mbq_cfg = {
+ .mbq_size = sn624x_sdca_mbq_size,
+};
+
+static const struct reg_default sn624x_sdca_reg_defaults[] = {
+ {}
+};
+
+static const struct regmap_config sn624x_sdca_regmap = {
+ .reg_bits = 32,
+ .val_bits = 16,
+ .readable_reg = sn624x_sdca_readable_register,
+ .volatile_reg = sn624x_sdca_volatile_register,
+ .max_register = 0xffffffff,
+ .reg_defaults = sn624x_sdca_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(sn624x_sdca_reg_defaults),
+ .cache_type = REGCACHE_MAPLE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+/*
+ * Prefer ACPI mipi-sdca-function-initialization-table (sdca_regmap_write_init).
+ * find_sdca_init_table() runs before entity parsing, so a partial parse may
+ * still leave a usable init table even when later DisCo entities fail.
+ */
+static int sn624x_write_sdca_init_table(struct device *dev,
+ struct sn624x_sdca_priv *sn624x,
+ struct sdca_function_data *fn,
+ const char *label)
+{
+ int ret;
+
+ if (!sn624x_use_sdca_init_table)
+ return -EOPNOTSUPP;
+ if (!fn || !fn->num_init_table)
+ return -ENOENT;
+
+ ret = sdca_regmap_write_init(dev, sn624x->regmap, fn);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: %s function-initialization-table write failed (%d)\n",
+ label, ret);
+ return ret;
+ }
+
+ dev_info(dev,
+ "sn624x: applied %s function-initialization-table (%d writes)\n",
+ label, fn->num_init_table);
+ return 0;
+}
+
+/*
+ * Temporary vendor-window fallback when ACPI has no init table.
+ * Validated on current platforms; remove once DisCo init tables are complete.
+ */
+static int sn624x_jack_oem_fallback_init(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ static const struct reg_sequence jack_oem_table[] = {
+ REG_SEQ0(SN624X_UAJ_CTL_IT33_MIC_BIAS, 0x06),
+ REG_SEQ0(SN624X_UAJ_CTL_IT31_MIC_BIAS, 0x06),
+ REG_SEQ0(SN624X_UAJ_CTL_FU41_CH1_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU41_CH2_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU36_CH1_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU36_CH2_MUTE, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH, 0x12),
+ REG_SEQ0(SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH, 0x0C),
+ REG_SEQ0(SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH, 0x12),
+ REG_SEQ0(SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW, 0x00),
+ REG_SEQ0(SN624X_UAJ_CTL_INPUT_DELAY_TIME, 0x0F),
+ REG_SEQ0(SN624X_UAJ_CTL_PORTA_CHARGE_PUMP, 0x0B),
+ };
+ int ret;
+
+ dev_warn_once(dev,
+ "sn624x: using temporary jack OEM init fallback (no ACPI init table)\n");
+ ret = regmap_multi_reg_write_bypassed(sn624x->regmap, jack_oem_table,
+ ARRAY_SIZE(jack_oem_table));
+ if (ret < 0) {
+ dev_warn(dev, "sn624x: jack OEM fallback failed (%d)\n", ret);
+ return ret;
+ }
+ usleep_range(1000, 1500);
+ return 0;
+}
+
+static int sn624x_dmic_oem_fallback_init(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ static const struct reg_sequence dmic_oem_table[] = {
+ REG_SEQ0(0x00002041, 0x00),
+ REG_SEQ0(SN624X_UAJ_DMIC_PPU11, 0x30),
+ REG_SEQ0(0x00002105, 0x66),
+ REG_SEQ0(0x00002107, 0x26),
+ REG_SEQ0(0x00002109, 0x62),
+ REG_SEQ0(0x40800a09, 0x00),
+ REG_SEQ0(0x40800a0a, 0x00),
+ REG_SEQ0(0x40802a11, 0x00),
+ REG_SEQ0(0x40800a11, 0x00),
+ REG_SEQ0(0x40802a12, 0x00),
+ REG_SEQ0(0x40800a12, 0x00),
+ REG_SEQ0(0x40802a13, 0x00),
+ REG_SEQ0(0x40800a13, 0x00),
+ REG_SEQ0(0x40802a14, 0x00),
+ REG_SEQ0(0x40800a14, 0x00),
+ REG_SEQ0(0x408029d8, 0x0c),
+ REG_SEQ0(0x408009d8, 0x00),
+ REG_SEQ0(0x408029d9, 0x0c),
+ REG_SEQ0(0x408009d9, 0x00),
+ REG_SEQ0(0x00002140, 0x18),
+ REG_SEQ0(0x00002045, 0x01),
+ };
+ int ret;
+
+ dev_warn_once(dev,
+ "sn624x: using temporary dmic OEM init fallback (no ACPI init table)\n");
+ ret = regmap_multi_reg_write_bypassed(sn624x->regmap, dmic_oem_table,
+ ARRAY_SIZE(dmic_oem_table));
+ if (ret < 0) {
+ dev_warn(dev, "sn624x: dmic OEM fallback failed (%d)\n", ret);
+ return ret;
+ }
+ usleep_range(1000, 1500);
+ return 0;
+}
+
+static void sn624x_uaj_apply_io_defaults(struct sn624x_sdca_priv *sn624x)
+{
+ struct device *dev = &sn624x->slave->dev;
+ int ret;
+
+ ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func,
+ "jack");
+ if (ret)
+ sn624x_jack_oem_fallback_init(dev, sn624x);
+}
+
+static int sn624x_parse_sdca_functions(struct sn624x_sdca_priv *sn624x)
+{
+ struct sdw_slave *slave = sn624x->slave;
+ struct device *dev = &slave->dev;
+ int i, ret;
+
+ if (!slave->sdca_data.num_functions) {
+ dev_dbg(dev, "sn624x: no SDCA function descriptors from DisCo\n");
+ return 0;
+ }
+
+ for (i = 0; i < slave->sdca_data.num_functions; i++) {
+ struct sdca_function_desc *desc = &slave->sdca_data.function[i];
+ struct sdca_function_data *fn;
+ bool is_jack = false, is_mic = false, is_amp = false;
+
+ switch (desc->type) {
+ case SDCA_FUNCTION_TYPE_UAJ:
+ case SDCA_FUNCTION_TYPE_SIMPLE_JACK:
+ case SDCA_FUNCTION_TYPE_RJ:
+ is_jack = true;
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_MIC:
+ case SDCA_FUNCTION_TYPE_SIMPLE_MIC:
+ is_mic = true;
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_AMP:
+ case SDCA_FUNCTION_TYPE_SIMPLE_AMP:
+ case SDCA_FUNCTION_TYPE_SPEAKER_MIC:
+ is_amp = true;
+ break;
+ default:
+ break;
+ }
+
+ if (desc->adr == SN624X_FUNC_NUM_JACK_CODEC)
+ is_jack = true;
+ else if (desc->adr == SN624X_FUNC_NUM_MIC_ARRAY)
+ is_mic = true;
+ else if (desc->adr == SN624X_FUNC_NUM_SPEAKER_AMP)
+ is_amp = true;
+
+ if (!is_jack && !is_mic && !is_amp)
+ continue;
+
+ fn = devm_kzalloc(dev, sizeof(*fn), GFP_KERNEL);
+ if (!fn)
+ return -ENOMEM;
+
+ fn->desc = desc;
+ ret = sdca_parse_function(dev, slave, fn);
+ if (ret) {
+ /*
+ * Init table is parsed before entities. Keep a partial
+ * function if the ACPI init table was already loaded.
+ */
+ dev_warn(dev,
+ "sn624x: sdca_parse_function(%s adr=%u) failed (%d)%s\n",
+ desc->name ? desc->name : "?", desc->adr, ret,
+ fn->num_init_table ?
+ ", keeping ACPI init table" : "");
+ if (!fn->num_init_table)
+ continue;
+ } else {
+ dev_dbg(dev,
+ "sn624x: parsed SDCA function %s type=%u adr=%u init_writes=%d\n",
+ desc->name ? desc->name : "?", desc->type,
+ desc->adr, fn->num_init_table);
+ }
+
+ if (is_jack && !sn624x->jack_func)
+ sn624x->jack_func = fn;
+ else if (is_mic && !sn624x->mic_func)
+ sn624x->mic_func = fn;
+ else if (is_amp && !sn624x->amp_func)
+ sn624x->amp_func = fn;
+ }
+
+ return 0;
+}
+
+/*
+ * Poll-driven jack detect: pulse UAJ jack-detect, then apply GE35
+ * DETECTED_MODE → SELECTED_MODE via sn624x_uaj_ge35_apply_detected_mode().
+ */
+static int sn624x_sdca_headset_detect(struct sn624x_sdca_priv *sn624x)
+{
+ struct device *dev = &sn624x->slave->dev;
+ int ret;
+
+ SN624X_DBG(dev, "headset_detect: pulse UAJ jack-detect, read GE35\n");
+ ret = regmap_write(sn624x->regmap,
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC,
+ SN624X_UAJ_ENT_BLOCK,
+ SN624X_UAJ_CTL_JACK_DETECT, 0),
+ 0x08);
+ if (ret < 0)
+ goto io_error;
+
+ usleep_range(5000, 5500);
+
+ ret = sn624x_uaj_ge35_apply_detected_mode(dev, sn624x);
+
+ /* Clear the detect pulse even if GE35 apply failed. */
+ regmap_write(sn624x->regmap,
+ SDW_SDCA_CTL(SN624X_FUNC_NUM_JACK_CODEC, SN624X_UAJ_ENT_BLOCK,
+ SN624X_UAJ_CTL_JACK_DETECT, 0),
+ 0x00);
+
+ if (ret < 0)
+ goto io_error;
+
+ SN624X_DBG(dev, "headset_detect: jack_type=0x%x\n", sn624x->jack_type);
+ return 0;
+
+io_error:
+ if (ret == -ENODATA)
+ SN624X_DBG(dev, "%s: UAJ/GE35 access ignored (-ENODATA)\n",
+ __func__);
+ else
+ dev_err_ratelimited(dev, "%s failed (%d)\n", __func__, ret);
+ return ret;
+}
+
+static void sn624x_sdca_sdca_irq_mask_all(struct sn624x_sdca_priv *sn624x)
+{
+ if (!sn624x->slave)
+ return;
+
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK1, 0);
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK2, 0);
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK3, 0);
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK4, 0);
+ SN624X_DBG(&sn624x->slave->dev,
+ "SDCA INTMASK1-4 = 0 (all SDCA sources masked)\n");
+}
+
+static void sn624x_sdca_jack_irq_unmask(struct sn624x_sdca_priv *sn624x)
+{
+ if (!sn624x->slave || sn624x_mask_sdca_irqs)
+ return;
+
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK1,
+ SDW_SCP_SDCA_INTMASK_SDCA_0);
+ sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK2,
+ SDW_SCP_SDCA_INTMASK_SDCA_8);
+}
+
+static void sn624x_sdca_jack_irq_mask(struct sn624x_sdca_priv *sn624x)
+{
+ if (!sn624x->slave)
+ return;
+
+ if (sn624x_mask_sdca_irqs) {
+ sn624x_sdca_sdca_irq_mask_all(sn624x);
+ return;
+ }
+
+ sdw_update_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK1,
+ SDW_SCP_SDCA_INTMASK_SDCA_0, 0);
+ sdw_update_no_pm(sn624x->slave, SDW_SCP_SDCA_INTMASK2,
+ SDW_SCP_SDCA_INTMASK_SDCA_8, 0);
+}
+
+static void sn624x_jack_schedule_poll(struct sn624x_sdca_priv *sn624x)
+{
+ if (!sn624x || !sn624x->hs_jack || !sn624x->slave)
+ return;
+
+ mod_delayed_work(system_power_efficient_wq, &sn624x->jack_detect_work,
+ msecs_to_jiffies(SN624X_JACK_POLL_MS));
+
+ /* Keep poll alive across autosuspend (3s default) when idle */
+ if (sn624x->component)
+ pm_runtime_mark_last_busy(sn624x->component->dev);
+}
+
+static void sn624x_sdca_jack_poll_and_report(struct sn624x_sdca_priv *sn624x)
+{
+ struct device *dev;
+
+ if (!sn624x->hs_jack || !sn624x->slave)
+ return;
+
+ dev = &sn624x->slave->dev;
+ if (sn624x_sdca_headset_detect(sn624x) < 0)
+ return;
+
+ snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type,
+ SND_JACK_HEADSET | SND_JACK_HEADPHONE);
+ sn624x->jack_type_last = sn624x->jack_type;
+ dev_dbg(dev, "sn624x: jack polled: detected_mode -> jack_type=0x%x\n",
+ sn624x->jack_type);
+}
+
+static void sn624x_sdca_jack_detect_handler(struct work_struct *work)
+{
+ struct sn624x_sdca_priv *sn624x =
+ container_of(work, struct sn624x_sdca_priv, jack_detect_work.work);
+ struct device *dev;
+ struct snd_soc_component *component;
+ int pm_ret;
+ bool got_pm = false;
+
+ if (!sn624x->slave) {
+ sn624x_jack_schedule_poll(sn624x);
+ return;
+ }
+
+ dev = &sn624x->slave->dev;
+ component = sn624x->component;
+ if (component) {
+ pm_ret = pm_runtime_resume_and_get(component->dev);
+ if (pm_ret >= 0) {
+ got_pm = true;
+ } else if (pm_ret != -EACCES) {
+ SN624X_DBG(dev, "jack_work: pm resume failed (%d)\n", pm_ret);
+ }
+ }
+
+ if (!sn624x->hs_jack) {
+ SN624X_DBG(dev, "jack_work: skip (no hs_jack)\n");
+ goto out_reschedule;
+ }
+
+ /* Registers are only valid after ATTACHED → io_init (hw_init). */
+ if (!sn624x->hw_init) {
+ SN624X_DBG(dev, "jack_work: skip (hw_init not ready)\n");
+ goto out_reschedule;
+ }
+
+ /* Poll GE35 every interval; do not rely on SDCA_0 IRQ or PCM activity */
+ sn624x_sdca_headset_detect(sn624x);
+
+ if (sn624x->jack_type != sn624x->jack_type_last) {
+ dev_dbg(dev,
+ "sn624x: jack event: %s (jack_type=0x%x; HP=0x%x HS=0x%x)\n",
+ sn624x->jack_type ? "plug" : "unplug",
+ sn624x->jack_type, SND_JACK_HEADPHONE, SND_JACK_HEADSET);
+ sn624x->jack_type_last = sn624x->jack_type;
+
+ snd_soc_jack_report(sn624x->hs_jack, sn624x->jack_type,
+ SND_JACK_HEADSET | SND_JACK_HEADPHONE);
+ }
+
+out_reschedule:
+ sn624x_jack_schedule_poll(sn624x);
+ if (got_pm) {
+ pm_runtime_mark_last_busy(component->dev);
+ pm_runtime_put_autosuspend(component->dev);
+ }
+}
+
+static void sn624x_sdca_jack_init(struct sn624x_sdca_priv *sn624x)
+{
+ /* No machine set_jack() yet — not "jack unsupported". */
+ if (!sn624x->hs_jack)
+ return;
+
+ sn624x_sdca_jack_irq_unmask(sn624x);
+ sn624x_sdca_jack_poll_and_report(sn624x);
+ sn624x_jack_schedule_poll(sn624x);
+
+ dev_dbg(&sn624x->slave->dev,
+ "sn624x: jack_init: poll scheduled every %ums\n", SN624X_JACK_POLL_MS);
+ SN624X_DBG(&sn624x->slave->dev, "jack_init: SDCA INTMASK1/2 unmasked\n");
+ SN624X_TRC(&sn624x->slave->dev, "jack detection IRQ unmasked\n");
+}
+
+static int sn624x_sdca_set_jack_detect(struct snd_soc_component *component,
+ struct snd_soc_jack *hs_jack, void *data)
+{
+ struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ sn624x->hs_jack = hs_jack;
+ sn624x->jack_type_last = -1;
+ dev_dbg(component->dev,
+ "sn624x: set_jack: hs_jack=%p first_hw_init=%d hw_init=%d\n",
+ hs_jack, sn624x->first_hw_init, sn624x->hw_init);
+
+ /* set_jack may run before io_init; poll waits until hw_init. */
+ if (!sn624x->first_hw_init) {
+ sn624x_jack_schedule_poll(sn624x);
+ return 0;
+ }
+
+ ret = pm_runtime_resume_and_get(component->dev);
+ if (ret < 0) {
+ if (ret != -EACCES) {
+ dev_err(component->dev, "%s: resume failed (%d)\n", __func__, ret);
+ return ret;
+ }
+ sn624x_jack_schedule_poll(sn624x);
+ return 0;
+ }
+
+ sn624x_sdca_jack_init(sn624x);
+ pm_runtime_put_autosuspend(component->dev);
+
+ return 0;
+}
+
+/* Prefer ACPI init table; OEM vendor-window fallback only if absent. */
+static int sn624x_jack_apply_defaults(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ int ret;
+
+ if (!sn624x || !sn624x->regmap || !sn624x->slave)
+ return -ENODEV;
+
+ ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->jack_func,
+ "jack");
+ if (ret == 0) {
+ usleep_range(1000, 1500);
+ return 0;
+ }
+
+ return sn624x_jack_oem_fallback_init(dev, sn624x);
+}
+
+static int sn624x_dmic_apply_defaults(struct device *dev,
+ struct sn624x_sdca_priv *sn624x)
+{
+ int ret;
+
+ if (!sn624x || !sn624x->regmap || !sn624x->slave)
+ return -ENODEV;
+
+ ret = sn624x_write_sdca_init_table(dev, sn624x, sn624x->mic_func,
+ "dmic");
+ if (ret == 0) {
+ usleep_range(1000, 1500);
+ return 0;
+ }
+
+ return sn624x_dmic_oem_fallback_init(dev, sn624x);
+}
+
+static int sn624x_sdca_io_init(struct device *dev, struct sdw_slave *slave)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev);
+ int ret;
+ unsigned int jack_func_status, dmic_func_status;
+
+ if (sn624x->hw_init) {
+ SN624X_DBG(dev, "io_init: skip (hw_init already true)\n");
+ return 0;
+ }
+
+ SN624X_TRC(dev, "io_init: start (first hardware bring-up)\n");
+ sn624x->disable_irq = false;
+
+ if (sn624x_mask_sdca_irqs)
+ sn624x_sdca_sdca_irq_mask_all(sn624x);
+
+ regcache_cache_only(sn624x->regmap, false);
+
+ pm_runtime_get_noresume(dev);
+
+ sn624x_uaj_apply_io_defaults(sn624x);
+
+ if (sn624x->hs_jack)
+ sn624x_sdca_jack_init(sn624x);
+
+ ret = regmap_read_bypassed(sn624x->regmap,
+ SN624X_REG_JACK_FUN_STATUS_CTL,
+ &jack_func_status);
+ if (ret < 0) {
+ dev_dbg(dev,
+ "sn624x: io_init: JACK FUN STATUS read failed (%d)\n",
+ ret);
+ goto io_init_next;
+ }
+ if (jack_func_status & (SN624X_JACK_FUN_NEEDS_INIT |
+ SN624X_JACK_FUN_HAS_BEEN_RESET)) {
+ const struct reg_sequence clear_status =
+ REG_SEQ0(SN624X_REG_JACK_FUN_STATUS_CTL, 0x61);
+
+ ret = sn624x_jack_apply_defaults(dev, sn624x);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: io_init: jack defaults failed (%d)\n",
+ ret);
+ goto io_init_next;
+ }
+ ret = regmap_multi_reg_write_bypassed(
+ sn624x->regmap, &clear_status, 1);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: io_init: JACK FUN STATUS clear write failed (%d)\n",
+ ret);
+ goto io_init_next;
+ }
+ usleep_range(1000, 1500);
+ }
+
+io_init_next:
+ ret = regmap_read_bypassed(sn624x->regmap,
+ SN624X_REG_DMIC_FUN_STATUS_CTL,
+ &dmic_func_status);
+ if (ret < 0) {
+ dev_dbg(dev,
+ "sn624x: io_init: DMIC FUN STATUS read failed (%d)\n",
+ ret);
+ goto io_init_end;
+ }
+
+ if (dmic_func_status & (SN624X_JACK_FUN_NEEDS_INIT |
+ SN624X_JACK_FUN_HAS_BEEN_RESET)){
+ const struct reg_sequence clear_status =
+ REG_SEQ0(SN624X_REG_DMIC_FUN_STATUS_CTL, 0x61);
+ ret = sn624x_dmic_apply_defaults(dev, sn624x);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: io_init: dmic defaults failed (%d)\n",
+ ret);
+ goto io_init_end;
+ }
+ ret = regmap_multi_reg_write_bypassed(sn624x->regmap,
+ &clear_status,
+ 1);
+ if (ret < 0) {
+ dev_warn(dev,
+ "sn624x: io_init: DMIC FUN STATUS clear write failed (%d)\n",
+ ret);
+ goto io_init_end;
+ }
+ usleep_range(1000, 1500);
+ }
+
+io_init_end:
+ pm_runtime_set_active(dev);
+
+ sn624x->hw_init = true;
+ sn624x->first_hw_init = true;
+
+ pm_runtime_put_autosuspend(dev);
+
+ sn624x_log_power_mode_once(dev, sn624x);
+
+ SN624X_DBG(dev, "io_init: complete (hw_init set)\n");
+ SN624X_TRC(dev, "io_init: done, jack %s\n",
+ sn624x->hs_jack ? "will init" : "not registered yet");
+ return 0;
+}
+
+/*
+ * Bus lifecycle (same as rt722): enumerate → assign dev_num → ATTACHED →
+ * io_init(). Do not poll enumeration/init around each register access; after
+ * unattach, resume waits once on initialization_complete then regcache_sync.
+ */
+static int sn624x_sdca_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev);
+ bool need_io_init;
+ int ret;
+
+ SN624X_DBG(&slave->dev, "update_status: %s hw_init=%d dev_num=%u\n",
+ sn624x_status_str(status), sn624x->hw_init, slave->dev_num);
+ SN624X_TRC(&slave->dev, "SDW status=%s (hw_init=%d)\n",
+ sn624x_status_str(status), sn624x->hw_init);
+
+ if (status == SDW_SLAVE_UNATTACHED) {
+ cancel_delayed_work_sync(&sn624x->jack_detect_work);
+ sn624x->hw_init = false;
+ SN624X_TRC(&slave->dev, "slave UNATTACHED: cleared hw_init, jack work cancelled\n");
+ }
+
+ if (status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ if (sn624x_mask_sdca_irqs)
+ sn624x_sdca_sdca_irq_mask_all(sn624x);
+
+ need_io_init = !sn624x->hw_init;
+ if (need_io_init) {
+ dev_notice(&slave->dev, "sn624x: update_status ATTACHED -> io_init()\n");
+ SN624X_TRC(&slave->dev, "calling io_init from ATTACHED\n");
+ ret = sn624x_sdca_io_init(&slave->dev, slave);
+ if (ret < 0)
+ return ret;
+ } else {
+ dev_dbg(&slave->dev,
+ "sn624x: update_status ATTACHED: io_init already done\n");
+ }
+
+ /* Re-ATTACHED without io_init: restore jack detect after bus is up. */
+ if (sn624x->hs_jack && sn624x->hw_init && !need_io_init) {
+ sn624x_sdca_jack_irq_unmask(sn624x);
+ sn624x_sdca_jack_poll_and_report(sn624x);
+ sn624x_jack_schedule_poll(sn624x);
+ }
+
+ return 0;
+}
+
+static int sn624x_sdca_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ struct sdw_dpn_prop *dpn;
+ unsigned long addr;
+ unsigned int bit;
+ int nval;
+ int i, j;
+
+ sdw_slave_read_lane_mapping(slave);
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+ prop->paging_support = true;
+ prop->use_domain_irq = true;
+ prop->wake_capable = true;
+
+ prop->source_ports = BIT(SN624X_PORT_JACK_CAPTURE) |
+ BIT(SN624X_PORT_DMIC_CAPTURE);
+ prop->sink_ports = BIT(SN624X_PORT_JACK_PLAYBACK) |
+ BIT(SN624X_PORT_SPEAKER_PLAYBACK);
+
+ nval = hweight32(prop->source_ports);
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop), GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+ i = 0;
+ dpn = prop->src_dpn_prop;
+ addr = prop->source_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+ nval = hweight32(prop->sink_ports);
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop), GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+ j = 0;
+ dpn = prop->sink_dpn_prop;
+ addr = prop->sink_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[j].num = bit;
+ dpn[j].type = SDW_DPN_FULL;
+ dpn[j].simple_ch_prep_sm = true;
+ dpn[j].ch_prep_timeout = 10;
+ j++;
+ }
+ prop->clk_stop_timeout = 900;
+ prop->simple_clk_stop_capable = true;
+ prop->lane_control_support = true;
+ SN624X_DBG(&slave->dev,
+ "read_prop: src_ports=0x%x sink_ports=0x%x lane irq paging\n",
+ prop->source_ports, prop->sink_ports);
+ SN624X_TRC(&slave->dev, "read_prop: slave properties filled\n");
+
+ return 0;
+}
+
+/*
+ * Clear SDCA INT1/2 only. DP0_INT standard fields (PORT_READY, etc.) are
+ * owned by the SoundWire bus core; cascade clears when SDCA status is clear.
+ * Pending SDCA IRQs can block clock-stop prepare (CLK_STP_NF -> -ETIMEDOUT).
+ */
+static int sn624x_sdca_flush_pending_irqs(struct sn624x_sdca_priv *sn624x,
+ struct device *dev)
+{
+ int ret, stat = 0;
+ int count = 0;
+ const int retry = 3;
+ unsigned int scp_sdca_stat1, scp_sdca_stat2 = 0;
+
+ if (!sn624x || !sn624x->slave)
+ return 0;
+
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1);
+ if (ret < 0)
+ return ret;
+ sn624x->scp_sdca_stat1 = ret;
+
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2);
+ if (ret < 0)
+ return ret;
+ sn624x->scp_sdca_stat2 = ret;
+
+ do {
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1);
+ if (ret < 0)
+ return ret;
+ if (ret & SDW_SCP_SDCA_INTMASK_SDCA_0) {
+ ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1,
+ SDW_SCP_SDCA_INTMASK_SDCA_0);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2);
+ if (ret < 0)
+ return ret;
+ if (ret & SDW_SCP_SDCA_INTMASK_SDCA_8) {
+ ret = sdw_write_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2,
+ SDW_SCP_SDCA_INTMASK_SDCA_8);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT1);
+ if (ret < 0)
+ return ret;
+ scp_sdca_stat1 = ret & SDW_SCP_SDCA_INTMASK_SDCA_0;
+
+ ret = sdw_read_no_pm(sn624x->slave, SDW_SCP_SDCA_INT2);
+ if (ret < 0)
+ return ret;
+ scp_sdca_stat2 = ret & SDW_SCP_SDCA_INTMASK_SDCA_8;
+
+ stat = scp_sdca_stat1 || scp_sdca_stat2;
+ count++;
+ } while (stat != 0 && count < retry);
+
+ if (stat && dev)
+ dev_warn(dev,
+ "sn624x: SDCA IRQ flush incomplete (stat=%u)\n", stat);
+
+ return stat ? -EBUSY : 0;
+}
+
+static int sn624x_sdca_interrupt_callback(struct sdw_slave *slave,
+ struct sdw_slave_intr_status *status)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev);
+ int ret;
+
+ if (!sn624x)
+ return 0;
+
+ /*
+ * Bus expects the codec to clear SCP SDCA INT1/2 when cascade is set.
+ * Leaving them set can stall alert handling.
+ */
+
+ SN624X_DBG(&slave->dev,
+ "irq: control_port=%#x sdca_cascade=%u disable_irq=%d\n",
+ status->control_port, status->sdca_cascade, sn624x->disable_irq);
+
+ cancel_delayed_work_sync(&sn624x->jack_detect_work);
+
+ mutex_lock(&sn624x->disable_irq_lock);
+
+ ret = sn624x_sdca_flush_pending_irqs(sn624x, &slave->dev);
+ if (ret < 0 && ret != -EBUSY)
+ goto io_error;
+
+ SN624X_DBG(&slave->dev, "irq: INT1=0x%x INT2=0x%x\n",
+ sn624x->scp_sdca_stat1, sn624x->scp_sdca_stat2);
+
+ /*
+ * Always restart the jack poll after IRQ handling so a cancel above
+ * cannot stop detection. With mask_sdca_irqs=N this also turns a jack
+ * SDCA_0/8 interrupt into an early poll; the poll itself still does
+ * the UAJ pulse + GE35 read.
+ */
+ if (!sn624x->disable_irq)
+ sn624x_jack_schedule_poll(sn624x);
+
+ mutex_unlock(&sn624x->disable_irq_lock);
+ return 0;
+
+io_error:
+ mutex_unlock(&sn624x->disable_irq_lock);
+ dev_err_ratelimited(&slave->dev, "%s: IO error (%d)\n", __func__, ret);
+ return ret;
+}
+
+static const struct sdw_slave_ops sn624x_sdca_slave_ops = {
+ .read_prop = sn624x_sdca_read_prop,
+ .interrupt_callback = sn624x_sdca_interrupt_callback,
+ .update_status = sn624x_sdca_update_status,
+};
+
+static int sn624x_sdca_probe(struct snd_soc_component *component)
+{
+ struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ sn624x->component = component;
+
+ if (!sn624x->first_hw_init)
+ return 0;
+
+ ret = pm_runtime_resume(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
+ return 0;
+}
+
+static int sn624x_sdca_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_config stream_config;
+ struct sdw_port_config port_config;
+ enum sdw_data_direction direction;
+ struct sdw_stream_runtime *sdw_stream;
+ unsigned int ch = params_channels(params);
+ int port;
+ int ret;
+
+ dev_dbg(dai->dev,
+ "sn624x: hw_params: entered dai=%s id=%d stream=%s\n",
+ dai->name, dai->id, snd_pcm_stream_str(substream));
+
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+ if (!sdw_stream) {
+ dev_warn(dai->dev,
+ "sn624x: hw_params: no SDW stream (set_stream not run yet?)\n");
+ return -EINVAL;
+ }
+ if (!sn624x->slave) {
+ dev_warn(dai->dev, "sn624x: hw_params: slave NULL\n");
+ return -EINVAL;
+ }
+
+ ret = pm_runtime_resume(component->dev);
+ if (ret < 0 && ret != -EACCES) {
+ dev_err(dai->dev,
+ "sn624x: hw_params: pm_runtime_resume failed (%d)\n", ret);
+ return ret;
+ }
+
+ if (sn624x->hw_init)
+ sn624x_log_power_mode_once(dai->dev, sn624x);
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ direction = SDW_DATA_DIR_RX;
+ if (dai->id == SN624X_DAI_JACK)
+ port = SN624X_PORT_JACK_PLAYBACK;
+ else if (dai->id == SN624X_DAI_SPEAKER)
+ port = SN624X_PORT_SPEAKER_PLAYBACK;
+ else
+ return -EINVAL;
+ } else {
+ direction = SDW_DATA_DIR_TX;
+ if (dai->id == SN624X_DAI_JACK) {
+ port = SN624X_PORT_JACK_CAPTURE;
+ } else if (dai->id == SN624X_DAI_DMIC) {
+ port = SN624X_PORT_DMIC_CAPTURE;
+ } else {
+ return -EINVAL;
+ }
+ }
+ stream_config.frame_rate = params_rate(params);
+ stream_config.ch_count = ch;
+ stream_config.bps = snd_pcm_format_width(params_format(params));
+ stream_config.direction = direction;
+ port_config.ch_mask = GENMASK(ch - 1, 0);
+ port_config.num = port;
+
+ ret = sdw_stream_add_slave(sn624x->slave, &stream_config,
+ &port_config, 1, sdw_stream);
+ if (ret)
+ dev_err(dai->dev, "%s: sdw_stream_add_slave port %d failed: %d\n",
+ __func__, port, ret);
+ return ret;
+}
+
+static int sn624x_sdca_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sn624x_sdca_priv *sn624x = snd_soc_component_get_drvdata(dai->component);
+ struct sdw_stream_runtime *sdw_stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!sn624x->slave)
+ return -EINVAL;
+
+ return sdw_stream_remove_slave(sn624x->slave, sdw_stream);
+}
+
+static int sn624x_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream,
+ int direction)
+{
+ snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+ return 0;
+}
+
+static void sn624x_sdca_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static const struct snd_soc_dapm_widget sn624x_sdca_dapm_widgets[] = {
+ SND_SOC_DAPM_AIF_IN("RX", "Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("TX", "Capture", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SPK RX", "Speaker Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DMIC TX", "DMic Capture", 0, SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_SUPPLY("PDE HP", SND_SOC_NOPM, 0, 0,
+ sn624x_pde_hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SUPPLY("PDE SPK", SND_SOC_NOPM, 0, 0,
+ sn624x_pde_spk_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SUPPLY("PDE MIC2", SND_SOC_NOPM, 0, 0,
+ sn624x_pde_mic2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SUPPLY("PDE DMIC", SND_SOC_NOPM, 0, 0,
+ sn624x_pde_dmic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_DAC_E("FU HP", NULL, SND_SOC_NOPM, 0, 0,
+ sn624x_fu_hp_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_DAC_E("FU SPK", NULL, SND_SOC_NOPM, 0, 0,
+ sn624x_fu_spk_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("FU MIC2", NULL, SND_SOC_NOPM, 0, 0,
+ sn624x_fu_mic2_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("FU DMIC", NULL, SND_SOC_NOPM, 0, 0,
+ sn624x_fu_dmic_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_OUTPUT("SPK"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_INPUT("DMIC"),
+};
+
+static const struct snd_soc_dapm_route sn624x_sdca_audio_map[] = {
+ { "FU HP", NULL, "RX" },
+ { "FU HP", NULL, "PDE HP" },
+ { "HP", NULL, "FU HP" },
+
+ { "FU SPK", NULL, "SPK RX" },
+ { "FU SPK", NULL, "PDE SPK" },
+ { "SPK", NULL, "FU SPK" },
+
+ { "FU MIC2", NULL, "MIC2" },
+ { "FU MIC2", NULL, "PDE MIC2" },
+ /* UAJ headset capture also needs jack PDE03 (shared with HP) */
+ { "FU MIC2", NULL, "PDE HP" },
+ { "TX", NULL, "FU MIC2" },
+
+ { "FU DMIC", NULL, "DMIC" },
+ { "FU DMIC", NULL, "PDE DMIC" },
+ { "DMIC TX", NULL, "FU DMIC" },
+};
+
+static const struct snd_soc_dai_ops sn624x_sdca_ops = {
+ .hw_params = sn624x_sdca_pcm_hw_params,
+ .hw_free = sn624x_sdca_pcm_hw_free,
+ .set_stream = sn624x_sdca_set_sdw_stream,
+ .shutdown = sn624x_sdca_shutdown,
+};
+
+static struct snd_soc_dai_driver sn624x_sdca_dai[] = {
+ {
+ .name = "sn6242-sdca-aif",
+ .id = 0,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SN624X_STEREO_RATES,
+ .formats = SN624X_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SN624X_STEREO_RATES,
+ .formats = SN624X_FORMATS,
+ },
+ .ops = &sn624x_sdca_ops,
+ },
+ {
+ .name = "sn6242-sdca-aif2",
+ .id = 1,
+ .playback = {
+ .stream_name = "Speaker Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SN624X_STEREO_RATES,
+ .formats = SN624X_FORMATS,
+ },
+ .ops = &sn624x_sdca_ops,
+ },
+ {
+ .name = "sn6242-sdca-aif3",
+ .id = 2,
+ .capture = {
+ .stream_name = "DMic Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = SN624X_STEREO_RATES,
+ .formats = SN624X_FORMATS,
+ },
+ .ops = &sn624x_sdca_ops,
+ },
+};
+
+static const struct snd_soc_component_driver soc_sdca_dev_sn624x = {
+ .probe = sn624x_sdca_probe,
+ .dapm_widgets = sn624x_sdca_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(sn624x_sdca_dapm_widgets),
+ .dapm_routes = sn624x_sdca_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(sn624x_sdca_audio_map),
+ .set_jack = sn624x_sdca_set_jack_detect,
+ .endianness = 1,
+};
+
+int sn624x_sdca_init(struct device *dev, struct regmap *regmap,
+ struct sdw_slave *slave)
+{
+ struct sn624x_sdca_priv *sn624x;
+ int ret;
+
+ sn624x = devm_kzalloc(dev, sizeof(*sn624x), GFP_KERNEL);
+ if (!sn624x)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, sn624x);
+ sn624x->slave = slave;
+ sn624x->regmap = regmap;
+
+ ret = sn624x_parse_sdca_functions(sn624x);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&sn624x->disable_irq_lock);
+ INIT_DELAYED_WORK(&sn624x->jack_detect_work, sn624x_sdca_jack_detect_handler);
+
+ regcache_cache_only(sn624x->regmap, true);
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_sdca_dev_sn624x, sn624x_sdca_dai,
+ ARRAY_SIZE(sn624x_sdca_dai));
+ if (ret < 0)
+ return ret;
+
+ pm_runtime_set_autosuspend_delay(dev, 3000);
+ pm_runtime_use_autosuspend(dev);
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_enable(dev);
+
+ SN624X_TRC(dev, "snd_soc_register_component OK (DAI sn624x-sdca-aif)\n");
+ return 0;
+}
+
+static int sn624x_sdca_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *regmap;
+ int ret;
+
+ SN624X_DBG(&slave->dev, "sdw_probe: dev_num=%u status=%s\n",
+ slave->dev_num, sn624x_status_str(slave->status));
+
+ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave,
+ &sn624x_sdca_regmap,
+ &sn624x_sdca_mbq_cfg);
+ if (IS_ERR(regmap)) {
+ dev_err(&slave->dev, "sn624x: devm_regmap_init_sdw_mbq_cfg failed (%ld)\n",
+ PTR_ERR(regmap));
+ return PTR_ERR(regmap);
+ }
+
+ ret = sn624x_sdca_init(&slave->dev, regmap, slave);
+ if (ret < 0)
+ dev_err(&slave->dev, "sn624x: sn624x_sdca_init failed (%d)\n", ret);
+ else {
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev);
+
+ if (sn624x && sn624x_mask_sdca_irqs)
+ sn624x_sdca_sdca_irq_mask_all(sn624x);
+ SN624X_TRC(&slave->dev, "SoundWire probe finished OK\n");
+ }
+
+ return ret;
+}
+
+static void sn624x_sdca_sdw_remove(struct sdw_slave *slave)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(&slave->dev);
+
+ SN624X_TRC(&slave->dev, "SoundWire driver remove\n");
+ cancel_delayed_work_sync(&sn624x->jack_detect_work);
+ if (sn624x->first_hw_init)
+ pm_runtime_disable(&slave->dev);
+ mutex_destroy(&sn624x->disable_irq_lock);
+}
+
+static const struct sdw_device_id sn624x_sdca_id[] = {
+ SDW_SLAVE_ENTRY_EXT(0x0496, 0x6242, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x0496, 0x6244, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x0496, 0x6247, 0x3, 0x1, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, sn624x_sdca_id);
+
+static int sn624x_sdca_dev_suspend(struct device *dev)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev);
+
+ if (!sn624x->first_hw_init)
+ return 0;
+
+ regcache_cache_only(sn624x->regmap, true);
+
+ return 0;
+}
+
+static int sn624x_sdca_dev_system_suspend(struct device *dev)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev);
+
+ if (!sn624x->first_hw_init)
+ return 0;
+
+ cancel_delayed_work_sync(&sn624x->jack_detect_work);
+
+ mutex_lock(&sn624x->disable_irq_lock);
+ sn624x->disable_irq = true;
+ sn624x_sdca_jack_irq_mask(sn624x);
+ mutex_unlock(&sn624x->disable_irq_lock);
+
+ return sn624x_sdca_dev_suspend(dev);
+}
+
+static int sn624x_sdca_regmap_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev);
+ unsigned long time;
+
+ if (!sn624x->first_hw_init)
+ return 0;
+
+ SN624X_DBG(dev, "resume: unattach_request=%u\n", slave->unattach_request);
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(SN624X_PROBE_TIMEOUT_MS));
+ if (!time) {
+ dev_err(dev, "%s: initialization timed out\n", __func__);
+ sdw_show_ping_status(slave->bus, true);
+ return -ETIMEDOUT;
+ }
+
+regmap_sync:
+ slave->unattach_request = 0;
+ regcache_cache_only(sn624x->regmap, false);
+ regcache_sync(sn624x->regmap);
+
+ return 0;
+}
+
+static int sn624x_sdca_dev_resume(struct device *dev)
+{
+ return sn624x_sdca_regmap_resume(dev);
+}
+
+static int sn624x_sdca_dev_system_resume(struct device *dev)
+{
+ struct sn624x_sdca_priv *sn624x = dev_get_drvdata(dev);
+ int ret;
+
+ ret = sn624x_sdca_regmap_resume(dev);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&sn624x->disable_irq_lock);
+ if (sn624x->disable_irq) {
+ sn624x_sdca_jack_irq_unmask(sn624x);
+ sn624x->disable_irq = false;
+ }
+ mutex_unlock(&sn624x->disable_irq_lock);
+
+ if (sn624x->hs_jack && sn624x->hw_init) {
+ sn624x_sdca_jack_poll_and_report(sn624x);
+ sn624x_jack_schedule_poll(sn624x);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops sn624x_sdca_pm = {
+ SYSTEM_SLEEP_PM_OPS(sn624x_sdca_dev_system_suspend, sn624x_sdca_dev_system_resume)
+ RUNTIME_PM_OPS(sn624x_sdca_dev_suspend, sn624x_sdca_dev_resume, NULL)
+};
+
+static struct sdw_driver sn624x_sdca_sdw_driver = {
+ .driver = {
+ .name = "sn624x-sdca",
+ .pm = pm_ptr(&sn624x_sdca_pm),
+ },
+ .probe = sn624x_sdca_sdw_probe,
+ .remove = sn624x_sdca_sdw_remove,
+ .ops = &sn624x_sdca_slave_ops,
+ .id_table = sn624x_sdca_id,
+};
+module_sdw_driver(sn624x_sdca_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC SN624X SDCA SoundWire driver with UAJ jack support");
+MODULE_AUTHOR("Senary Semiconductor Corp.");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
+MODULE_SOFTDEP("pre: snd-soc-sdca");
diff --git a/sound/soc/codecs/sn624x-sdca.h b/sound/soc/codecs/sn624x-sdca.h
new file mode 100644
index 000000000000..db6e699d463c
--- /dev/null
+++ b/sound/soc/codecs/sn624x-sdca.h
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * sn624x-sdca.h -- SN624X SDCA ALSA SoC audio driver header
+ *
+ * Copyright(c) 2025 Senary Semiconductor Corp.
+ */
+
+#ifndef __SN624X_H__
+#define __SN624X_H__
+
+#include <linux/mutex.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/workqueue.h>
+#include <sound/sdca_function.h>
+#include <sound/soc.h>
+
+struct sn624x_sdca_priv {
+ struct regmap *regmap;
+ struct snd_soc_component *component;
+ struct sdw_slave *slave;
+ /* Parsed DisCo functions (init tables / defaults from ACPI) */
+ struct sdca_function_data *jack_func;
+ struct sdca_function_data *mic_func;
+ struct sdca_function_data *amp_func;
+ bool hw_init;
+ bool first_hw_init;
+ struct snd_soc_jack *hs_jack;
+ struct delayed_work jack_detect_work;
+ struct mutex disable_irq_lock;
+ bool disable_irq;
+ int jack_type;
+ int jack_type_last;
+ unsigned int scp_sdca_stat1;
+ unsigned int scp_sdca_stat2;
+};
+
+/* SoundWire data port numbers (slave prop source/sink port masks) */
+#define SN624X_PORT_JACK_PLAYBACK 1
+#define SN624X_PORT_SPEAKER_PLAYBACK 2
+#define SN624X_PORT_JACK_CAPTURE 4
+#define SN624X_PORT_DMIC_CAPTURE 5
+
+/* Order matches sn624x_sdca_dai[] .id */
+enum sn624x_dai_id {
+ SN624X_DAI_JACK = 0,
+ SN624X_DAI_SPEAKER = 1,
+ SN624X_DAI_DMIC = 2,
+};
+
+/*
+ * SDCA Function Numbers (from platform ACPI / device topology).
+ * Address with SDW_SDCA_CTL(fun, ent, ctl, ch) from sdw_registers.h.
+ */
+#define SN624X_FUNC_NUM_JACK_CODEC 0x01
+#define SN624X_FUNC_NUM_MIC_ARRAY 0x02
+#define SN624X_FUNC_NUM_SPEAKER_AMP 0x04
+
+/* Entity instance IDs (from platform ACPI / SN624X topology) */
+#define SN624X_SDCA_ENT_ENTITY0 0x00
+#define SN624X_SDCA_ENT_CS01 0x01
+#define SN624X_SDCA_ENT_CS02 0x02
+#define SN624X_SDCA_ENT_PDE03 0x03
+#define SN624X_SDCA_ENT_PDE04 0x04
+#define SN624X_SDCA_ENT_CRU05 0x05
+#define SN624X_SDCA_ENT_USER_FU10 0x10
+#define SN624X_SDCA_ENT_USER_FU14 0x14
+#define SN624X_SDCA_ENT_USER_FU1E 0x1e
+#define SN624X_UAJ_ENT_BLOCK 0x20
+#define SN624X_SDCA_ENT_GE35 0x1F
+/* UAJ power domains from ssdt26.dsl AF01 entity list (PDE 34 / PDE 47) */
+#define SN624X_UAJ_ENT_PDE47 SN624X_SDCA_ENT_PDE03
+#define SN624X_UAJ_ENT_PDE34 SN624X_SDCA_ENT_PDE04
+
+/* Control selectors (SDCA; same values as sound/sdca_function.h) */
+/* PDE power state selectors — use standard SDCA defines from sdca_function.h */
+#define SN624X_SDCA_CTL_FU_MUTE 0x01
+#define SN624X_SDCA_CTL_FU_VOLUME 0x02
+#define SN624X_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10
+#define SN624X_SDCA_CTL_CLUSTER_INDEX 0x10
+#define SN624X_SDCA_CTL_FUNCTION_STATUS 0x10 /* Entity 0 */
+#define SN624X_SDCA_CTL_SELECTED_MODE 0x01
+#define SN624X_SDCA_CTL_DETECTED_MODE 0x02
+
+/*
+ * Control Number / channel for multi-channel FU controls (mute/volume).
+ * Single-value controls (PDE power, rate, function status, …) pass 0
+ * directly to SDW_SDCA_CTL — no CH_0 alias.
+ */
+#define SN624X_SDCA_CH_L 0x01
+#define SN624X_SDCA_CH_R 0x02
+
+/* GE35 Selected_Mode / Detected_Mode (SDCA Table 153, UAJ §5.5) */
+#define SN624X_GE35_MODE_HEADPHONE 0x03
+#define SN624X_GE35_MODE_HEADSET 0x05
+
+/* Vendor UAJ SCP windows */
+#define SN624X_UAJ_CTL_INPUT_DELAY 0x30
+#define SN624X_UAJ_CTL_CHARGE_PUMP 0x31
+#define SN624X_UAJ_CTL_JACK_DETECT 0x32
+
+/*
+ * Vendor SCP addresses used only when ACPI
+ * mipi-sdca-function-initialization-table is missing (temporary fallback).
+ * Remove once platform DisCo ships a complete init table.
+ */
+#define SN624X_UAJ_CTL_IT33_MIC_BIAS 0x40400418
+#define SN624X_UAJ_CTL_IT31_MIC_BIAS 0x40400318
+#define SN624X_UAJ_CTL_FU41_CH1_MUTE 0x40400A09
+#define SN624X_UAJ_CTL_FU41_CH2_MUTE 0x40400A0A
+#define SN624X_UAJ_CTL_FU31_CH0_MUTE 0x40400C08
+#define SN624X_UAJ_CTL_FU32_CH0_MUTE 0x40400C88
+#define SN624X_UAJ_CTL_FU33_CH0_MUTE 0x40400D08
+#define SN624X_UAJ_CTL_FU36_CH1_MUTE 0x40400F09
+#define SN624X_UAJ_CTL_FU36_CH2_MUTE 0x40400F0A
+#define SN624X_UAJ_CTL_FU31_CH0_GAIN_HIGH 0x40402C58
+#define SN624X_UAJ_CTL_FU31_CH0_GAIN_LOW 0x40400C58
+#define SN624X_UAJ_CTL_FU32_CH0_GAIN_HIGH 0x40402CD8
+#define SN624X_UAJ_CTL_FU32_CH0_GAIN_LOW 0x40400CD8
+#define SN624X_UAJ_CTL_FU33_CH0_GAIN_HIGH 0x40402D58
+#define SN624X_UAJ_CTL_FU33_CH0_GAIN_LOW 0x40400D58
+#define SN624X_UAJ_CTL_INPUT_DELAY_TIME 0x00002124
+#define SN624X_UAJ_CTL_PORTA_CHARGE_PUMP 0x00002086
+#define SN624X_UAJ_DMIC_PPU11 0x40880380
+
+/* Jack poll interval (ms) */
+#define SN624X_JACK_POLL_MS 1000
+
+/* µs settle times for headphone <-> speaker route switch during active PCM */
+#define SN624X_ROUTE_AFTER_MUTE_US_MIN 5000
+#define SN624X_ROUTE_AFTER_MUTE_US_MAX 8000
+#define SN624X_ROUTE_AFTER_D3_US_MIN 10000
+#define SN624X_ROUTE_AFTER_D3_US_MAX 15000
+#define SN624X_ROUTE_BEFORE_ACTIVE_US_MIN 10000
+#define SN624X_ROUTE_BEFORE_ACTIVE_US_MAX 15000
+
+#define SN624X_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
+ SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000)
+#define SN624X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
+ SNDRV_PCM_FMTBIT_S24_LE)
+
+int sn624x_sdca_init(struct device *dev, struct regmap *regmap,
+ struct sdw_slave *slave);
+
+#endif /* __SN624X_H__ */
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 2/3] ASoC: sdw_utils: add Senary SN624x helpers and codec_info
2026-08-12 6:59 [PATCH 0/3] ASoC: add Senary SN624x SoundWire SDCA support qianghua wang
2026-08-12 6:59 ` [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver qianghua wang
@ 2026-08-12 6:59 ` qianghua wang
2026-08-12 6:59 ` [PATCH 3/3] ASoC: Intel: soc-acpi: add SN624x entries for PTL qianghua wang
2 siblings, 0 replies; 5+ messages in thread
From: qianghua wang @ 2026-08-12 6:59 UTC (permalink / raw)
To: linux-sound
Cc: broonie, pierre-louis.bossart, yung-chuan.liao, liam.r.girdwood,
peter.ujfalusi, kai.vehmanen, cezary.rojewski, bo.liu,
qianghua.wang
From: "qianghua.wang" <qianghua.wang@senarytech.com>
Register SN6242/6244/6247 in codec_info_list and add Senary-specific
jack/amp/dmic rtd helpers used by the generic sof_sdw machine driver.
Signed-off-by: qianghua.wang <qianghua.wang@senarytech.com>
---
include/sound/soc_sdw_utils.h | 20 ++
sound/soc/sdw_utils/Makefile | 4 +-
sound/soc/sdw_utils/soc_sdw_senary_amp.c | 80 +++++++
sound/soc/sdw_utils/soc_sdw_senary_dmic.c | 49 +++++
sound/soc/sdw_utils/soc_sdw_senary_sdca.c | 63 ++++++
.../soc_sdw_senary_sdca_jack_common.c | 197 ++++++++++++++++++
sound/soc/sdw_utils/soc_sdw_utils.c | 166 +++++++++++++++
7 files changed, 578 insertions(+), 1 deletion(-)
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_amp.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_dmic.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca.c
create mode 100644 sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c
diff --git a/include/sound/soc_sdw_utils.h b/include/sound/soc_sdw_utils.h
index 79c21966220b..e8bdc20a75f6 100644
--- a/include/sound/soc_sdw_utils.h
+++ b/include/sound/soc_sdw_utils.h
@@ -293,4 +293,24 @@ int asoc_sdw_es9356_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai
int asoc_sdw_es9356_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
int asoc_sdw_es9356_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai);
+/* Senary SN624x SDCA */
+int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_links,
+ struct asoc_sdw_codec_info *info,
+ bool playback);
+int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link);
+int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai);
+int asoc_sdw_senary_amp_init(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_links,
+ struct asoc_sdw_codec_info *info,
+ bool playback);
+int asoc_sdw_senary_amp_exit(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_link);
+int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai);
+int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd,
+ struct snd_soc_dai *dai);
+
#endif
diff --git a/sound/soc/sdw_utils/Makefile b/sound/soc/sdw_utils/Makefile
index 5ae8c69b8b98..1dbc00a2d64a 100644
--- a/sound/soc/sdw_utils/Makefile
+++ b/sound/soc/sdw_utils/Makefile
@@ -10,5 +10,7 @@ snd-soc-sdw-utils-y := soc_sdw_utils.o soc_sdw_dmic.o soc_sdw_rt_dmic.o \
soc_sdw_cs_amp.o \
soc_sdw_es9356.o \
soc_sdw_maxim.o \
- soc_sdw_ti_amp.o
+ soc_sdw_ti_amp.o \
+ soc_sdw_senary_sdca.o soc_sdw_senary_sdca_jack_common.o \
+ soc_sdw_senary_amp.o soc_sdw_senary_dmic.o
obj-$(CONFIG_SND_SOC_SDW_UTILS) += snd-soc-sdw-utils.o
diff --git a/sound/soc/sdw_utils/soc_sdw_senary_amp.c b/sound/soc/sdw_utils/soc_sdw_senary_amp.c
new file mode 100644
index 000000000000..9370402ab4c7
--- /dev/null
+++ b/sound/soc/sdw_utils/soc_sdw_senary_amp.c
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This file incorporates work covered by the following copyright notice:
+// Copyright (c) 2022 Intel Corporation
+// Copyright (c) 2024 Advanced Micro Devices, Inc.
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/soc_sdw_utils.h>
+
+static int senary_amp_add_device_props(struct device *sdw_dev)
+{
+ return 0;
+}
+
+int asoc_sdw_senary_amp_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
+{
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+ if (ctx->amp_dev1) {
+ device_remove_software_node(ctx->amp_dev1);
+ put_device(ctx->amp_dev1);
+ }
+
+ if (ctx->amp_dev2) {
+ device_remove_software_node(ctx->amp_dev2);
+ put_device(ctx->amp_dev2);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_exit, "SND_SOC_SDW_UTILS");
+
+int asoc_sdw_senary_amp_init(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_links,
+ struct asoc_sdw_codec_info *info,
+ bool playback)
+{
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct device *sdw_dev1, *sdw_dev2;
+ int ret;
+
+ /* Count amp number and do init on playback link only. */
+ if (!playback)
+ return 0;
+
+ info->amp_num++;
+
+ if (info->amp_num == 2) {
+ sdw_dev1 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name);
+ if (!sdw_dev1)
+ return -EPROBE_DEFER;
+
+ ret = senary_amp_add_device_props(sdw_dev1);
+ if (ret < 0) {
+ put_device(sdw_dev1);
+ return ret;
+ }
+ ctx->amp_dev1 = sdw_dev1;
+
+ sdw_dev2 = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[1].name);
+ if (!sdw_dev2)
+ return -EPROBE_DEFER;
+
+ ret = senary_amp_add_device_props(sdw_dev2);
+ if (ret < 0) {
+ put_device(sdw_dev2);
+ return ret;
+ }
+ ctx->amp_dev2 = sdw_dev2;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_amp_init, "SND_SOC_SDW_UTILS");
diff --git a/sound/soc/sdw_utils/soc_sdw_senary_dmic.c b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c
new file mode 100644
index 000000000000..378d2955bcbc
--- /dev/null
+++ b/sound/soc/sdw_utils/soc_sdw_senary_dmic.c
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This file incorporates work covered by the following copyright notice:
+// Copyright (c) 2024 Intel Corporation
+// Copyright (c) 2024 Advanced Micro Devices, Inc.
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc_sdw_utils.h>
+
+/* Card pin "Dmic" → codec input (name_prefix + " DMIC") */
+static const struct snd_soc_dapm_route senary_dmic_map[] = {
+ { "sn6242 DMIC", NULL, "Dmic" },
+};
+
+int asoc_sdw_senary_dmic_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ struct snd_soc_component *component;
+ char *mic_name;
+ int ret;
+
+ component = dai->component;
+
+ ret = snd_soc_dapm_add_routes(dapm, senary_dmic_map,
+ ARRAY_SIZE(senary_dmic_map));
+ if (ret) {
+ dev_err(card->dev, "senary dmic map addition failed: %d\n", ret);
+ return ret;
+ }
+
+ mic_name = devm_kasprintf(card->dev, GFP_KERNEL, "%s", component->name_prefix);
+ if (!mic_name)
+ return -ENOMEM;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s mic:%s-dmic", card->components,
+ mic_name);
+ if (!card->components)
+ return -ENOMEM;
+
+ dev_dbg(card->dev, "card->components: %s\n", card->components);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_dmic_rtd_init, "SND_SOC_SDW_UTILS");
diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c
new file mode 100644
index 000000000000..8b814d803654
--- /dev/null
+++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This file incorporates work covered by the following copyright notice:
+// Copyright (c) 2024 Intel Corporation.
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc_sdw_utils.h>
+
+/* dapm routes for SPK will be registered dynamically */
+static const struct snd_soc_dapm_route sn6242_spk_map[] = {
+ { "Speaker", NULL, "sn6242 SPK" },
+};
+
+/* Structure to map codec names to respective route arrays and sizes */
+struct codec_route_map {
+ const char *codec_name;
+ const struct snd_soc_dapm_route *route_map;
+ size_t route_size;
+};
+
+/* Codec route maps array */
+static const struct codec_route_map codec_routes[] = {
+ { "sn6242", sn6242_spk_map, ARRAY_SIZE(sn6242_spk_map) },
+};
+
+static const struct codec_route_map *get_codec_route_map(const char *dai_name)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(codec_routes); i++) {
+ if (str_has_prefix(dai_name, codec_routes[i].codec_name))
+ return &codec_routes[i];
+ }
+ return NULL;
+}
+
+int asoc_sdw_senary_sdca_spk_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ int ret;
+
+ /* Match by DAI name prefix (e.g. sn6242-sdca-aif2). */
+ const struct codec_route_map *route_map = get_codec_route_map(dai->name);
+
+ if (!route_map) {
+ dev_err(rtd->dev, "failed to get codec name and route map\n");
+ return -EINVAL;
+ }
+
+ /* Add routes */
+ ret = snd_soc_dapm_add_routes(dapm, route_map->route_map, route_map->route_size);
+ if (ret)
+ dev_err(rtd->dev, "failed to add rt sdca spk map: %d\n", ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_spk_rtd_init, "SND_SOC_SDW_UTILS");
diff --git a/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c
new file mode 100644
index 000000000000..fcf1bd5f35fe
--- /dev/null
+++ b/sound/soc/sdw_utils/soc_sdw_senary_sdca_jack_common.c
@@ -0,0 +1,197 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This file incorporates work covered by the following copyright notice:
+// Copyright (c) 2020 Intel Corporation
+// Copyright (c) 2024 Advanced Micro Devices, Inc.
+
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/control.h>
+#include <sound/soc.h>
+#include <sound/soc-acpi.h>
+#include <sound/soc-dapm.h>
+#include <sound/jack.h>
+#include <sound/soc_sdw_utils.h>
+
+/*
+ * jack_init / jack_exit / most of rtd_init follow soc_sdw_rt_sdca_jack_common.c
+ * (same SDCA headset machine-helper pattern). Vendor-specific pieces here are
+ * mainly the "senarytech,jd-src" property name and senary_sdca_jack_map[].
+ *
+ * Keep a Senary-local file for now to mirror the existing RT layout. Sharing
+ * parameterised SDCA jack helpers in snd-soc-sdw-utils (jd-src key + common
+ * init/exit/rtd_init boilerplate) is intentional follow-up work, out of scope
+ * for the sn624x codec series.
+ */
+
+/*
+ * Note this MUST be called before snd_soc_register_card(), so that the props
+ * are in place before the codec component driver's probe function parses them.
+ */
+static int senary_sdca_jack_add_codec_device_props(struct device *sdw_dev, unsigned long quirk)
+{
+ struct property_entry props[SOC_SDW_MAX_NO_PROPS] = {};
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ if (!SOC_SDW_JACK_JDSRC(quirk))
+ return 0;
+
+ props[0] = PROPERTY_ENTRY_U32("senarytech,jd-src", SOC_SDW_JACK_JDSRC(quirk));
+
+ fwnode = fwnode_create_software_node(props, NULL);
+ if (IS_ERR(fwnode))
+ return PTR_ERR(fwnode);
+
+ ret = device_add_software_node(sdw_dev, to_software_node(fwnode));
+
+ fwnode_handle_put(fwnode);
+
+ return ret;
+}
+
+static const struct snd_soc_dapm_route senary_sdca_jack_map[] = {
+ { "Headphone", NULL, "sn6242 HP" },
+ { "sn6242 MIC2", NULL, "Headset Mic" },
+};
+
+static struct snd_soc_jack_pin senary_sdca_jack_pins[] = {
+ {
+ .pin = "Headphone",
+ .mask = SND_JACK_HEADPHONE,
+ },
+ {
+ .pin = "Headset Mic",
+ .mask = SND_JACK_MICROPHONE,
+ },
+};
+
+static const char * const need_sdca_suffix[] = {
+ "sn6242", "sn6244", "sn6247"
+};
+
+int asoc_sdw_senary_sdca_jack_rtd_init(struct snd_soc_pcm_runtime *rtd, struct snd_soc_dai *dai)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct snd_soc_dapm_context *dapm = snd_soc_card_to_dapm(card);
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct snd_soc_component *component;
+ struct snd_soc_jack *jack;
+ int ret;
+ int i;
+
+ component = dai->component;
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s hs:%s",
+ card->components, component->name_prefix);
+ if (!card->components)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(need_sdca_suffix); i++) {
+ if (!strstr(component->name_prefix, need_sdca_suffix[i]))
+ continue;
+
+ card->components = devm_kasprintf(card->dev, GFP_KERNEL,
+ "%s-sdca", card->components);
+ if (!card->components)
+ return -ENOMEM;
+
+ ret = snd_soc_dapm_add_routes(dapm, senary_sdca_jack_map,
+ ARRAY_SIZE(senary_sdca_jack_map));
+ if (ret) {
+ dev_err(card->dev,
+ "senary sdca jack map addition failed: %d\n", ret);
+ return ret;
+ }
+ break;
+ }
+
+ if (i == ARRAY_SIZE(need_sdca_suffix)) {
+ dev_err(card->dev, "%s is not supported\n", component->name_prefix);
+ return -EINVAL;
+ }
+
+ ret = snd_soc_card_jack_new_pins(rtd->card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0 |
+ SND_JACK_BTN_1 | SND_JACK_BTN_2 |
+ SND_JACK_BTN_3,
+ &ctx->sdw_headset,
+ senary_sdca_jack_pins,
+ ARRAY_SIZE(senary_sdca_jack_pins));
+ if (ret) {
+ dev_err(rtd->card->dev, "Headset Jack creation failed: %d\n",
+ ret);
+ return ret;
+ }
+
+ jack = &ctx->sdw_headset;
+
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_0, KEY_PLAYPAUSE);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_1, KEY_VOICECOMMAND);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_2, KEY_VOLUMEUP);
+ snd_jack_set_key(jack->jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN);
+
+ ret = snd_soc_component_set_jack(component, jack, NULL);
+
+ if (ret)
+ dev_err(rtd->card->dev, "Headset Jack call-back failed: %d\n",
+ ret);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_rtd_init, "SND_SOC_SDW_UTILS");
+
+int asoc_sdw_senary_sdca_jack_exit(struct snd_soc_card *card, struct snd_soc_dai_link *dai_link)
+{
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+
+ if (!ctx->headset_codec_dev)
+ return 0;
+
+ if (!SOC_SDW_JACK_JDSRC(ctx->mc_quirk))
+ return 0;
+
+ device_remove_software_node(ctx->headset_codec_dev);
+ put_device(ctx->headset_codec_dev);
+ ctx->headset_codec_dev = NULL;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_exit, "SND_SOC_SDW_UTILS");
+
+/*
+ * Same control flow as asoc_sdw_rt_sdca_jack_init(); see file comment above
+ * about deferred helper sharing in snd-soc-sdw-utils.
+ */
+int asoc_sdw_senary_sdca_jack_init(struct snd_soc_card *card,
+ struct snd_soc_dai_link *dai_links,
+ struct asoc_sdw_codec_info *info,
+ bool playback)
+{
+ struct asoc_sdw_mc_private *ctx = snd_soc_card_get_drvdata(card);
+ struct device *sdw_dev;
+ int ret;
+
+ /*
+ * Jack detection should be only initialized once for headsets since
+ * the playback/capture is sharing the same jack
+ */
+ if (ctx->headset_codec_dev)
+ return 0;
+
+ sdw_dev = bus_find_device_by_name(&sdw_bus_type, NULL, dai_links->codecs[0].name);
+ if (!sdw_dev)
+ return -EPROBE_DEFER;
+
+ ret = senary_sdca_jack_add_codec_device_props(sdw_dev, ctx->mc_quirk);
+ if (ret < 0) {
+ put_device(sdw_dev);
+ return ret;
+ }
+ ctx->headset_codec_dev = sdw_dev;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS(asoc_sdw_senary_sdca_jack_init, "SND_SOC_SDW_UTILS");
diff --git a/sound/soc/sdw_utils/soc_sdw_utils.c b/sound/soc/sdw_utils/soc_sdw_utils.c
index d8db8fc5313e..a0c814fa4747 100644
--- a/sound/soc/sdw_utils/soc_sdw_utils.c
+++ b/sound/soc/sdw_utils/soc_sdw_utils.c
@@ -17,6 +17,15 @@ static const struct snd_soc_dapm_widget generic_dmic_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL),
};
+/* Senary SN624x UCM uses CaptureMixerElem "Dmic"; keep separate from generic DMIC. */
+static const struct snd_soc_dapm_widget sn624x_dmic_widgets[] = {
+ SND_SOC_DAPM_MIC("Dmic", NULL),
+};
+
+static const struct snd_kcontrol_new sn624x_dmic_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Dmic"),
+};
+
static const struct snd_soc_dapm_widget generic_jack_widgets[] = {
SND_SOC_DAPM_HP("Headphone", NULL),
SND_SOC_DAPM_MIC("Headset Mic", NULL),
@@ -1228,6 +1237,163 @@ struct asoc_sdw_codec_info codec_info_list[] = {
},
.dai_num = 1,
},
+ /*
+ * Senary SN6242/6244/6247: same SDCA jack/amp/mic topology; only part_id
+ * differs. name_prefix stays "sn6242" for UCM/DAI naming.
+ */
+ {
+ .vendor_id = 0x0496,
+ .part_id = 0x6244,
+ .name_prefix = "sn6242",
+ .ignore_internal_dmic = true,
+ .dais = {
+ {
+ .direction = {true, true},
+ .dai_name = "sn6242-sdca-aif",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .init = asoc_sdw_senary_sdca_jack_init,
+ .exit = asoc_sdw_senary_sdca_jack_exit,
+ .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init,
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ },
+ {
+ .direction = {true, false},
+ .dai_name = "sn6242-sdca-aif2",
+ .component_name = "sn6242",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},
+ .init = asoc_sdw_senary_amp_init,
+ .exit = asoc_sdw_senary_amp_exit,
+ .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init,
+ .controls = generic_spk_controls,
+ .num_controls = ARRAY_SIZE(generic_spk_controls),
+ .widgets = generic_spk_widgets,
+ .num_widgets = ARRAY_SIZE(generic_spk_widgets),
+ .quirk = SOC_SDW_CODEC_SPKR,
+ .quirk_exclude = true,
+ },
+ {
+ .direction = {false, true},
+ .dai_name = "sn6242-sdca-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_senary_dmic_rtd_init,
+ .widgets = sn624x_dmic_widgets,
+ .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets),
+ .controls = sn624x_dmic_controls,
+ .num_controls = ARRAY_SIZE(sn624x_dmic_controls),
+ .quirk = SOC_SDW_CODEC_MIC,
+ .quirk_exclude = true,
+ },
+ },
+ .dai_num = 3,
+ },
+ {
+ .vendor_id = 0x0496,
+ .part_id = 0x6247,
+ .name_prefix = "sn6242",
+ .ignore_internal_dmic = true,
+ .dais = {
+ {
+ .direction = {true, true},
+ .dai_name = "sn6242-sdca-aif",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .init = asoc_sdw_senary_sdca_jack_init,
+ .exit = asoc_sdw_senary_sdca_jack_exit,
+ .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init,
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ },
+ {
+ .direction = {true, false},
+ .dai_name = "sn6242-sdca-aif2",
+ .component_name = "sn6242",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},
+ .init = asoc_sdw_senary_amp_init,
+ .exit = asoc_sdw_senary_amp_exit,
+ .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init,
+ .controls = generic_spk_controls,
+ .num_controls = ARRAY_SIZE(generic_spk_controls),
+ .widgets = generic_spk_widgets,
+ .num_widgets = ARRAY_SIZE(generic_spk_widgets),
+ .quirk = SOC_SDW_CODEC_SPKR,
+ .quirk_exclude = true,
+ },
+ {
+ .direction = {false, true},
+ .dai_name = "sn6242-sdca-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_senary_dmic_rtd_init,
+ .widgets = sn624x_dmic_widgets,
+ .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets),
+ .controls = sn624x_dmic_controls,
+ .num_controls = ARRAY_SIZE(sn624x_dmic_controls),
+ .quirk = SOC_SDW_CODEC_MIC,
+ .quirk_exclude = true,
+ },
+ },
+ .dai_num = 3,
+ },
+ {
+ .vendor_id = 0x0496,
+ .part_id = 0x6242,
+ .name_prefix = "sn6242",
+ .ignore_internal_dmic = true,
+ .dais = {
+ {
+ .direction = {true, true},
+ .dai_name = "sn6242-sdca-aif",
+ .dai_type = SOC_SDW_DAI_TYPE_JACK,
+ .dailink = {SOC_SDW_JACK_OUT_DAI_ID, SOC_SDW_JACK_IN_DAI_ID},
+ .init = asoc_sdw_senary_sdca_jack_init,
+ .exit = asoc_sdw_senary_sdca_jack_exit,
+ .rtd_init = asoc_sdw_senary_sdca_jack_rtd_init,
+ .controls = generic_jack_controls,
+ .num_controls = ARRAY_SIZE(generic_jack_controls),
+ .widgets = generic_jack_widgets,
+ .num_widgets = ARRAY_SIZE(generic_jack_widgets),
+ },
+ {
+ .direction = {true, false},
+ .dai_name = "sn6242-sdca-aif2",
+ .component_name = "sn6242",
+ .dai_type = SOC_SDW_DAI_TYPE_AMP,
+ .dailink = {SOC_SDW_AMP_OUT_DAI_ID, SOC_SDW_UNUSED_DAI_ID},
+ .init = asoc_sdw_senary_amp_init,
+ .exit = asoc_sdw_senary_amp_exit,
+ .rtd_init = asoc_sdw_senary_sdca_spk_rtd_init,
+ .controls = generic_spk_controls,
+ .num_controls = ARRAY_SIZE(generic_spk_controls),
+ .widgets = generic_spk_widgets,
+ .num_widgets = ARRAY_SIZE(generic_spk_widgets),
+ .quirk = SOC_SDW_CODEC_SPKR,
+ .quirk_exclude = true,
+ },
+ {
+ .direction = {false, true},
+ .dai_name = "sn6242-sdca-aif3",
+ .dai_type = SOC_SDW_DAI_TYPE_MIC,
+ .dailink = {SOC_SDW_UNUSED_DAI_ID, SOC_SDW_DMIC_DAI_ID},
+ .rtd_init = asoc_sdw_senary_dmic_rtd_init,
+ .widgets = sn624x_dmic_widgets,
+ .num_widgets = ARRAY_SIZE(sn624x_dmic_widgets),
+ .controls = sn624x_dmic_controls,
+ .num_controls = ARRAY_SIZE(sn624x_dmic_controls),
+ .quirk = SOC_SDW_CODEC_MIC,
+ .quirk_exclude = true,
+ },
+ },
+ .dai_num = 3,
+ },
};
EXPORT_SYMBOL_NS(codec_info_list, "SND_SOC_SDW_UTILS");
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 3/3] ASoC: Intel: soc-acpi: add SN624x entries for PTL
2026-08-12 6:59 [PATCH 0/3] ASoC: add Senary SN624x SoundWire SDCA support qianghua wang
2026-08-12 6:59 ` [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver qianghua wang
2026-08-12 6:59 ` [PATCH 2/3] ASoC: sdw_utils: add Senary SN624x helpers and codec_info qianghua wang
@ 2026-08-12 6:59 ` qianghua wang
2 siblings, 0 replies; 5+ messages in thread
From: qianghua wang @ 2026-08-12 6:59 UTC (permalink / raw)
To: linux-sound
Cc: broonie, pierre-louis.bossart, yung-chuan.liao, liam.r.girdwood,
peter.ujfalusi, kai.vehmanen, cezary.rojewski, bo.liu,
qianghua.wang
From: "qianghua.wang" <qianghua.wang@senarytech.com>
Add Panther Lake SoundWire machine entries for Senary SN6242/6244/6247
on link 3, and select the SN624x SDCA codec from the SOF SoundWire
machine Kconfig.
Signed-off-by: qianghua.wang <qianghua.wang@senarytech.com>
---
sound/soc/intel/boards/Kconfig | 1 +
.../intel/common/soc-acpi-intel-ptl-match.c | 81 +++++++++++++++++++
2 files changed, 82 insertions(+)
diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig
index cddbd2aa424e..2f2692cd8135 100644
--- a/sound/soc/intel/boards/Kconfig
+++ b/sound/soc/intel/boards/Kconfig
@@ -519,6 +519,7 @@ config SND_SOC_INTEL_SOUNDWIRE_SOF_MACH
select SND_SOC_RT715_SDCA_SDW
select SND_SOC_RT721_SDCA_SDW
select SND_SOC_RT722_SDCA_SDW
+ select SND_SOC_SN624X_SDCA_SDW
select SND_SOC_RT1308_SDW
select SND_SOC_RT1308
select SND_SOC_RT1316_SDW
diff --git a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c
index f7694b2a2b02..88bf1552be77 100644
--- a/sound/soc/intel/common/soc-acpi-intel-ptl-match.c
+++ b/sound/soc/intel/common/soc-acpi-intel-ptl-match.c
@@ -438,6 +438,65 @@ static const struct snd_soc_acpi_link_adr ptl_sdw_rt712_vb_l3_rt1320_l3[] = {
};
/* this table is used when there is no I2S codec present */
+
+/*
+ * Senary SN624x: one multi-function device on link 3. Separate mach entries
+ * per part ID (adr_d[] is AND, not OR). ADR version nibble 0x3 matches the
+ * silicon SoundWire version. Amp endpoint is aggregated like the generic
+ * SOC_SDW_DAI_TYPE_AMP shape from find_acpi_adr_device().
+ */
+static const struct snd_soc_acpi_endpoint sn624x_endpoints[] = {
+ /* Jack */
+ {
+ .num = 0,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+ /* Amp */
+ {
+ .num = 1,
+ .aggregated = 1,
+ .group_position = 1,
+ .group_id = 1,
+ },
+ /* DMIC */
+ {
+ .num = 2,
+ .aggregated = 0,
+ .group_position = 0,
+ .group_id = 0,
+ },
+};
+
+#define SN624X_PTL_SINGLE_ADR(_sym, _adr) \
+static const struct snd_soc_acpi_adr_device _sym[] = { \
+ { \
+ .adr = (_adr), \
+ .num_endpoints = ARRAY_SIZE(sn624x_endpoints), \
+ .endpoints = sn624x_endpoints, \
+ .name_prefix = "sn6242", \
+ }, \
+}
+
+#define SN624X_PTL_LINK(_sym, _adr_arr) \
+static const struct snd_soc_acpi_link_adr _sym[] = { \
+ { \
+ .mask = BIT(3), \
+ .num_adr = ARRAY_SIZE(_adr_arr), \
+ .adr_d = _adr_arr, \
+ }, \
+ {} \
+}
+
+SN624X_PTL_SINGLE_ADR(sn6242_single_adr, 0x0003300496624201ull);
+SN624X_PTL_SINGLE_ADR(sn6244_single_adr, 0x0003300496624401ull);
+SN624X_PTL_SINGLE_ADR(sn6247_single_adr, 0x0003300496624701ull);
+
+SN624X_PTL_LINK(ptl_link_sn6242, sn6242_single_adr);
+SN624X_PTL_LINK(ptl_link_sn6244, sn6244_single_adr);
+SN624X_PTL_LINK(ptl_link_sn6247, sn6247_single_adr);
+
struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = {
/* Order Priority: mockup > most links > most bit link-mask > alphabetical */
{
@@ -531,6 +590,28 @@ struct snd_soc_acpi_mach snd_soc_acpi_intel_ptl_sdw_machines[] = {
.sof_tplg_filename = "sof-ptl-rt712-l3-rt1320-l3.tplg",
.get_function_tplg_files = sof_sdw_get_tplg_files,
},
+
+ {
+ .link_mask = BIT(3),
+ .links = ptl_link_sn6242,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-ptl-sn624x.tplg",
+ .get_function_tplg_files = sof_sdw_get_tplg_files,
+ },
+ {
+ .link_mask = BIT(3),
+ .links = ptl_link_sn6244,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-ptl-sn624x.tplg",
+ .get_function_tplg_files = sof_sdw_get_tplg_files,
+ },
+ {
+ .link_mask = BIT(3),
+ .links = ptl_link_sn6247,
+ .drv_name = "sof_sdw",
+ .sof_tplg_filename = "sof-ptl-sn624x.tplg",
+ .get_function_tplg_files = sof_sdw_get_tplg_files,
+ },
{},
};
EXPORT_SYMBOL_GPL(snd_soc_acpi_intel_ptl_sdw_machines);
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver
2026-08-12 6:59 ` [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver qianghua wang
@ 2026-08-12 8:27 ` Uwe Kleine-König
0 siblings, 0 replies; 5+ messages in thread
From: Uwe Kleine-König @ 2026-08-12 8:27 UTC (permalink / raw)
To: qianghua wang
Cc: linux-sound, broonie, pierre-louis.bossart, yung-chuan.liao,
liam.r.girdwood, peter.ujfalusi, kai.vehmanen, cezary.rojewski,
bo.liu
[-- Attachment #1: Type: text/plain, Size: 5171 bytes --]
Hello,
On Wed, Aug 12, 2026 at 02:59:42PM +0800, qianghua wang wrote:
> From: "qianghua.wang" <qianghua.wang@senarytech.com>
huh, why is here a . in your real name and in the real From header there
isn't?
> Add SoundWire SDCA driver for Senary SN624x multi-function codecs
> (jack, speaker amp, and DMIC). Extend the Senary MAINTAINERS entry
> for the new codec files only; Intel ACPI match tables remain under
> INTEL ASoC DRIVERS.
>
> Signed-off-by: qianghua.wang <qianghua.wang@senarytech.com>
> ---
> MAINTAINERS | 3 +
> sound/soc/codecs/Kconfig | 12 +
> sound/soc/codecs/Makefile | 2 +
> sound/soc/codecs/sn624x-sdca-sdw.c | 1873 ++++++++++++++++++++++++++++
> sound/soc/codecs/sn624x-sdca.h | 144 +++
> 5 files changed, 2034 insertions(+)
> create mode 100644 sound/soc/codecs/sn624x-sdca-sdw.c
> create mode 100644 sound/soc/codecs/sn624x-sdca.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index a21c79f3e436..f18e0ed039e9 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -29837,9 +29837,12 @@ F: tools/testing/selftests/cgroup/test_zswap.c
>
> SENARYTECH AUDIO CODEC DRIVER
> M: bo liu <bo.liu@senarytech.com>
> +M: qianghua.wang <qianghua.wang@senarytech.com>
> S: Maintained
> T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
> F: sound/hda/codecs/senarytech.c
> +F: sound/soc/codecs/sn624x*
> +F: sound/soc/sdw_utils/soc_sdw_senary*
>
> THE REST
> M: Linus Torvalds <torvalds@linux-foundation.org>
> diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
> index 4761ff4afb59..06e4a02f0c58 100644
> --- a/sound/soc/codecs/Kconfig
> +++ b/sound/soc/codecs/Kconfig
> @@ -238,6 +238,7 @@ config SND_SOC_ALL_CODECS
> imply SND_SOC_RT715_SDCA_SDW
> imply SND_SOC_RT721_SDCA_SDW
> imply SND_SOC_RT722_SDCA_SDW
> + imply SND_SOC_SN624X_SDCA_SDW
> imply SND_SOC_RT1308_SDW
> imply SND_SOC_RT1316_SDW
> imply SND_SOC_RT1318
> @@ -1968,6 +1969,17 @@ config SND_SOC_RT722_SDCA_SDW
> select REGMAP_SOUNDWIRE
> select REGMAP_SOUNDWIRE_MBQ
>
> +config SND_SOC_SN624X_SDCA_SDW
> + tristate "Senary SN624x SDCA Codec - SDW"
> + depends on SOUNDWIRE
> + select REGMAP_SOUNDWIRE
> + select REGMAP_SOUNDWIRE_MBQ
> + help
> + Enable support for Senary SN624x SDCA SoundWire codecs.
> + These multi-function devices provide headset jack, speaker
> + amplifier, and digital microphone endpoints on the SoundWire
> + bus.
> +
> config SND_SOC_RT715
> tristate
>
> diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
> index d2a689006d69..44b471d8b8c3 100644
> --- a/sound/soc/codecs/Makefile
> +++ b/sound/soc/codecs/Makefile
> @@ -285,6 +285,7 @@ snd-soc-rt715-y := rt715.o rt715-sdw.o
> snd-soc-rt715-sdca-y := rt715-sdca.o rt715-sdca-sdw.o
> snd-soc-rt721-sdca-y := rt721-sdca.o rt721-sdca-sdw.o
> snd-soc-rt722-sdca-y := rt722-sdca.o rt722-sdca-sdw.o
> +snd-soc-sn624x-sdca-y := sn624x-sdca-sdw.o
> snd-soc-rt9120-y := rt9120.o
> snd-soc-rt9123-y := rt9123.o
> snd-soc-rt9123p-y := rt9123p.o
> @@ -726,6 +727,7 @@ obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o
> obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o
> obj-$(CONFIG_SND_SOC_RT721_SDCA_SDW) += snd-soc-rt721-sdca.o
> obj-$(CONFIG_SND_SOC_RT722_SDCA_SDW) += snd-soc-rt722-sdca.o
> +obj-$(CONFIG_SND_SOC_SN624X_SDCA_SDW) += snd-soc-sn624x-sdca.o
> obj-$(CONFIG_SND_SOC_RT9120) += snd-soc-rt9120.o
> obj-$(CONFIG_SND_SOC_RT9123) += snd-soc-rt9123.o
> obj-$(CONFIG_SND_SOC_RT9123P) += snd-soc-rt9123p.o
> diff --git a/sound/soc/codecs/sn624x-sdca-sdw.c b/sound/soc/codecs/sn624x-sdca-sdw.c
> new file mode 100644
> index 000000000000..5129e883cddc
> --- /dev/null
> +++ b/sound/soc/codecs/sn624x-sdca-sdw.c
> @@ -0,0 +1,1873 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +//
> +// sn624x-sdw-sdca.c -- SN624X SDCA ALSA SoC SoundWire audio driver
> +//
> +// Copyright(c) 2021 Realtek Semiconductor Corp.
> +// Copyright(c) 2025 Senary Semiconductor Corp.
> +//
> +
> +#include <linux/atomic.h>
> +#include <linux/bitops.h>
> +#include <linux/delay.h>
> +#include <linux/find.h>
> +#include <linux/device.h>
> +#include <linux/errno.h>
> +#include <linux/jiffies.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/soundwire/sdw.h>
> +#include <linux/soundwire/sdw_registers.h>
> +#include <linux/soundwire/sdw_type.h>
Please rely on linux/soundwire/sdw.h to provide sdw_device_id and drop
including linux/mod_devicetable.h. mod_devicetable.h will go away soon.
> [...]
> +static const struct sdw_device_id sn624x_sdca_id[] = {
> + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6242, 0x3, 0x1, 0),
> + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6244, 0x3, 0x1, 0),
> + SDW_SLAVE_ENTRY_EXT(0x0496, 0x6247, 0x3, 0x1, 0),
> + {},
Please make this
{ }
(i.e. use a space between the braces and no trailing comma)
> +};
> +MODULE_DEVICE_TABLE(sdw, sn624x_sdca_id);
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-12 8:27 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-12 6:59 [PATCH 0/3] ASoC: add Senary SN624x SoundWire SDCA support qianghua wang
2026-08-12 6:59 ` [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver qianghua wang
2026-08-12 8:27 ` Uwe Kleine-König
2026-08-12 6:59 ` [PATCH 2/3] ASoC: sdw_utils: add Senary SN624x helpers and codec_info qianghua wang
2026-08-12 6:59 ` [PATCH 3/3] ASoC: Intel: soc-acpi: add SN624x entries for PTL qianghua wang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox