* [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; 6+ 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] 6+ 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; 6+ 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] 6+ 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; 6+ 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] 6+ 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; 6+ 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] 6+ 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
2026-08-12 8:59 ` reply " qianghua.wang
0 siblings, 1 reply; 6+ 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] 6+ messages in thread
* reply [PATCH 1/3] ASoC: codecs: add SN624x SDCA SoundWire driver
2026-08-12 8:27 ` Uwe Kleine-König
@ 2026-08-12 8:59 ` qianghua.wang
0 siblings, 0 replies; 6+ messages in thread
From: qianghua.wang @ 2026-08-12 8:59 UTC (permalink / raw)
To: 'Uwe Kleine-König'
Cc: linux-sound, broonie, pierre-louis.bossart, yung-chuan.liao,
liam.r.girdwood, peter.ujfalusi, kai.vehmanen, cezary.rojewski,
bo.liu
Hi Uwe:
Thanks for your comments. I will address them in v2.
Best regards
Qianghua Wang
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-08-12 9:35 UTC | newest]
Thread overview: 6+ 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 8:59 ` reply " 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 ` [PATCH 3/3] ASoC: Intel: soc-acpi: add SN624x entries for PTL qianghua wang
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.