* [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
@ 2026-07-20 9:06 shumingf
2026-07-20 14:38 ` Pierre-Louis Bossart
2026-07-20 16:21 ` Charles Keepax
0 siblings, 2 replies; 7+ messages in thread
From: shumingf @ 2026-07-20 9:06 UTC (permalink / raw)
To: broonie, lgirdwood
Cc: linux-sound, lars, flove, oder_chiou, jack.yu, derek.fang,
Shuming Fan
From: Shuming Fan <shumingf@realtek.com>
This patch adds the initial SDCA multi-function codec driver for the RT766 and RT767.
Signed-off-by: Shuming Fan <shumingf@realtek.com>
---
sound/soc/codecs/Kconfig | 8 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/rt766-sdca-sdw.c | 350 +++++++
sound/soc/codecs/rt766-sdca-sdw.h | 71 ++
sound/soc/codecs/rt766-sdca.c | 1521 +++++++++++++++++++++++++++++
sound/soc/codecs/rt766-sdca.h | 132 +++
6 files changed, 2084 insertions(+)
create mode 100644 sound/soc/codecs/rt766-sdca-sdw.c
create mode 100644 sound/soc/codecs/rt766-sdca-sdw.h
create mode 100644 sound/soc/codecs/rt766-sdca.c
create mode 100644 sound/soc/codecs/rt766-sdca.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 76e90144ea91..8b3690fe2b5a 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_RT766_SDCA_SDW
imply SND_SOC_RT1308_SDW
imply SND_SOC_RT1316_SDW
imply SND_SOC_RT1318
@@ -1978,6 +1979,13 @@ config SND_SOC_RT715_SDCA_SDW
select REGMAP_SOUNDWIRE
select REGMAP_SOUNDWIRE_MBQ
+config SND_SOC_RT766_SDCA_SDW
+ tristate "Realtek RT766 SDCA Codec - SDW"
+ depends on SOUNDWIRE
+ depends on SND_SOC_SDCA
+ select REGMAP_SOUNDWIRE
+ select REGMAP_SOUNDWIRE_MBQ
+
config SND_SOC_RT9120
tristate "Richtek RT9120 Stereo Class-D Amplifier"
depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index aa0396e5b575..bcc753bd0ca9 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -284,6 +284,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-rt766-sdca-y := rt766-sdca.o rt766-sdca-sdw.o
snd-soc-rt9120-y := rt9120.o
snd-soc-rt9123-y := rt9123.o
snd-soc-rt9123p-y := rt9123p.o
@@ -724,6 +725,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_RT766_SDCA_SDW) += snd-soc-rt766-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/rt766-sdca-sdw.c b/sound/soc/codecs/rt766-sdca-sdw.c
new file mode 100644
index 000000000000..94c5a7b0eac2
--- /dev/null
+++ b/sound/soc/codecs/rt766-sdca-sdw.c
@@ -0,0 +1,350 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// rt766-sdca-sdw.c -- rt766 SDCA ALSA SoC audio driver
+//
+// Copyright(c) 2026 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+#include "rt766-sdca.h"
+#include "rt766-sdca-sdw.h"
+
+#define RT766_PROBE_TIMEOUT 5000
+
+static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4:
+ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID:
+ case 0xc344 ... 0xc345:
+ case 0xc900:
+ case 0xc920:
+ case 0xd540 ... 0xd542:
+ case 0xf01e:
+ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1:
+ case 0x310100:
+ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END:
+ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END:
+ case RT766_MUTE_REG(UAJ, USER_FU41, 1):
+ case RT766_MUTE_REG(UAJ, USER_FU41, 2):
+ case RT766_VOLUME_REG(UAJ, USER_FU41, 1):
+ case RT766_VOLUME_REG(UAJ, USER_FU41, 2):
+ case RT766_MUTE_REG(UAJ, USER_FU36, 1):
+ case RT766_MUTE_REG(UAJ, USER_FU36, 2):
+ case RT766_VOLUME_REG(UAJ, USER_FU36, 1):
+ case RT766_VOLUME_REG(UAJ, USER_FU36, 2):
+ case RT766_PDE_REQ_REG(UAJ, PDE47):
+ case RT766_PDE_REQ_REG(UAJ, PDE34):
+ case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX):
+ case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX):
+ /* 0x40480000 */
+ case RT766_FUNC_STATUS_REG(UAJ):
+ /* 0x40481400 */
+ case RT766_PDE_ACTUAL_REG(UAJ, PDE47):
+ /* 0x40481480 */
+ case RT766_PDE_ACTUAL_REG(UAJ, PDE34):
+ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1):
+ case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2):
+ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE):
+ /* 0x40600490 */
+ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE):
+ case RT766_PDE_REQ_REG(MIC, PDE11):
+ case RT766_MUTE_REG(MIC, USER_FU113, 1):
+ case RT766_MUTE_REG(MIC, USER_FU113, 2):
+ case RT766_MUTE_REG(MIC, USER_FU113, 3):
+ case RT766_MUTE_REG(MIC, USER_FU113, 4):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 1):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 2):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 3):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 4):
+ /* 0x40880000 */
+ case RT766_FUNC_STATUS_REG(MIC):
+ case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX):
+ /* 0x40881500 */
+ case RT766_PDE_ACTUAL_REG(MIC, PDE11):
+ /* 0x40c80000 */
+ case RT766_FUNC_STATUS_REG(HID):
+ /* 0x40c80080 - 0x40c80098 */
+ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ...
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH):
+ case RT766_MUTE_REG(AMP, USER_FU21, 1):
+ case RT766_MUTE_REG(AMP, USER_FU21, 2):
+ case RT766_VOLUME_REG(AMP, USER_FU21, 1):
+ case RT766_VOLUME_REG(AMP, USER_FU21, 2):
+ case RT766_PDE_REQ_REG(AMP, PDE23):
+ /* 0x41080000 */
+ case RT766_FUNC_STATUS_REG(AMP):
+ case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER):
+ case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX):
+ /* 0x41081980 */
+ case RT766_PDE_ACTUAL_REG(AMP, PDE23):
+ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt766_sdca_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4:
+ case RT766_VERSION_ID ... RT766_BOND_LATCH_ID:
+ case 0xc344 ... 0xc345:
+ case 0xc900:
+ case 0xc920:
+ case 0xd540 ... 0xd542:
+ case 0xf01e:
+ case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1:
+ case 0x310100:
+ case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END:
+ case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END:
+ case RT766_FUNC_STATUS_REG(UAJ):
+ case RT766_PDE_ACTUAL_REG(UAJ, PDE47):
+ case RT766_PDE_ACTUAL_REG(UAJ, PDE34):
+ case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE):
+ case RT766_FUNC_STATUS_REG(MIC):
+ case RT766_PDE_ACTUAL_REG(MIC, PDE11):
+ case RT766_FUNC_STATUS_REG(HID):
+ case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ...
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH):
+ case RT766_FUNC_STATUS_REG(AMP):
+ case RT766_PDE_ACTUAL_REG(AMP, PDE23):
+ case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int rt766_sdca_mbq_size(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case RT766_VOLUME_REG(UAJ, USER_FU41, 1):
+ case RT766_VOLUME_REG(UAJ, USER_FU41, 2):
+ case RT766_VOLUME_REG(UAJ, USER_FU36, 1):
+ case RT766_VOLUME_REG(UAJ, USER_FU36, 2):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 1):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 2):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 3):
+ case RT766_VOLUME_REG(MIC, USER_FU113, 4):
+ case RT766_VOLUME_REG(AMP, USER_FU21, 1):
+ case RT766_VOLUME_REG(AMP, USER_FU21, 2):
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+static const struct regmap_sdw_mbq_cfg rt766_sdca_mbq_cfg = {
+ .mbq_size = rt766_sdca_mbq_size,
+};
+
+static const struct regmap_config rt766_sdca_regmap = {
+ .reg_bits = 32,
+ .val_bits = 16,
+ .readable_reg = rt766_sdca_readable_register,
+ .volatile_reg = rt766_sdca_volatile_register,
+ .reg_defaults = rt766_sdca_defaults,
+ .num_reg_defaults = ARRAY_SIZE(rt766_sdca_defaults),
+ .max_register = SDW_SDCA_MAX_REGISTER,
+ .cache_type = REGCACHE_MAPLE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int rt766_sdca_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev);
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ rt766->hw_init = false;
+
+ if (status == SDW_SLAVE_ATTACHED) {
+ if (rt766->hs_jack) {
+ /*
+ * Due to the SCP_SDCA_INTMASK will be cleared by any reset, and then
+ * if the device attached again, we will need to set the setting back.
+ * It could avoid losing the jack detection interrupt.
+ * This also could sync with the cache value as the rt766_sdca_jack_init set.
+ */
+ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK3,
+ SDW_SCP_SDCA_INTMASK_SDCA_16);
+ sdw_write_no_pm(rt766->slave, SDW_SCP_SDCA_INTMASK4,
+ SDW_SCP_SDCA_INTMASK_SDCA_24);
+ }
+ }
+
+ /*
+ * Perform initialization only if slave status is present and
+ * hw_init flag is false
+ */
+ if (rt766->hw_init || status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return rt766_sdca_io_init(&slave->dev, slave);
+}
+
+static int rt766_sdca_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int ret;
+
+ ret = sdw_slave_read_prop(slave);
+ if (ret < 0)
+ return ret;
+
+ prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY;
+ prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY;
+ /*
+ * SDCA interrupts are routed through SoundWire domain IRQ.
+ */
+ prop->use_domain_irq = true;
+
+ return 0;
+}
+
+static const struct sdw_slave_ops rt766_sdca_slave_ops = {
+ .read_prop = rt766_sdca_read_prop,
+ .update_status = rt766_sdca_update_status,
+};
+
+static int rt766_sdca_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *regmap;
+
+ /* Regmap Initialization */
+ regmap = devm_regmap_init_sdw_mbq_cfg(&slave->dev, slave,
+ &rt766_sdca_regmap, &rt766_sdca_mbq_cfg);
+ if (IS_ERR(regmap))
+ return PTR_ERR(regmap);
+
+ return rt766_sdca_init(&slave->dev, regmap, slave);
+}
+
+static void rt766_sdca_sdw_remove(struct sdw_slave *slave)
+{
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(&slave->dev);
+
+ pm_runtime_disable(&slave->dev);
+
+ mutex_destroy(&rt766->disable_irq_lock);
+}
+
+static const struct sdw_device_id rt766_sdca_id[] = {
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x766, 0x3, 0x1, 0),
+ SDW_SLAVE_ENTRY_EXT(0x025d, 0x767, 0x3, 0x1, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, rt766_sdca_id);
+
+static int rt766_sdca_dev_suspend(struct device *dev)
+{
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev);
+
+ if (!rt766->hw_init)
+ return 0;
+
+ regcache_cache_only(rt766->regmap, true);
+ return 0;
+}
+
+static int rt766_sdca_dev_system_suspend(struct device *dev)
+{
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev);
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ int ret1, ret2;
+
+ if (!rt766->hw_init)
+ return 0;
+
+ /*
+ * prevent new interrupts from being handled after the
+ * deferred work completes and before the parent disables
+ * interrupts on the link
+ */
+ mutex_lock(&rt766->disable_irq_lock);
+ rt766->disable_irq = true;
+ ret1 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK3,
+ SDW_SCP_SDCA_INTMASK_SDCA_16, 0);
+ ret2 = sdw_update_no_pm(slave, SDW_SCP_SDCA_INTMASK4,
+ SDW_SCP_SDCA_INTMASK_SDCA_24, 0);
+ mutex_unlock(&rt766->disable_irq_lock);
+
+ if (ret1 < 0 || ret2 < 0) {
+ /* log but don't prevent suspend from happening */
+ dev_dbg(&slave->dev, "%s: could not disable SDCA interrupts\n:", __func__);
+ }
+
+ return rt766_sdca_dev_suspend(dev);
+}
+
+static int rt766_sdca_dev_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev);
+ int ret;
+
+ if (!rt766->first_hw_init)
+ return 0;
+
+ if (!slave->unattach_request) {
+ mutex_lock(&rt766->disable_irq_lock);
+ if (rt766->disable_irq == true) {
+ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK3, SDW_SCP_SDCA_INTMASK_SDCA_16);
+ sdw_write_no_pm(slave, SDW_SCP_SDCA_INTMASK4, SDW_SCP_SDCA_INTMASK_SDCA_24);
+ rt766->disable_irq = false;
+ }
+ mutex_unlock(&rt766->disable_irq_lock);
+ goto regmap_sync;
+ }
+
+ ret = sdw_slave_wait_for_init(slave, RT766_PROBE_TIMEOUT);
+ if (ret) {
+ sdw_show_ping_status(slave->bus, true);
+ return ret;
+ }
+
+regmap_sync:
+ regcache_cache_only(rt766->regmap, false);
+ ret = regcache_sync(rt766->regmap);
+ if (ret) {
+ regcache_cache_only(rt766->regmap, true);
+ regcache_mark_dirty(rt766->regmap);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops rt766_sdca_pm = {
+ SYSTEM_SLEEP_PM_OPS(rt766_sdca_dev_system_suspend, rt766_sdca_dev_resume)
+ RUNTIME_PM_OPS(rt766_sdca_dev_suspend, rt766_sdca_dev_resume, NULL)
+};
+
+static struct sdw_driver rt766_sdca_sdw_driver = {
+ .driver = {
+ .name = "rt766-sdca",
+ .pm = pm_ptr(&rt766_sdca_pm),
+ },
+ .probe = rt766_sdca_sdw_probe,
+ .remove = rt766_sdca_sdw_remove,
+ .ops = &rt766_sdca_slave_ops,
+ .id_table = rt766_sdca_id,
+};
+module_sdw_driver(rt766_sdca_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/sound/soc/codecs/rt766-sdca-sdw.h b/sound/soc/codecs/rt766-sdca-sdw.h
new file mode 100644
index 000000000000..ae6e534d4ccc
--- /dev/null
+++ b/sound/soc/codecs/rt766-sdca-sdw.h
@@ -0,0 +1,71 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * rt766-sdca-sdw.h -- RT766 SDCA ALSA SoC audio driver header
+ *
+ * Copyright(c) 2026 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT766_SDW_H__
+#define __RT766_SDW_H__
+
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw_registers.h>
+
+static const struct reg_default rt766_sdca_defaults[] = {
+ /* 0x40400289 - 0x4040028a */
+ { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 },
+ { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 },
+ /* 0x40400291 - 0x40400292 */
+ { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 },
+ { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 },
+ /* 0x40400789 - 0x4040078a */
+ { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 },
+ { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 },
+ /* 0x40400791 - 0x40400792 */
+ { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 },
+ { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 },
+ /* 0x40401408 */
+ { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 },
+ /* 0x40401488 */
+ { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 },
+ /* 0x40480080 */
+ { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
+ /* 0x40480880 */
+ { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
+ /* 0x40600259 - 0x4060025a */
+ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 },
+ { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 },
+ /* 0x40600488 */
+ { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 },
+
+ /* 0x40801508 */
+ { RT766_PDE_REQ_REG(MIC, PDE11), 0x03 },
+ /* 0x40801809 - 0x4080180c */
+ { RT766_MUTE_REG(MIC, USER_FU113, 1), 0x01 },
+ { RT766_MUTE_REG(MIC, USER_FU113, 2), 0x01 },
+ { RT766_MUTE_REG(MIC, USER_FU113, 3), 0x01 },
+ { RT766_MUTE_REG(MIC, USER_FU113, 4), 0x01 },
+ /* 0x40801811 - 0x40801814 */
+ { RT766_VOLUME_REG(MIC, USER_FU113, 1), 0x0000 },
+ { RT766_VOLUME_REG(MIC, USER_FU113, 2), 0x0000 },
+ { RT766_VOLUME_REG(MIC, USER_FU113, 3), 0x0000 },
+ { RT766_VOLUME_REG(MIC, USER_FU113, 4), 0x0000 },
+ /* 0x40880900 */
+ { RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
+
+ /* 0x41000189 - 0x4100018a */
+ { RT766_MUTE_REG(AMP, USER_FU21, 1), 0x01 },
+ { RT766_MUTE_REG(AMP, USER_FU21, 2), 0x01 },
+ /* 0x41000191 - 0x41000192 */
+ { RT766_VOLUME_REG(AMP, USER_FU21, 1), 0x0000 },
+ { RT766_VOLUME_REG(AMP, USER_FU21, 2), 0x0000 },
+ /* 0x41001988 */
+ { RT766_PDE_REQ_REG(AMP, PDE23), 0x03 },
+ /* 0x41080200 */
+ { RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0x00 },
+ /* 0x41081080 */
+ { RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
+
+};
+
+#endif /* __RT766_SDW_H__ */
diff --git a/sound/soc/codecs/rt766-sdca.c b/sound/soc/codecs/rt766-sdca.c
new file mode 100644
index 000000000000..ac920736ee48
--- /dev/null
+++ b/sound/soc/codecs/rt766-sdca.c
@@ -0,0 +1,1521 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// rt766-sdca.c -- rt766 SDCA ALSA SoC audio driver
+//
+// Copyright(c) 2026 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/bitops.h>
+#include <sound/core.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw_registers.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/sdca_interrupts.h>
+#include <linux/slab.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+#include "rt766-sdca.h"
+#include "rt-sdw-common.h"
+
+static struct sdca_entity *rt766_find_entity_by_label(struct sdca_function_data *func,
+ const char *label)
+{
+ struct sdca_entity *entity = NULL;
+ int idx;
+
+ for (idx = 0; idx < func->num_entities; idx++) {
+ entity = &func->entities[idx];
+
+ if (!strcmp(entity->label, label))
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int rt766_sdca_btn_detect(struct sdca_interrupt *interrupt)
+{
+ struct rt766_sdca_priv *rt766 = interrupt->priv;
+ struct sdca_entity *ent_hid = interrupt->entity;
+ unsigned char *buf = NULL;
+ unsigned int offset, owner, length;
+ unsigned int det_mode, idx, val;
+ int ret;
+
+ ret = regmap_read(rt766->regmap,
+ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE),
+ &det_mode);
+ if (ret < 0)
+ goto io_error;
+
+ /* get current UMP message owner */
+ ret = regmap_read(rt766->regmap,
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER),
+ &owner);
+ if (ret < 0)
+ goto io_error;
+
+ /* if owner is device then there is no button event from device */
+ if (owner == 1)
+ return 0;
+
+ if (det_mode) {
+ /* read UMP message length */
+ ret = regmap_read(rt766->regmap,
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH),
+ &length);
+ if (ret < 0)
+ goto _end_btn_det_;
+
+ /* read UMP message offset */
+ ret = regmap_read(rt766->regmap,
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGEOFFSET),
+ &offset);
+ if (ret < 0)
+ goto _end_btn_det_;
+
+ buf = devm_kzalloc(&rt766->slave->dev, length, GFP_KERNEL);
+ if (!buf) {
+ dev_err(&rt766->slave->dev, "%s: alloc buf failed\n", __func__);
+ goto _end_btn_det_;
+ }
+
+ for (idx = 0; idx < length; idx++) {
+ ret = regmap_read(rt766->regmap,
+ RT766_BUF_ADDR_HID1 + offset + idx, &val);
+ if (ret < 0)
+ goto _end_btn_det_;
+ buf[idx] = val & 0xff;
+ }
+
+ if (ent_hid)
+ hid_input_report(ent_hid->hide.hid, HID_INPUT_REPORT,
+ buf, length, 1);
+ }
+
+_end_btn_det_:
+ if (buf)
+ devm_kfree(&rt766->slave->dev, buf);
+
+ /* Host is owner, so set back to device */
+ if (owner == 0) {
+ regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER), 0x01);
+ }
+
+ return 0;
+
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+ return ret;
+}
+
+static irqreturn_t rt766_sdca_irq_btn_handler(int irq, void *data)
+{
+ struct sdca_interrupt *interrupt = data;
+ struct rt766_sdca_priv *rt766 = interrupt->priv;
+
+ if (!rt766->hs_jack)
+ return IRQ_HANDLED;
+
+ if (!rt766->component->card || !rt766->component->card->instantiated)
+ return IRQ_HANDLED;
+
+ mutex_lock(&rt766->disable_irq_lock);
+ if (!rt766->disable_irq)
+ rt766_sdca_btn_detect(interrupt);
+ mutex_unlock(&rt766->disable_irq_lock);
+ return IRQ_HANDLED;
+}
+
+static int rt766_sdca_headset_detect(struct rt766_sdca_priv *rt766)
+{
+ unsigned int det_mode;
+ int ret;
+
+ /* get detected_mode */
+ ret = regmap_read(rt766->regmap,
+ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE),
+ &det_mode);
+ if (ret < 0)
+ goto io_error;
+
+ switch (det_mode) {
+ case 0x00:
+ rt766->jack_type = 0;
+ break;
+ case 0x03:
+ rt766->jack_type = SND_JACK_HEADPHONE;
+ break;
+ case 0x05:
+ rt766->jack_type = SND_JACK_HEADSET;
+ break;
+ }
+
+ /* write selected_mode */
+ if (det_mode) {
+ ret = regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE),
+ det_mode);
+ if (ret < 0)
+ goto io_error;
+ }
+
+ dev_dbg(&rt766->slave->dev,
+ "%s, detected_mode=0x%x\n", __func__, det_mode);
+
+ return 0;
+
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+ return ret;
+}
+
+static irqreturn_t rt766_sdca_irq_jd_handler(int irq, void *data)
+{
+ struct sdca_interrupt *interrupt = data;
+ struct rt766_sdca_priv *rt766 = interrupt->priv;
+
+ if (!rt766->hs_jack)
+ return IRQ_HANDLED;
+
+ if (!rt766->component->card || !rt766->component->card->instantiated)
+ return IRQ_HANDLED;
+
+ mutex_lock(&rt766->disable_irq_lock);
+ if (!rt766->disable_irq)
+ rt766_sdca_headset_detect(rt766);
+ mutex_unlock(&rt766->disable_irq_lock);
+
+ dev_dbg(&rt766->slave->dev,
+ "in %s, jack_type=%d\n", __func__, rt766->jack_type);
+
+ snd_soc_jack_report(rt766->hs_jack, rt766->jack_type, SND_JACK_HEADSET);
+ return IRQ_HANDLED;
+}
+
+static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
+ struct sdca_function_data *function,
+ struct snd_soc_component *component,
+ struct sdca_interrupt_info *info,
+ bool enabled)
+{
+ struct device *dev = &rt766->slave->dev;
+ struct sdca_interrupt *interrupt;
+ struct sdca_control *control;
+ struct sdca_entity *entity;
+ irq_handler_t handler;
+ int i, j, irq, ret;
+
+ for (i = 0; i < function->num_entities; i++) {
+ entity = &function->entities[i];
+
+ for (j = 0; j < entity->num_controls; j++) {
+ control = &entity->controls[j];
+ irq = control->interrupt_position;
+
+ switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+ case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
+ handler = rt766_sdca_irq_jd_handler;
+ break;
+ case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
+ handler = rt766_sdca_irq_btn_handler;
+ break;
+ default:
+ continue;
+ }
+
+ interrupt = &info->irqs[irq];
+
+ if (enabled) {
+ ret = sdca_irq_data_populate(dev, rt766->regmap, component,
+ function, entity, control,
+ interrupt);
+ if (ret)
+ return ret;
+
+ interrupt->priv = rt766;
+ ret = sdca_irq_request(dev, info, irq, interrupt->name,
+ handler, interrupt);
+ if (ret) {
+ dev_err(dev, "failed to request irq %s: %d\n",
+ interrupt->name, ret);
+ return ret;
+ }
+ dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name);
+ } else {
+ sdca_irq_free(dev, info, irq, interrupt->name, interrupt);
+ dev_dbg(dev, "Freeing IRQ %d\n", irq);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int rt766_sdca_set_jack_detect(struct snd_soc_component *component,
+ struct snd_soc_jack *hs_jack, void *data)
+{
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ int ret;
+
+ if (!rt766->uaj_func_data) {
+ dev_err(&rt766->slave->dev, "The SDCA UAJ function is not supported.\n");
+ return -EINVAL;
+ }
+
+ rt766->hs_jack = hs_jack;
+
+ if (!rt766->first_hw_init)
+ return 0;
+
+ ret = pm_runtime_resume_and_get(component->dev);
+ if (ret < 0) {
+ if (ret != -EACCES) {
+ dev_err(component->dev, "%s: failed to resume %d\n", __func__, ret);
+ return ret;
+ }
+
+ /* pm_runtime not enabled yet */
+ dev_dbg(component->dev, "%s: skipping jack init for now\n", __func__);
+ return 0;
+ }
+
+ /* disable interrupts if hs_jack is not set */
+ if (!rt766->hs_jack) {
+ if (rt766->uaj_func_data)
+ rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data,
+ rt766->component, rt766->irq_info, false);
+
+ if (rt766->hid_func_data)
+ rt766_sdca_irq_ctl(rt766, rt766->hid_func_data,
+ rt766->component, rt766->irq_info, false);
+ }
+
+ pm_runtime_put_autosuspend(component->dev);
+
+ return 0;
+}
+
+static int rt766_sdca_set_fu41_playback_ctl(struct rt766_sdca_priv *rt766)
+{
+ int err;
+ unsigned int ch_01, ch_02;
+
+ ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00;
+ ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 1), ch_01);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 2), ch_02);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int rt766_sdca_fu41_playback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = !rt766->fu41_mixer_l_mute;
+ ucontrol->value.integer.value[1] = !rt766->fu41_mixer_r_mute;
+ return 0;
+}
+
+static int rt766_sdca_fu41_playback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ int err;
+
+ if (rt766->fu41_mixer_l_mute == !ucontrol->value.integer.value[0] &&
+ rt766->fu41_mixer_r_mute == !ucontrol->value.integer.value[1])
+ return 0;
+
+ rt766->fu41_mixer_l_mute = !ucontrol->value.integer.value[0];
+ rt766->fu41_mixer_r_mute = !ucontrol->value.integer.value[1];
+
+ err = rt766_sdca_set_fu41_playback_ctl(rt766);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+static int rt766_sdca_set_fu36_capture_ctl(struct rt766_sdca_priv *rt766)
+{
+ int err;
+ unsigned int ch_01, ch_02;
+
+ ch_01 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_l_mute) ? 0x01 : 0x00;
+ ch_02 = (rt766->fu36_dapm_mute || rt766->fu36_mixer_r_mute) ? 0x01 : 0x00;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU36, 1), ch_01);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU36, 2), ch_02);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int rt766_sdca_fu36_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = !rt766->fu36_mixer_l_mute;
+ ucontrol->value.integer.value[1] = !rt766->fu36_mixer_r_mute;
+ return 0;
+}
+
+static int rt766_sdca_fu36_capture_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ int err;
+
+ if (rt766->fu36_mixer_l_mute == !ucontrol->value.integer.value[0] &&
+ rt766->fu36_mixer_r_mute == !ucontrol->value.integer.value[1])
+ return 0;
+
+ rt766->fu36_mixer_l_mute = !ucontrol->value.integer.value[0];
+ rt766->fu36_mixer_r_mute = !ucontrol->value.integer.value[1];
+ err = rt766_sdca_set_fu36_capture_ctl(rt766);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+static int rt766_sdca_set_fu21_playback_ctl(struct rt766_sdca_priv *rt766)
+{
+ int err;
+ unsigned int ch_01, ch_02;
+
+ ch_01 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_l_mute) ? 0x01 : 0x00;
+ ch_02 = (rt766->fu21_dapm_mute || rt766->fu21_mixer_r_mute) ? 0x01 : 0x00;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(AMP, USER_FU21, 1), ch_01);
+ if (err < 0)
+ return err;
+
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(AMP, USER_FU21, 2), ch_02);
+ if (err < 0)
+ return err;
+
+ return 0;
+}
+
+static int rt766_sdca_fu21_playback_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ ucontrol->value.integer.value[0] = !rt766->fu21_mixer_l_mute;
+ ucontrol->value.integer.value[1] = !rt766->fu21_mixer_r_mute;
+ return 0;
+}
+
+static int rt766_sdca_fu21_playback_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ int err;
+
+ if (rt766->fu21_mixer_l_mute == !ucontrol->value.integer.value[0] &&
+ rt766->fu21_mixer_r_mute == !ucontrol->value.integer.value[1])
+ return 0;
+
+ rt766->fu21_mixer_l_mute = !ucontrol->value.integer.value[0];
+ rt766->fu21_mixer_r_mute = !ucontrol->value.integer.value[1];
+
+ err = rt766_sdca_set_fu21_playback_ctl(rt766);
+ if (err < 0)
+ return err;
+
+ return 1;
+}
+
+static int rt766_sdca_set_fu113_capture_ctl(struct rt766_sdca_priv *rt766)
+{
+ int err, i;
+ unsigned int ch_mute;
+
+ for (i = 0; i < ARRAY_SIZE(rt766->fu113_mixer_mute); i++) {
+ ch_mute = (rt766->fu113_dapm_mute || rt766->fu113_mixer_mute[i]) ? 0x01 : 0x00;
+ err = regmap_write(rt766->regmap, RT766_MUTE_REG(MIC, USER_FU113, 1) + i,
+ ch_mute);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int rt766_sdca_fu113_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ rt766->fu113_dapm_mute = false;
+ rt766_sdca_set_fu113_capture_ctl(rt766);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ rt766->fu113_dapm_mute = true;
+ rt766_sdca_set_fu113_capture_ctl(rt766);
+ break;
+ }
+ return 0;
+}
+
+static int rt766_sdca_dmic_set_gain_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ struct rt_sdca_dmic_kctrl_priv *p =
+ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ const unsigned int interval_offset = 0xc0;
+ unsigned int regvalue, ctl, i;
+
+ /* check all channels */
+ for (i = 0; i < p->count; i++) {
+ regmap_read(rt766->regmap, p->reg_base + i, ®value);
+ ctl = p->max - (((0x1e00 - regvalue) & 0xffff) / interval_offset);
+
+ ucontrol->value.integer.value[i] = ctl;
+ }
+
+ return 0;
+}
+
+static int rt766_sdca_dmic_set_gain_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt_sdca_dmic_kctrl_priv *p =
+ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ const unsigned int interval_offset = 0xc0;
+ unsigned int gain_val[4];
+ unsigned int i, changed = 0;
+ unsigned int regvalue[4];
+ int err;
+
+ /* check all channels */
+ for (i = 0; i < p->count; i++) {
+ regmap_read(rt766->regmap, p->reg_base + i, ®value[i]);
+
+ gain_val[i] = ucontrol->value.integer.value[i];
+ if (gain_val[i] > p->max)
+ gain_val[i] = p->max;
+
+ gain_val[i] = 0x1e00 - ((p->max - gain_val[i]) * interval_offset);
+ gain_val[i] &= 0xffff;
+
+ if (regvalue[i] != gain_val[i])
+ changed = 1;
+ }
+
+ if (!changed)
+ return 0;
+
+ for (i = 0; i < p->count; i++) {
+ err = regmap_write(rt766->regmap, p->reg_base + i, gain_val[i]);
+ if (err < 0)
+ dev_err(&rt766->slave->dev, "0x%08x can't be set\n", p->reg_base + i);
+ }
+
+ return changed;
+}
+
+static int rt766_sdca_dmic_fu113_capture_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ unsigned int i;
+
+ for (i = 0; i < 4; i++)
+ ucontrol->value.integer.value[i] = !rt766->fu113_mixer_mute[i];
+ return 0;
+}
+
+static int rt766_sdca_dmic_fu113_capture_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_component *component = snd_kcontrol_chip(kcontrol);
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ int err, changed = 0, i;
+
+ for (i = 0; i < 4; i++) {
+ if (rt766->fu113_mixer_mute[i] != !ucontrol->value.integer.value[i])
+ changed = 1;
+ rt766->fu113_mixer_mute[i] = !ucontrol->value.integer.value[i];
+ }
+
+ err = rt766_sdca_set_fu113_capture_ctl(rt766);
+ if (err < 0)
+ return err;
+ return changed;
+}
+
+static int rt766_sdca_fu41_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ rt766->fu41_dapm_mute = false;
+ rt766_sdca_set_fu41_playback_ctl(rt766);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ rt766->fu41_dapm_mute = true;
+ rt766_sdca_set_fu41_playback_ctl(rt766);
+ break;
+ }
+ return 0;
+}
+
+static int rt766_sdca_pde47_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ const struct sdca_entity *entity = NULL;
+ unsigned char ps0 = 0x0, ps3 = 0x3;
+ int from_ps, to_ps;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps0);
+ from_ps = ps3;
+ to_ps = ps0;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps3);
+ from_ps = ps0;
+ to_ps = ps3;
+ break;
+ }
+
+ entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
+ ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
+ RT766_FUNC_NUM_UAJ,
+ RT766_SDCA_ENT_PDE47,
+ from_ps, to_ps,
+ entity ? entity->pde.max_delay : NULL,
+ entity ? entity->pde.num_max_delay : 0);
+ if (ret)
+ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
+ __func__, from_ps, to_ps, ret);
+
+ return ret;
+}
+
+static int rt766_sdca_fu36_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ rt766->fu36_dapm_mute = false;
+ rt766_sdca_set_fu36_capture_ctl(rt766);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ rt766->fu36_dapm_mute = true;
+ rt766_sdca_set_fu36_capture_ctl(rt766);
+ break;
+ }
+ return 0;
+}
+
+static int rt766_sdca_pde34_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ const struct sdca_entity *entity = NULL;
+ unsigned char ps0 = 0x0, ps3 = 0x3;
+ int from_ps, to_ps;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps0);
+ from_ps = ps3;
+ to_ps = ps0;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps3);
+ from_ps = ps0;
+ to_ps = ps3;
+ break;
+ }
+
+ entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 34");
+ ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
+ RT766_FUNC_NUM_UAJ,
+ RT766_SDCA_ENT_PDE34,
+ from_ps, to_ps,
+ entity ? entity->pde.max_delay : NULL,
+ entity ? entity->pde.num_max_delay : 0);
+ if (ret)
+ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
+ __func__, from_ps, to_ps, ret);
+
+ return ret;
+}
+
+static int rt766_sdca_fu21_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ rt766->fu21_dapm_mute = false;
+ rt766_sdca_set_fu21_playback_ctl(rt766);
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ rt766->fu21_dapm_mute = true;
+ rt766_sdca_set_fu21_playback_ctl(rt766);
+ break;
+ }
+ return 0;
+}
+
+static int rt766_sdca_pde23_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ const struct sdca_entity *entity = NULL;
+ unsigned char ps0 = 0x0, ps3 = 0x3;
+ int from_ps, to_ps;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(AMP, PDE23), ps0);
+ from_ps = ps3;
+ to_ps = ps0;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(AMP, PDE23), ps3);
+ from_ps = ps0;
+ to_ps = ps3;
+ break;
+ }
+
+ entity = rt766_find_entity_by_label(rt766->sa_func_data, "PDE 23");
+ ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
+ RT766_FUNC_NUM_AMP,
+ RT766_SDCA_ENT_PDE23,
+ from_ps, to_ps,
+ entity ? entity->pde.max_delay : NULL,
+ entity ? entity->pde.num_max_delay : 0);
+ if (ret)
+ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
+ __func__, from_ps, to_ps, ret);
+
+ return ret;
+}
+
+static int rt766_sdca_pde11_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ const struct sdca_entity *entity = NULL;
+ unsigned char ps0 = 0x0, ps3 = 0x3;
+ int from_ps, to_ps;
+ int ret;
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC, PDE11), ps0);
+ from_ps = ps3;
+ to_ps = ps0;
+ break;
+ case SND_SOC_DAPM_PRE_PMD:
+ regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC, PDE11), ps3);
+ from_ps = ps0;
+ to_ps = ps3;
+ break;
+ }
+
+ entity = rt766_find_entity_by_label(rt766->sm_func_data, "PDE 11");
+ ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
+ RT766_FUNC_NUM_MIC,
+ RT766_SDCA_ENT_PDE11,
+ from_ps, to_ps,
+ entity ? entity->pde.max_delay : NULL,
+ entity ? entity->pde.num_max_delay : 0);
+ if (ret)
+ dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
+ __func__, from_ps, to_ps, ret);
+
+ return ret;
+}
+
+static int rt766_dmic_fu_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ struct rt_sdca_dmic_kctrl_priv *p =
+ (struct rt_sdca_dmic_kctrl_priv *)kcontrol->private_value;
+
+ if (p->max == 1)
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+ else
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = p->count;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = p->max;
+ return 0;
+}
+
+static const char * const rt766_rx_data_ch_select[] = {
+ "L,R",
+ "R,L",
+ "L,L",
+ "R,R",
+ "L,L+R",
+ "R,L+R",
+ "L+R,L",
+ "L+R,R",
+ "L+R,L+R",
+};
+
+static SOC_ENUM_SINGLE_DECL(rt766_rx_data_ch_enum,
+ RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER), 0,
+ rt766_rx_data_ch_select);
+
+static const DECLARE_TLV_DB_SCALE(hp_vol_tlv, -9525, 75, 0);
+static const DECLARE_TLV_DB_SCALE(spk_vol_tlv, -6525, 75, 0);
+static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, -1725, 75, 0);
+static const DECLARE_TLV_DB_SCALE(boost_vol_tlv, -200, 200, 0);
+
+#define RT766_P75DB_STEP 0xC0 /* 0.75 dB in Q7.8 format */
+#define RT766_2DB_STEP 0x200 /* 2 dB in Q7.8 format */
+#define RT766_HP_VOL_MIN (-127) /* -95.25 dB / 0.75 dB step */
+#define RT766_HS_VOL_MIN (-23) /* -17.25 dB / 0.75 dB step */
+#define RT766_HS_BOOST_VOL_MIN (-1) /* -2 dB / 2 dB step */
+#define RT766_SPK_VOL_MIN (-87) /* -65.25 dB / 0.75 dB step */
+#define RT766_P_VOL_MAX 0 /* 0 dB / 0.75 dB step */
+#define RT766_HS_VOL_MAX 40 /* 30 dB / 0.75 dB step */
+#define RT766_HS_BOOST_VOL_MAX 20 /* 40 dB / 2 dB step */
+
+static const struct snd_kcontrol_new rt766_sdca_controls[] = {
+ SOC_DOUBLE_EXT("FU41 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0,
+ rt766_sdca_fu41_playback_get, rt766_sdca_fu41_playback_put),
+ SDCA_DOUBLE_Q78_TLV("FU41 Playback Volume",
+ RT766_VOLUME_REG(UAJ, USER_FU41, 1),
+ RT766_VOLUME_REG(UAJ, USER_FU41, 2),
+ RT766_HP_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, hp_vol_tlv),
+ SOC_DOUBLE_EXT("FU36 Capture Switch", SND_SOC_NOPM, 0, 1, 1, 0,
+ rt766_sdca_fu36_capture_get, rt766_sdca_fu36_capture_put),
+ SDCA_DOUBLE_Q78_TLV("FU36 Capture Volume",
+ RT766_VOLUME_REG(UAJ, USER_FU36, 1),
+ RT766_VOLUME_REG(UAJ, USER_FU36, 2),
+ RT766_HS_VOL_MIN, RT766_HS_VOL_MAX, RT766_P75DB_STEP, mic_vol_tlv),
+ SDCA_DOUBLE_Q78_TLV("FU33 Boost Volume",
+ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1),
+ RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2),
+ RT766_HS_BOOST_VOL_MIN, RT766_HS_BOOST_VOL_MAX, RT766_2DB_STEP, boost_vol_tlv),
+
+ SOC_DOUBLE_EXT("FU21 Playback Switch", SND_SOC_NOPM, 0, 1, 1, 0,
+ rt766_sdca_fu21_playback_get, rt766_sdca_fu21_playback_put),
+ SDCA_DOUBLE_Q78_TLV("FU21 Playback Volume",
+ RT766_VOLUME_REG(AMP, USER_FU21, 1),
+ RT766_VOLUME_REG(AMP, USER_FU21, 2),
+ RT766_SPK_VOL_MIN, RT766_P_VOL_MAX, RT766_P75DB_STEP, spk_vol_tlv),
+ SOC_ENUM("RX Channel Select", rt766_rx_data_ch_enum),
+
+ RT_SDCA_FU_CTRL("FU113 Capture Switch",
+ RT766_MUTE_REG(MIC, USER_FU113, 1), 1, 1, 4, rt766_dmic_fu_info,
+ rt766_sdca_dmic_fu113_capture_get, rt766_sdca_dmic_fu113_capture_put),
+ RT_SDCA_EXT_TLV("FU113 Capture Volume",
+ RT766_VOLUME_REG(MIC, USER_FU113, 1),
+ rt766_sdca_dmic_set_gain_get, rt766_sdca_dmic_set_gain_put,
+ 4, 0x3f, mic_vol_tlv, rt766_dmic_fu_info),
+};
+
+static const struct snd_soc_dapm_widget rt766_sdca_dapm_widgets[] = {
+ /* UAJ */
+ SND_SOC_DAPM_OUTPUT("HP"),
+ SND_SOC_DAPM_INPUT("MIC2"),
+ SND_SOC_DAPM_SUPPLY("PDE 47", SND_SOC_NOPM, 0, 0,
+ rt766_sdca_pde47_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SUPPLY("PDE 34", SND_SOC_NOPM, 0, 0,
+ rt766_sdca_pde34_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_DAC_E("FU 41", NULL, SND_SOC_NOPM, 0, 0,
+ rt766_sdca_fu41_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("FU 36", NULL, SND_SOC_NOPM, 0, 0,
+ rt766_sdca_fu36_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("DP12TX", "DP12 Capture", 0, SND_SOC_NOPM, 0, 0),
+
+ /* AMP */
+ SND_SOC_DAPM_OUTPUT("SPOL"),
+ SND_SOC_DAPM_OUTPUT("SPOR"),
+ SND_SOC_DAPM_DAC_E("FU 21", NULL, SND_SOC_NOPM, 0, 0,
+ rt766_sdca_fu21_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_SUPPLY("PDE 23", SND_SOC_NOPM, 0, 0,
+ rt766_sdca_pde23_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0),
+
+ /* DMIC */
+ SND_SOC_DAPM_INPUT("DMIC1"),
+ SND_SOC_DAPM_INPUT("DMIC2"),
+ SND_SOC_DAPM_SUPPLY("PDE 11", SND_SOC_NOPM, 0, 0,
+ rt766_sdca_pde11_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_ADC_E("FU 113", NULL, SND_SOC_NOPM, 0, 0,
+ rt766_sdca_fu113_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+ SND_SOC_DAPM_AIF_OUT("DP8TX", "DP8 Capture", 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_soc_dapm_route rt766_sdca_audio_map[] = {
+ { "FU 41", NULL, "DP3RX" },
+ { "DP12TX", NULL, "FU 36" },
+ { "FU 36", NULL, "PDE 34" },
+ { "FU 36", NULL, "MIC2" },
+ { "HP", NULL, "PDE 47" },
+ { "HP", NULL, "FU 41" },
+
+ { "FU 21", NULL, "DP1RX" },
+ { "FU 21", NULL, "PDE 23" },
+ { "SPOL", NULL, "FU 21" },
+ { "SPOR", NULL, "FU 21" },
+
+ {"DP8TX", NULL, "FU 113"},
+ {"FU 113", NULL, "PDE 11"},
+ {"FU 113", NULL, "DMIC1"},
+ {"FU 113", NULL, "DMIC2"},
+};
+
+static int rt766_sdca_probe(struct snd_soc_component *component)
+{
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ struct device *dev = &rt766->slave->dev;
+ int ret;
+
+ rt766->component = component;
+
+ ret = pm_runtime_resume(component->dev);
+ if (ret < 0 && ret != -EACCES)
+ return ret;
+
+ if (rt766->uaj_func_data) {
+ dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq);
+
+ rt766->irq_info = sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq);
+ if (IS_ERR(rt766->irq_info))
+ return PTR_ERR(rt766->irq_info);
+
+ ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data,
+ component, rt766->irq_info, true);
+ if (ret < 0) {
+ dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret);
+ return ret;
+ }
+
+ if (rt766->hid_func_data) {
+ ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data,
+ component, rt766->irq_info, true);
+ if (ret < 0) {
+ dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void rt766_sdca_remove(struct snd_soc_component *component)
+{
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+
+ sdca_irq_cleanup(component->dev, rt766->uaj_func_data, rt766->irq_info);
+ sdca_irq_cleanup(component->dev, rt766->hid_func_data, rt766->irq_info);
+}
+
+static const struct snd_soc_component_driver soc_sdca_dev_rt766 = {
+ .probe = rt766_sdca_probe,
+ .remove = rt766_sdca_remove,
+ .controls = rt766_sdca_controls,
+ .num_controls = ARRAY_SIZE(rt766_sdca_controls),
+ .dapm_widgets = rt766_sdca_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(rt766_sdca_dapm_widgets),
+ .dapm_routes = rt766_sdca_audio_map,
+ .num_dapm_routes = ARRAY_SIZE(rt766_sdca_audio_map),
+ .set_jack = rt766_sdca_set_jack_detect,
+ .endianness = 1,
+};
+
+static int rt766_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 rt766_sdca_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ snd_soc_dai_set_dma_data(dai, substream, NULL);
+}
+
+static int rt766_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 rt766_sdca_priv *rt766 = 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;
+ int retval, port, num_channels;
+ unsigned int sampling_rate;
+
+ dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id);
+ sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!sdw_stream)
+ return -EINVAL;
+
+ if (!rt766->slave)
+ return -EINVAL;
+
+ /* SoundWire specific configuration */
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ direction = SDW_DATA_DIR_RX;
+ if (dai->id == RT766_AIF1)
+ port = 3;
+ else if (dai->id == RT766_AIF2)
+ port = 1;
+ else
+ return -EINVAL;
+ } else {
+ direction = SDW_DATA_DIR_TX;
+ if (dai->id == RT766_AIF1)
+ port = 12;
+ else if (dai->id == RT766_AIF3)
+ port = 8;
+ else
+ return -EINVAL;
+ }
+
+ stream_config.frame_rate = params_rate(params);
+ stream_config.ch_count = params_channels(params);
+ stream_config.bps = snd_pcm_format_width(params_format(params));
+ stream_config.direction = direction;
+
+ num_channels = params_channels(params);
+ port_config.ch_mask = GENMASK(num_channels - 1, 0);
+ port_config.num = port;
+
+ retval = sdw_stream_add_slave(rt766->slave, &stream_config,
+ &port_config, 1, sdw_stream);
+ if (retval) {
+ dev_err(dai->dev, "%s: Unable to configure port\n", __func__);
+ return retval;
+ }
+
+ if (params_channels(params) > 16) {
+ dev_err(component->dev, "%s: Unsupported channels %d\n",
+ __func__, params_channels(params));
+ return -EINVAL;
+ }
+
+ /* sampling rate configuration */
+ switch (params_rate(params)) {
+ case 44100:
+ sampling_rate = RT766_SDCA_RATE_44100HZ;
+ break;
+ case 48000:
+ sampling_rate = RT766_SDCA_RATE_48000HZ;
+ break;
+ case 96000:
+ sampling_rate = RT766_SDCA_RATE_96000HZ;
+ break;
+ case 192000:
+ sampling_rate = RT766_SDCA_RATE_192000HZ;
+ break;
+ default:
+ dev_err(component->dev, "%s: Rate %d is not supported\n",
+ __func__, params_rate(params));
+ return -EINVAL;
+ }
+
+ /* set sampling frequency */
+ switch (dai->id) {
+ case RT766_AIF1:
+ regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX),
+ sampling_rate);
+ regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX),
+ sampling_rate);
+ break;
+ case RT766_AIF2:
+ regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX),
+ sampling_rate);
+ break;
+ case RT766_AIF3:
+ regmap_write(rt766->regmap,
+ RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX),
+ sampling_rate);
+ break;
+ default:
+ dev_err(component->dev, "%s: Wrong DAI id\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int rt766_sdca_pcm_hw_free(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
+ struct sdw_stream_runtime *sdw_stream =
+ snd_soc_dai_get_dma_data(dai, substream);
+
+ if (!rt766->slave)
+ return -EINVAL;
+
+ sdw_stream_remove_slave(rt766->slave, sdw_stream);
+ return 0;
+}
+
+#define RT766_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \
+ SNDRV_PCM_RATE_192000)
+#define RT766_DAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE)
+#define RT766_ADC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE | \
+ SNDRV_PCM_FMTBIT_S32_LE)
+
+static const struct snd_soc_dai_ops rt766_sdca_ops = {
+ .hw_params = rt766_sdca_pcm_hw_params,
+ .hw_free = rt766_sdca_pcm_hw_free,
+ .set_stream = rt766_sdca_set_sdw_stream,
+ .shutdown = rt766_sdca_shutdown,
+};
+
+static struct snd_soc_dai_driver rt766_sdca_dai[] = {
+ {
+ .name = "rt766-sdca-aif1",
+ .id = RT766_AIF1,
+ .playback = {
+ .stream_name = "DP3 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RT766_STEREO_RATES,
+ .formats = RT766_DAC_FORMATS,
+ },
+ .capture = {
+ .stream_name = "DP12 Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = RT766_STEREO_RATES,
+ .formats = RT766_ADC_FORMATS,
+ },
+ .ops = &rt766_sdca_ops,
+ },
+ {
+ .name = "rt766-sdca-aif2",
+ .id = RT766_AIF2,
+ .playback = {
+ .stream_name = "DP1 Playback",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = RT766_STEREO_RATES,
+ .formats = RT766_DAC_FORMATS,
+ },
+ .ops = &rt766_sdca_ops,
+ },
+ {
+ .name = "rt766-sdca-aif3",
+ .id = RT766_AIF3,
+ .capture = {
+ .stream_name = "DP8 Capture",
+ .channels_min = 1,
+ .channels_max = 4,
+ .rates = RT766_STEREO_RATES,
+ .formats = RT766_ADC_FORMATS,
+ },
+ .ops = &rt766_sdca_ops,
+ }
+};
+
+static unsigned int rate_find_mask(struct device *dev, unsigned int rate)
+{
+ switch (rate) {
+ case 44100:
+ return SNDRV_PCM_RATE_44100;
+ case 48000:
+ return SNDRV_PCM_RATE_48000;
+ case 96000:
+ return SNDRV_PCM_RATE_96000;
+ case 192000:
+ return SNDRV_PCM_RATE_192000;
+ default:
+ dev_warn(dev, "%s: Rate %d is not supported\n", __func__, rate);
+ return 0;
+ }
+}
+
+static int rt766_parse_rates(struct device *dev,
+ struct sdca_function_data *function,
+ struct sdca_entity *entity,
+ unsigned int *out_rates)
+{
+ struct sdca_control_range *range;
+ unsigned int sample_rate;
+ unsigned int clock_rates = 0;
+ unsigned int rates = 0;
+ int sel, i;
+
+ switch (entity->type) {
+ case SDCA_ENTITY_TYPE_IT:
+ sel = SDCA_CTL_IT_USAGE;
+ break;
+ case SDCA_ENTITY_TYPE_OT:
+ sel = SDCA_CTL_OT_USAGE;
+ break;
+ default:
+ dev_err(dev, "%s: entity type has no usage control\n",
+ entity->label);
+ return -EINVAL;
+ }
+
+ if (entity->iot.clock) {
+ range = sdca_selector_find_range(dev, entity->iot.clock,
+ SDCA_CTL_CS_SAMPLERATEINDEX,
+ SDCA_SAMPLERATEINDEX_NCOLS, 0);
+ if (!range)
+ return -EINVAL;
+
+ for (i = 0; i < range->rows; i++) {
+ sample_rate = sdca_range(range, SDCA_SAMPLERATEINDEX_RATE, i);
+ clock_rates |= rate_find_mask(dev, sample_rate);
+ }
+ } else {
+ clock_rates = UINT_MAX;
+ }
+
+ range = sdca_selector_find_range(dev, entity, sel, SDCA_USAGE_NCOLS, 0);
+ if (!range)
+ return -EINVAL;
+
+ for (i = 0; i < range->rows; i++) {
+ sample_rate = sdca_range(range, SDCA_USAGE_SAMPLE_RATE, i);
+ sample_rate = rate_find_mask(dev, sample_rate);
+
+ if (sample_rate & clock_rates)
+ rates |= sample_rate;
+ }
+
+ *out_rates = rates;
+
+ dev_dbg(dev, "%s: entity %s supports rates 0x%08x, clock_rates=0x%08x\n",
+ __func__, entity->label, rates, clock_rates);
+
+ return 0;
+}
+
+static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function,
+ const char *label)
+{
+ struct sdca_entity *entity = NULL;
+ unsigned int rates;
+ int i, ret;
+
+ for (i = 0; i < function->num_entities; i++) {
+ entity = &function->entities[i];
+
+ if (strcmp(entity->label, label))
+ continue;
+
+ /* Can't check earlier as only terminals have an iot member. */
+ if (!entity->iot.is_dataport)
+ continue;
+
+ ret = rt766_parse_rates(dev, function, entity, &rates);
+ if (ret < 0) {
+ dev_dbg(dev, "%s: failed to parse rates for entity %s\n",
+ __func__, entity->label);
+ return 0;
+ }
+
+ dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, rates);
+ }
+
+ return rates;
+}
+
+int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave)
+{
+ struct sdca_function_data *func_data_ptr;
+ struct rt766_sdca_priv *rt766;
+ unsigned int rates;
+ int ret;
+ int i;
+
+ rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL);
+ if (!rt766)
+ return -ENOMEM;
+
+ dev_set_drvdata(dev, rt766);
+ rt766->slave = slave;
+ rt766->regmap = regmap;
+
+ regcache_cache_only(rt766->regmap, true);
+
+ mutex_init(&rt766->disable_irq_lock);
+
+ /*
+ * Mark hw_init to false
+ * HW init will be performed when device reports present
+ */
+ rt766->hw_init = false;
+ rt766->first_hw_init = false;
+ rt766->fu41_dapm_mute = true;
+ rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false;
+ rt766->fu36_dapm_mute = true;
+ rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true;
+ rt766->fu21_dapm_mute = true;
+ rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false;
+ rt766->fu113_dapm_mute = true;
+ rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] =
+ rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true;
+
+ /* get SDCA function data */
+ dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions);
+ for (i = 0; i < slave->sdca_data.num_functions; i++) {
+ func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL);
+ if (!func_data_ptr)
+ return dev_err_probe(dev, -ENOMEM, "failed to allocate function data");
+
+ func_data_ptr->desc = &slave->sdca_data.function[i];
+ ret = sdca_parse_function(dev, slave, func_data_ptr);
+ if (ret) {
+ devm_kfree(dev, func_data_ptr);
+ return ret;
+ }
+ dev_dbg(dev, "Function type=%d, num_entities=%d",
+ slave->sdca_data.function[i].type, func_data_ptr->num_entities);
+
+ switch (slave->sdca_data.function[i].type) {
+ case SDCA_FUNCTION_TYPE_UAJ:
+ rt766->uaj_func_data = func_data_ptr;
+ /*
+ * Some machines may only support a subset of the sample rates supported by the codec.
+ * Therefore, we need to parse the supported sample rates from the DisCo table and
+ * configure them in the DAI. If the DisCo table does not provide sample rate information,
+ * we will fall back to the default supported rates defined in the codec driver.
+ */
+ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41");
+ if (rates)
+ rt766_sdca_dai[0].playback.rates = rates;
+
+ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36");
+ if (rates)
+ rt766_sdca_dai[0].capture.rates = rates;
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_AMP:
+ rt766->sa_func_data = func_data_ptr;
+ rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21");
+ if (rates)
+ rt766_sdca_dai[1].playback.rates = rates;
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_MIC:
+ rt766->sm_func_data = func_data_ptr;
+ rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113");
+ if (rates)
+ rt766_sdca_dai[2].capture.rates = rates;
+ break;
+ case SDCA_FUNCTION_TYPE_HID:
+ rt766->hid_func_data = func_data_ptr;
+ break;
+ default:
+ dev_dbg(dev, "Unexpected SDCA function type found: %d",
+ slave->sdca_data.function[i].type);
+ }
+ }
+
+ ret = devm_snd_soc_register_component(dev,
+ &soc_sdca_dev_rt766, rt766_sdca_dai, ARRAY_SIZE(rt766_sdca_dai));
+ if (ret < 0)
+ return ret;
+
+ /* set autosuspend parameters */
+ pm_runtime_set_autosuspend_delay(dev, 3000);
+ pm_runtime_use_autosuspend(dev);
+
+ /* make sure the device does not suspend immediately */
+ pm_runtime_mark_last_busy(dev);
+
+ pm_runtime_enable(dev);
+
+ dev_dbg(dev, "%s\n", __func__);
+
+ return 0;
+}
+
+static int rt766_func_initialize(struct rt766_sdca_priv *rt766, struct sdca_function_data *func_data)
+{
+ struct device *dev = &rt766->slave->dev;
+ unsigned int func_status_reg;
+ unsigned int func_status;
+ int ret;
+
+ switch (func_data->desc->type) {
+ case SDCA_FUNCTION_TYPE_UAJ:
+ func_status_reg = RT766_FUNC_STATUS_REG(UAJ);
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_AMP:
+ func_status_reg = RT766_FUNC_STATUS_REG(AMP);
+ break;
+ case SDCA_FUNCTION_TYPE_SMART_MIC:
+ func_status_reg = RT766_FUNC_STATUS_REG(MIC);
+ break;
+ case SDCA_FUNCTION_TYPE_HID:
+ func_status_reg = RT766_FUNC_STATUS_REG(HID);
+ break;
+ default:
+ dev_dbg(dev, "Unexpected SDCA function type found: %d",
+ func_data->desc->type);
+ return -EINVAL;
+ }
+
+ regmap_read(rt766->regmap, func_status_reg, &func_status);
+ dev_dbg(dev, "%s, %s func_status=0x%x\n", __func__, func_data->desc->name, func_status);
+
+ if ((func_status & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) || (!rt766->first_hw_init)) {
+ ret = sdca_regmap_write_init(dev, rt766->regmap, func_data);
+ if (ret) {
+ dev_err(dev, "%s initialization table update failed\n", func_data->desc->name);
+ goto _func_init_err_;
+ }
+
+ regmap_write(rt766->regmap, func_status_reg,
+ SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION);
+ }
+
+ return 0;
+
+_func_init_err_:
+ dev_err(dev, "%s: %s init writes failed, err=%d", __func__, func_data->desc->name, ret);
+ return ret;
+}
+
+int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave)
+{
+ struct rt766_sdca_priv *rt766 = dev_get_drvdata(dev);
+ unsigned int val;
+
+ rt766->disable_irq = false;
+
+ if (rt766->hw_init)
+ return 0;
+
+ regcache_cache_only(rt766->regmap, false);
+ if (rt766->first_hw_init) {
+ regcache_cache_bypass(rt766->regmap, true);
+ } else {
+ /*
+ * PM runtime status is marked as 'active' only when a Slave reports as Attached
+ */
+
+ /* update count of parent 'active' children */
+ pm_runtime_set_active(&slave->dev);
+ }
+
+ pm_runtime_get_noresume(&slave->dev);
+
+ regmap_read(rt766->regmap, RT766_BOND_LATCH_ID, &val);
+ dev_dbg(&slave->dev, "%s bond ID=0x%x (%s)\n", __func__, val, (val == 0x1) ? "RT767" : "RT766");
+
+ /* check function status and initialize if needed */
+ if (rt766->uaj_func_data)
+ rt766_func_initialize(rt766, rt766->uaj_func_data);
+ if (rt766->sa_func_data)
+ rt766_func_initialize(rt766, rt766->sa_func_data);
+ if (rt766->sm_func_data)
+ rt766_func_initialize(rt766, rt766->sm_func_data);
+ if (rt766->hid_func_data)
+ rt766_func_initialize(rt766, rt766->hid_func_data);
+
+ if (rt766->first_hw_init) {
+ regcache_cache_bypass(rt766->regmap, false);
+ regcache_mark_dirty(rt766->regmap);
+ } else {
+ rt766->first_hw_init = true;
+ }
+
+ /* Mark Slave initialization complete */
+ rt766->hw_init = true;
+
+ dev_dbg(&slave->dev, "%s hw_init complete\n", __func__);
+
+ pm_runtime_put_autosuspend(&slave->dev);
+
+ return 0;
+}
+
+MODULE_DESCRIPTION("ASoC RT766 SDCA SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/sound/soc/codecs/rt766-sdca.h b/sound/soc/codecs/rt766-sdca.h
new file mode 100644
index 000000000000..8c0fe7e80c76
--- /dev/null
+++ b/sound/soc/codecs/rt766-sdca.h
@@ -0,0 +1,132 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * rt766-sdca.h -- RT766 SDCA ALSA SoC audio driver header
+ *
+ * Copyright(c) 2026 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT766_H__
+#define __RT766_H__
+
+#include <linux/pm.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/soc.h>
+#include <linux/workqueue.h>
+
+struct rt766_sdca_priv {
+ struct regmap *regmap;
+ struct snd_soc_component *component;
+ struct sdw_slave *slave;
+ bool hw_init;
+ bool first_hw_init;
+ struct snd_soc_jack *hs_jack;
+ struct mutex disable_irq_lock; /* SDCA irq lock protection */
+ bool disable_irq;
+ int jack_type;
+ bool fu41_dapm_mute;
+ bool fu41_mixer_l_mute;
+ bool fu41_mixer_r_mute;
+ bool fu113_dapm_mute;
+ bool fu113_mixer_mute[4];
+ bool fu21_dapm_mute;
+ bool fu21_mixer_l_mute;
+ bool fu21_mixer_r_mute;
+ bool fu36_dapm_mute;
+ bool fu36_mixer_l_mute;
+ bool fu36_mixer_r_mute;
+ struct sdca_function_data *uaj_func_data;
+ struct sdca_function_data *sm_func_data;
+ struct sdca_function_data *sa_func_data;
+ struct sdca_function_data *hid_func_data;
+ struct sdca_interrupt_info *irq_info;
+};
+
+/* vendor registers */
+#define RT766_VERSION_ID 0xc404
+#define RT766_DEV_ID1 0xc405
+#define RT766_DEV_ID0 0xc406
+#define RT766_BOND_LATCH_ID 0xc407
+
+#define RT766_HP_POWER_STATE 0x1000004
+#define RT766_HP_FSM_CTL2_1 0x100000d
+
+/* MCU Patch address */
+#define RT766_MCU_PATCH_ADDR1_START 0x10010000
+#define RT766_MCU_PATCH_ADDR1_END 0x10011fff
+#define RT766_MCU_PATCH_ADDR2_START 0x10020000
+#define RT766_MCU_PATCH_ADDR2_END 0x10023fff
+
+/* Buffer address for HID */
+#define RT766_BUF_ADDR_HID1 0x44030000
+#define RT766_BUF_ADDR_HID2 0x44030020
+
+/* SDCA (Channel) */
+#define RT766_CH_1 0x01
+#define RT766_CH_2 0x02
+#define RT766_CH_3 0x03
+#define RT766_CH_4 0x04
+
+/* RT766 SDCA Control - function number */
+#define RT766_FUNC_NUM_UAJ 0x01
+#define RT766_FUNC_NUM_MIC 0x02
+#define RT766_FUNC_NUM_HID 0x03
+#define RT766_FUNC_NUM_AMP 0x04
+
+/* RT766 SDCA entity */
+#define RT766_SDCA_ENT_0 0x00
+#define RT766_SDCA_ENT_HID101 0x01
+#define RT766_SDCA_ENT_GE49 0x49
+#define RT766_SDCA_ENT_USER_FU41 0x05
+#define RT766_SDCA_ENT_USER_FU36 0x0f
+#define RT766_SDCA_ENT_USER_FU21 0x03
+#define RT766_SDCA_ENT_USER_FU113 0x30
+#define RT766_SDCA_ENT_PDE23 0x33
+#define RT766_SDCA_ENT_PDE47 0x28
+#define RT766_SDCA_ENT_PDE11 0x2a
+#define RT766_SDCA_ENT_PDE34 0x29
+#define RT766_SDCA_ENT_CS41 0x01
+#define RT766_SDCA_ENT_CS36 0x11
+#define RT766_SDCA_ENT_CS113 0x12
+#define RT766_SDCA_ENT_CS21 0x21
+#define RT766_SDCA_ENT_PLATFORM_FU33 0x44
+#define RT766_SDCA_ENT_PPU21 0x04
+
+/* sample frequency index */
+#define RT766_SDCA_RATE_44100HZ 0x08
+#define RT766_SDCA_RATE_48000HZ 0x09
+#define RT766_SDCA_RATE_96000HZ 0x0b
+#define RT766_SDCA_RATE_192000HZ 0x0d
+
+/* SDCA Register macros */
+#define RT766_MUTE_REG(func, fu, ch) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_MUTE, RT766_CH_##ch)
+
+#define RT766_VOLUME_REG(func, fu, ch) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_CHANNEL_VOLUME, RT766_CH_##ch)
+
+#define RT766_GAIN_REG(func, fu, ch) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##fu, SDCA_CTL_FU_GAIN, RT766_CH_##ch)
+
+#define RT766_PDE_REQ_REG(func, pde) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_REQUESTED_PS, 0)
+
+#define RT766_PDE_ACTUAL_REG(func, pde) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##pde, SDCA_CTL_PDE_ACTUAL_PS, 0)
+
+#define RT766_FUNC_STATUS_REG(func) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0)
+
+#define RT766_SDCA_CTL(func, ent, ctl) \
+ SDW_SDCA_CTL(RT766_FUNC_NUM_##func, RT766_SDCA_ENT_##ent, ctl, 0)
+
+enum {
+ RT766_AIF1,
+ RT766_AIF2,
+ RT766_AIF3,
+};
+
+int rt766_sdca_io_init(struct device *dev, struct sdw_slave *slave);
+int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave);
+#endif /* __RT766_H__ */
--
2.54.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-20 9:06 [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver shumingf
@ 2026-07-20 14:38 ` Pierre-Louis Bossart
2026-07-21 7:09 ` Shuming [范書銘]
2026-07-20 16:21 ` Charles Keepax
1 sibling, 1 reply; 7+ messages in thread
From: Pierre-Louis Bossart @ 2026-07-20 14:38 UTC (permalink / raw)
To: shumingf, broonie, lgirdwood
Cc: linux-sound, lars, flove, oder_chiou, jack.yu, derek.fang,
Charles Keepax
On 7/20/26 11:06, shumingf@realtek.com wrote:
> From: Shuming Fan <shumingf@realtek.com>
>
> This patch adds the initial SDCA multi-function codec driver for the RT766 and RT767.
>
> Signed-off-by: Shuming Fan <shumingf@realtek.com>
Looks mostly good, I only have minor comments, see below.
> +static bool rt766_sdca_readable_register(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4:
> + case RT766_VERSION_ID ... RT766_BOND_LATCH_ID:
> + case 0xc344 ... 0xc345:
> + case 0xc900:
> + case 0xc920:
> + case 0xd540 ... 0xd542:
> + case 0xf01e:
> + case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1:
> + case 0x310100:
> + case RT766_MCU_PATCH_ADDR1_START ... RT766_MCU_PATCH_ADDR1_END:
> + case RT766_MCU_PATCH_ADDR2_START ... RT766_MCU_PATCH_ADDR2_END:
> + case RT766_MUTE_REG(UAJ, USER_FU41, 1):
> + case RT766_MUTE_REG(UAJ, USER_FU41, 2):
> + case RT766_VOLUME_REG(UAJ, USER_FU41, 1):
> + case RT766_VOLUME_REG(UAJ, USER_FU41, 2):
> + case RT766_MUTE_REG(UAJ, USER_FU36, 1):
> + case RT766_MUTE_REG(UAJ, USER_FU36, 2):
> + case RT766_VOLUME_REG(UAJ, USER_FU36, 1):
> + case RT766_VOLUME_REG(UAJ, USER_FU36, 2):
> + case RT766_PDE_REQ_REG(UAJ, PDE47):
> + case RT766_PDE_REQ_REG(UAJ, PDE34):
> + case RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX):
> + case RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX):
> + /* 0x40480000 */
> + case RT766_FUNC_STATUS_REG(UAJ):
> + /* 0x40481400 */
> + case RT766_PDE_ACTUAL_REG(UAJ, PDE47):
> + /* 0x40481480 */
these single line registers listed in comments confuse me, not clear for
example if this one applies to the following line...
> + case RT766_PDE_ACTUAL_REG(UAJ, PDE34):
but then why not adde the values for the next 3?
> + case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1):
> + case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2):
> + case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE):
> + /* 0x40600490 */
> + case RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_DETECTED_MODE):
> + case RT766_PDE_REQ_REG(MIC, PDE11):
> + case RT766_MUTE_REG(MIC, USER_FU113, 1):
> + case RT766_MUTE_REG(MIC, USER_FU113, 2):
> + case RT766_MUTE_REG(MIC, USER_FU113, 3):
> + case RT766_MUTE_REG(MIC, USER_FU113, 4):
> + case RT766_VOLUME_REG(MIC, USER_FU113, 1):
> + case RT766_VOLUME_REG(MIC, USER_FU113, 2):
> + case RT766_VOLUME_REG(MIC, USER_FU113, 3):
> + case RT766_VOLUME_REG(MIC, USER_FU113, 4):
> + /* 0x40880000 */
> + case RT766_FUNC_STATUS_REG(MIC):
same here, are 0x40880000 and RT766_FUNC_STATUS_REG equivalent? If yes
maybe move the comment to the right side?
> + case RT766_SDCA_CTL(MIC, CS113, SDCA_CTL_CS_SAMPLERATEINDEX):
> + /* 0x40881500 */
> + case RT766_PDE_ACTUAL_REG(MIC, PDE11):
> + /* 0x40c80000 */
> + case RT766_FUNC_STATUS_REG(HID):
> + /* 0x40c80080 - 0x40c80098 */
> + case RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ...
> + RT766_SDCA_CTL(HID, HID101, SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH):
> + case RT766_MUTE_REG(AMP, USER_FU21, 1):
> + case RT766_MUTE_REG(AMP, USER_FU21, 2):
> + case RT766_VOLUME_REG(AMP, USER_FU21, 1):
> + case RT766_VOLUME_REG(AMP, USER_FU21, 2):
> + case RT766_PDE_REQ_REG(AMP, PDE23):
> + /* 0x41080000 */
> + case RT766_FUNC_STATUS_REG(AMP):
> + case RT766_SDCA_CTL(AMP, PPU21, SDCA_CTL_PPU_POSTURENUMBER):
> + case RT766_SDCA_CTL(AMP, CS21, SDCA_CTL_CS_SAMPLERATEINDEX):
> + /* 0x41081980 */
> + case RT766_PDE_ACTUAL_REG(AMP, PDE23):
> + case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2:
> + return true;
> + default:
> + return false;
> + }
> +}
> +static const struct reg_default rt766_sdca_defaults[] = {
> + /* 0x40400289 - 0x4040028a */
> + { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 },
> + { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 },
things are much clearer/organized here!
> + /* 0x40400291 - 0x40400292 */
> + { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 },
> + { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 },
> + /* 0x40400789 - 0x4040078a */
> + { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 },
> + { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 },
> + /* 0x40400791 - 0x40400792 */
> + { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 },
> + { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 },
> + /* 0x40401408 */
> + { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 },
> + /* 0x40401488 */
> + { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 },
> + /* 0x40480080 */
> + { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
> + /* 0x40480880 */
> + { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX), 0x09 },
> + /* 0x40600259 - 0x4060025a */
> + { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 },
> + { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 },
> + /* 0x40600488 */
> + { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE), 0x00 },
> +static struct sdca_entity *rt766_find_entity_by_label(struct sdca_function_data *func,
> + const char *label)
> +{
> + struct sdca_entity *entity = NULL;
useless initialization?
> + int idx;
> +
> + for (idx = 0; idx < func->num_entities; idx++) {
> + entity = &func->entities[idx];
> +
> + if (!strcmp(entity->label, label))
> + return entity;
> + }
> +
> + return NULL;
> +}
> +static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
> + struct sdca_function_data *function,
> + struct snd_soc_component *component,
> + struct sdca_interrupt_info *info,
> + bool enabled)
> +{
> + struct device *dev = &rt766->slave->dev;
> + struct sdca_interrupt *interrupt;
> + struct sdca_control *control;
> + struct sdca_entity *entity;
> + irq_handler_t handler;
> + int i, j, irq, ret;
> +
> + for (i = 0; i < function->num_entities; i++) {
> + entity = &function->entities[i];
> +
> + for (j = 0; j < entity->num_controls; j++) {
> + control = &entity->controls[j];
> + irq = control->interrupt_position;
> +
> + switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
> + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
> + handler = rt766_sdca_irq_jd_handler;
> + break;
> + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
> + handler = rt766_sdca_irq_btn_handler;
> + break;
> + default:
> + continue;
> + }
> +
> + interrupt = &info->irqs[irq];
> +
> + if (enabled) {
> + ret = sdca_irq_data_populate(dev, rt766->regmap, component,
> + function, entity, control,
> + interrupt);
> + if (ret)
> + return ret;
> +
> + interrupt->priv = rt766;
> + ret = sdca_irq_request(dev, info, irq, interrupt->name,
> + handler, interrupt);
There was a proposal from Charles to do an irq_populate_early, is there
a conflict between the two patchsets? see "ASoC: SDCA: Populate IRQ data
earlier"
> + if (ret) {
> + dev_err(dev, "failed to request irq %s: %d\n",
> + interrupt->name, ret);
> + return ret;
> + }
> + dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name);
> + } else {
> + sdca_irq_free(dev, info, irq, interrupt->name, interrupt);
> + dev_dbg(dev, "Freeing IRQ %d\n", irq);
> + }
> + }
> + }
> +
> + return 0;
> +}
> +static int rt766_sdca_set_fu41_playback_ctl(struct rt766_sdca_priv *rt766)
> +{
> + int err;
> + unsigned int ch_01, ch_02;
nit-pick: likely copy/paste from other drivers, but flipping the two
lines would be nicer. there are a couple of similar patterns in this driver.
> +
> + ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ? 0x01 : 0x00;
> + ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ? 0x01 : 0x00;
> +
> + err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 1), ch_01);
> + if (err < 0)
> + return err;
> +
> + err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ, USER_FU41, 2), ch_02);
> + if (err < 0)
> + return err;
> +
> + return 0;
> +}
> +static int rt766_sdca_pde47_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> + const struct sdca_entity *entity = NULL;
useless init?
> + unsigned char ps0 = 0x0, ps3 = 0x3;
> + int from_ps, to_ps;
> + int ret;
> +
> + switch (event) {
> + case SND_SOC_DAPM_POST_PMU:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps0);
> + from_ps = ps3;
> + to_ps = ps0;
> + break;
> + case SND_SOC_DAPM_PRE_PMD:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps3);
> + from_ps = ps0;
> + to_ps = ps3;
> + break;
> + }
> +
> + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
> + ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> + RT766_FUNC_NUM_UAJ,
> + RT766_SDCA_ENT_PDE47,
> + from_ps, to_ps,
> + entity ? entity->pde.max_delay : NULL,
> + entity ? entity->pde.num_max_delay : 0);
> + if (ret)
> + dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> + __func__, from_ps, to_ps, ret);
> +
> + return ret;
> +}
> +static int rt766_sdca_pde34_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> + const struct sdca_entity *entity = NULL;
useless init? Same comment for all PDE events.
> + unsigned char ps0 = 0x0, ps3 = 0x3;
> + int from_ps, to_ps;
> + int ret;
> +
> + switch (event) {
> + case SND_SOC_DAPM_POST_PMU:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps0);
> + from_ps = ps3;
> + to_ps = ps0;
> + break;
> + case SND_SOC_DAPM_PRE_PMD:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE34), ps3);
> + from_ps = ps0;
> + to_ps = ps3;
> + break;
> + }
> +
> + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 34");
> + ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> + RT766_FUNC_NUM_UAJ,
> + RT766_SDCA_ENT_PDE34,
> + from_ps, to_ps,
> + entity ? entity->pde.max_delay : NULL,
> + entity ? entity->pde.num_max_delay : 0);
> + if (ret)
> + dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> + __func__, from_ps, to_ps, ret);
> +
> + return ret;
> +}
> +static int rt766_sdca_probe(struct snd_soc_component *component)
> +{
> + struct rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> + struct device *dev = &rt766->slave->dev;
> + int ret;
> +
> + rt766->component = component;
> +
> + ret = pm_runtime_resume(component->dev);
> + if (ret < 0 && ret != -EACCES)
> + return ret;
> +
> + if (rt766->uaj_func_data) {
> + dev_dbg(dev, "%s : irq %d\n", __func__, rt766->slave->irq);
> +
> + rt766->irq_info = sdca_irq_allocate(dev, rt766->regmap, rt766->slave->irq);
> + if (IS_ERR(rt766->irq_info))
> + return PTR_ERR(rt766->irq_info);
> +
> + ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data,
> + component, rt766->irq_info, true);
same question on IRQ allocate/request for Charles.
> + if (ret < 0) {
> + dev_err(dev, "Failed to request UAJ SDCA IRQ: %d\n", ret);
> + return ret;
> + }
> +
> + if (rt766->hid_func_data) {
> + ret = rt766_sdca_irq_ctl(rt766, rt766->hid_func_data,
> + component, rt766->irq_info, true);
> + if (ret < 0) {
> + dev_err(dev, "Failed to request HID SDCA IRQ: %d\n", ret);
> + return ret;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +static unsigned int rt766_find_dt_rates(struct device *dev, struct sdca_function_data *function,
> + const char *label)
> +{
> + struct sdca_entity *entity = NULL;
useless init?
> + unsigned int rates;
> + int i, ret;
> +
> + for (i = 0; i < function->num_entities; i++) {
> + entity = &function->entities[i];
> +
> + if (strcmp(entity->label, label))
> + continue;
> +
> + /* Can't check earlier as only terminals have an iot member. */
> + if (!entity->iot.is_dataport)
> + continue;
> +
> + ret = rt766_parse_rates(dev, function, entity, &rates);
> + if (ret < 0) {
> + dev_dbg(dev, "%s: failed to parse rates for entity %s\n",
> + __func__, entity->label);
> + return 0;
> + }
> +
> + dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__, entity->label, rates);
> + }
> +
> + return rates;
> +}
> +
> +int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct sdw_slave *slave)
> +{
> + struct sdca_function_data *func_data_ptr;
> + struct rt766_sdca_priv *rt766;
> + unsigned int rates;
> + int ret;
> + int i;
> +
> + rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL);
> + if (!rt766)
> + return -ENOMEM;
> +
> + dev_set_drvdata(dev, rt766);
> + rt766->slave = slave;
> + rt766->regmap = regmap;
> +
> + regcache_cache_only(rt766->regmap, true);
> +
> + mutex_init(&rt766->disable_irq_lock);
> +
> + /*
> + * Mark hw_init to false
> + * HW init will be performed when device reports present
> + */
> + rt766->hw_init = false;
> + rt766->first_hw_init = false;
> + rt766->fu41_dapm_mute = true;
> + rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false;
> + rt766->fu36_dapm_mute = true;
> + rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true;
> + rt766->fu21_dapm_mute = true;
> + rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false;
> + rt766->fu113_dapm_mute = true;
> + rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] =
> + rt766->fu113_mixer_mute[2] = rt766->fu113_mixer_mute[3] = true;
> +
> + /* get SDCA function data */
> + dev_dbg(dev, "SDCA functions found: %d", slave->sdca_data.num_functions);
> + for (i = 0; i < slave->sdca_data.num_functions; i++) {
> + func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr), GFP_KERNEL);
> + if (!func_data_ptr)
> + return dev_err_probe(dev, -ENOMEM, "failed to allocate function data");
> +
> + func_data_ptr->desc = &slave->sdca_data.function[i];
> + ret = sdca_parse_function(dev, slave, func_data_ptr);
> + if (ret) {
> + devm_kfree(dev, func_data_ptr);
> + return ret;
> + }
> + dev_dbg(dev, "Function type=%d, num_entities=%d",
> + slave->sdca_data.function[i].type, func_data_ptr->num_entities);
> +
> + switch (slave->sdca_data.function[i].type) {
> + case SDCA_FUNCTION_TYPE_UAJ:
> + rt766->uaj_func_data = func_data_ptr;
> + /*
> + * Some machines may only support a subset of the sample rates supported by the codec.
> + * Therefore, we need to parse the supported sample rates from the DisCo table and
> + * configure them in the DAI. If the DisCo table does not provide sample rate information,
> + * we will fall back to the default supported rates defined in the codec driver.
> + */
> + rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 41");
> + if (rates)
> + rt766_sdca_dai[0].playback.rates = rates;
> +
> + rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 36");
> + if (rates)
> + rt766_sdca_dai[0].capture.rates = rates;
> + break;
> + case SDCA_FUNCTION_TYPE_SMART_AMP:
> + rt766->sa_func_data = func_data_ptr;
> + rates = rt766_find_dt_rates(dev, func_data_ptr, "IT 21");
> + if (rates)
> + rt766_sdca_dai[1].playback.rates = rates;
> + break;
> + case SDCA_FUNCTION_TYPE_SMART_MIC:
> + rt766->sm_func_data = func_data_ptr;
> + rates = rt766_find_dt_rates(dev, func_data_ptr, "OT 113");
> + if (rates)
> + rt766_sdca_dai[2].capture.rates = rates;
are the sdca_dai[index] related to the function? Wondering if we can use
e.g sdca_dai[MIC] instead?
> + break;
> + case SDCA_FUNCTION_TYPE_HID:
> + rt766->hid_func_data = func_data_ptr;
> + break;
> + default:
> + dev_dbg(dev, "Unexpected SDCA function type found: %d",
> + slave->sdca_data.function[i].type);
> + }
> + }
> +
> + ret = devm_snd_soc_register_component(dev,
> + &soc_sdca_dev_rt766, rt766_sdca_dai, ARRAY_SIZE(rt766_sdca_dai));
> + if (ret < 0)
> + return ret;
> +
> + /* set autosuspend parameters */
> + pm_runtime_set_autosuspend_delay(dev, 3000);
> + pm_runtime_use_autosuspend(dev);
> +
> + /* make sure the device does not suspend immediately */
> + pm_runtime_mark_last_busy(dev);
> +
> + pm_runtime_enable(dev);
> +
> + dev_dbg(dev, "%s\n", __func__);
> +
> + return 0;
> +}
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-20 9:06 [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver shumingf
2026-07-20 14:38 ` Pierre-Louis Bossart
@ 2026-07-20 16:21 ` Charles Keepax
2026-07-21 7:10 ` Shuming [范書銘]
1 sibling, 1 reply; 7+ messages in thread
From: Charles Keepax @ 2026-07-20 16:21 UTC (permalink / raw)
To: shumingf
Cc: broonie, lgirdwood, linux-sound, lars, flove, oder_chiou, jack.yu,
derek.fang
On Mon, Jul 20, 2026 at 05:06:13PM +0800, shumingf@realtek.com wrote:
> From: Shuming Fan <shumingf@realtek.com>
>
> This patch adds the initial SDCA multi-function codec driver for the RT766 and RT767.
>
> Signed-off-by: Shuming Fan <shumingf@realtek.com>
> ---
> +static struct sdca_entity *rt766_find_entity_by_label(struct sdca_function_data *func,
> + const char *label)
> +{
> + struct sdca_entity *entity = NULL;
> + int idx;
> +
> + for (idx = 0; idx < func->num_entities; idx++) {
> + entity = &func->entities[idx];
> +
> + if (!strcmp(entity->label, label))
> + return entity;
> + }
> +
> + return NULL;
> +}
Probably better to export find_sdca_entity_by_label() if you need
this from the driver.
> +static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
> + struct sdca_function_data *function,
> + struct snd_soc_component *component,
> + struct sdca_interrupt_info *info,
> + bool enabled)
> +{
> + struct device *dev = &rt766->slave->dev;
> + struct sdca_interrupt *interrupt;
> + struct sdca_control *control;
> + struct sdca_entity *entity;
> + irq_handler_t handler;
> + int i, j, irq, ret;
> +
> + for (i = 0; i < function->num_entities; i++) {
> + entity = &function->entities[i];
> +
> + for (j = 0; j < entity->num_controls; j++) {
> + control = &entity->controls[j];
> + irq = control->interrupt_position;
> +
> + switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
> + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
> + handler = rt766_sdca_irq_jd_handler;
> + break;
> + case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
> + handler = rt766_sdca_irq_btn_handler;
> + break;
> + default:
> + continue;
> + }
> +
> + interrupt = &info->irqs[irq];
> +
> + if (enabled) {
> + ret = sdca_irq_data_populate(dev, rt766->regmap, component,
> + function, entity, control,
> + interrupt);
> + if (ret)
> + return ret;
> +
> + interrupt->priv = rt766;
> + ret = sdca_irq_request(dev, info, irq, interrupt->name,
> + handler, interrupt);
> + if (ret) {
> + dev_err(dev, "failed to request irq %s: %d\n",
> + interrupt->name, ret);
> + return ret;
> + }
> + dev_dbg(dev, "Requesting IRQ %d InterruptName=%s\n", irq, interrupt->name);
> + } else {
> + sdca_irq_free(dev, info, irq, interrupt->name, interrupt);
> + dev_dbg(dev, "Freeing IRQ %d\n", irq);
> + }
> + }
> + }
> +
> + return 0;
> +}
Hmm... yeah this is moving in a slightly different direction from
where I was going with the core code. Let me think about that and
update tomorrow.
> +static int rt766_sdca_pde47_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> + const struct sdca_entity *entity = NULL;
> + unsigned char ps0 = 0x0, ps3 = 0x3;
> + int from_ps, to_ps;
> + int ret;
> +
> + switch (event) {
> + case SND_SOC_DAPM_POST_PMU:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps0);
> + from_ps = ps3;
> + to_ps = ps0;
> + break;
> + case SND_SOC_DAPM_PRE_PMD:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ, PDE47), ps3);
> + from_ps = ps0;
> + to_ps = ps3;
> + break;
> + }
> +
> + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
> + ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> + RT766_FUNC_NUM_UAJ,
> + RT766_SDCA_ENT_PDE47,
> + from_ps, to_ps,
> + entity ? entity->pde.max_delay : NULL,
> + entity ? entity->pde.num_max_delay : 0);
> + if (ret)
> + dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> + __func__, from_ps, to_ps, ret);
Does this actually run in cases where the entity doesn't exist? I
would be inclined to error out if you didn't find the entity.
> +static int rt766_sdca_pde11_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 rt766_sdca_priv *rt766 = snd_soc_component_get_drvdata(component);
> + const struct sdca_entity *entity = NULL;
> + unsigned char ps0 = 0x0, ps3 = 0x3;
> + int from_ps, to_ps;
> + int ret;
> +
> + switch (event) {
> + case SND_SOC_DAPM_POST_PMU:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC, PDE11), ps0);
> + from_ps = ps3;
> + to_ps = ps0;
> + break;
> + case SND_SOC_DAPM_PRE_PMD:
> + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC, PDE11), ps3);
> + from_ps = ps0;
> + to_ps = ps3;
> + break;
> + }
> +
> + entity = rt766_find_entity_by_label(rt766->sm_func_data, "PDE 11");
> + ret = sdca_asoc_pde_poll_actual_ps(component->dev, rt766->regmap,
> + RT766_FUNC_NUM_MIC,
> + RT766_SDCA_ENT_PDE11,
> + from_ps, to_ps,
> + entity ? entity->pde.max_delay : NULL,
> + entity ? entity->pde.num_max_delay : 0);
> + if (ret)
> + dev_err(component->dev, "%s: PDE transition %x -> %x failed, err=%d\n",
> + __func__, from_ps, to_ps, ret);
> +
> + return ret;
> +}
I feel like lot of these put/get and event helpers would benefit
from a parameterised helper. You have loads of functions doing
the same thing, would it be nicer to have a helper that takes
the required registers etc.
> +static int rt766_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 rt766_sdca_priv *rt766 = 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;
> + int retval, port, num_channels;
> + unsigned int sampling_rate;
> +
> + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id);
> + sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
> +
> + if (!sdw_stream)
> + return -EINVAL;
> +
> + if (!rt766->slave)
> + return -EINVAL;
> +
> + /* SoundWire specific configuration */
> + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> + direction = SDW_DATA_DIR_RX;
> + if (dai->id == RT766_AIF1)
> + port = 3;
> + else if (dai->id == RT766_AIF2)
> + port = 1;
> + else
> + return -EINVAL;
> + } else {
> + direction = SDW_DATA_DIR_TX;
> + if (dai->id == RT766_AIF1)
> + port = 12;
> + else if (dai->id == RT766_AIF3)
> + port = 8;
> + else
> + return -EINVAL;
> + }
> +
> + stream_config.frame_rate = params_rate(params);
> + stream_config.ch_count = params_channels(params);
> + stream_config.bps = snd_pcm_format_width(params_format(params));
> + stream_config.direction = direction;
> +
> + num_channels = params_channels(params);
> + port_config.ch_mask = GENMASK(num_channels - 1, 0);
> + port_config.num = port;
Can you use snd_sdw_params_to_config here?
> +static int rt766_parse_rates(struct device *dev,
> + struct sdca_function_data *function,
> + struct sdca_entity *entity,
> + unsigned int *out_rates)
> +{
> + struct sdca_control_range *range;
> + unsigned int sample_rate;
> + unsigned int clock_rates = 0;
> + unsigned int rates = 0;
> + int sel, i;
> +
> + switch (entity->type) {
> + case SDCA_ENTITY_TYPE_IT:
> + sel = SDCA_CTL_IT_USAGE;
> + break;
> + case SDCA_ENTITY_TYPE_OT:
> + sel = SDCA_CTL_OT_USAGE;
> + break;
> + default:
> + dev_err(dev, "%s: entity type has no usage control\n",
> + entity->label);
> + return -EINVAL;
> + }
> +
> + if (entity->iot.clock) {
> + range = sdca_selector_find_range(dev, entity->iot.clock,
> + SDCA_CTL_CS_SAMPLERATEINDEX,
> + SDCA_SAMPLERATEINDEX_NCOLS, 0);
> + if (!range)
> + return -EINVAL;
> +
> + for (i = 0; i < range->rows; i++) {
> + sample_rate = sdca_range(range, SDCA_SAMPLERATEINDEX_RATE, i);
> + clock_rates |= rate_find_mask(dev, sample_rate);
> + }
> + } else {
> + clock_rates = UINT_MAX;
> + }
> +
> + range = sdca_selector_find_range(dev, entity, sel, SDCA_USAGE_NCOLS, 0);
> + if (!range)
> + return -EINVAL;
> +
> + for (i = 0; i < range->rows; i++) {
> + sample_rate = sdca_range(range, SDCA_USAGE_SAMPLE_RATE, i);
> + sample_rate = rate_find_mask(dev, sample_rate);
> +
> + if (sample_rate & clock_rates)
> + rates |= sample_rate;
> + }
> +
> + *out_rates = rates;
> +
> + dev_dbg(dev, "%s: entity %s supports rates 0x%08x, clock_rates=0x%08x\n",
> + __func__, entity->label, rates, clock_rates);
> +
> + return 0;
> +}
This is mostly just populate_rate_format(), the values end up in
the snd_soc_pcm_stream, can we export that and use it here?
Thanks,
Charles
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-20 14:38 ` Pierre-Louis Bossart
@ 2026-07-21 7:09 ` Shuming [范書銘]
0 siblings, 0 replies; 7+ messages in thread
From: Shuming [范書銘] @ 2026-07-21 7:09 UTC (permalink / raw)
To: Pierre-Louis Bossart, broonie@kernel.org, lgirdwood@gmail.com
Cc: linux-sound@vger.kernel.org, lars@metafoo.de, Flove(HsinFu),
Oder Chiou, Jack Yu, Derek [方德義],
Charles Keepax
> > This patch adds the initial SDCA multi-function codec driver for the RT766
> and RT767.
> >
> > Signed-off-by: Shuming Fan <shumingf@realtek.com>
>
> Looks mostly good, I only have minor comments, see below.
Thanks for the review.
> > +static bool rt766_sdca_readable_register(struct device *dev, unsigned
> > +int reg) {
> > + switch (reg) {
> > + case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4:
> > + case RT766_VERSION_ID ... RT766_BOND_LATCH_ID:
> > + case 0xc344 ... 0xc345:
> > + case 0xc900:
> > + case 0xc920:
> > + case 0xd540 ... 0xd542:
> > + case 0xf01e:
> > + case RT766_HP_POWER_STATE ... RT766_HP_FSM_CTL2_1:
> > + case 0x310100:
> > + case RT766_MCU_PATCH_ADDR1_START ...
> RT766_MCU_PATCH_ADDR1_END:
> > + case RT766_MCU_PATCH_ADDR2_START ...
> RT766_MCU_PATCH_ADDR2_END:
> > + case RT766_MUTE_REG(UAJ, USER_FU41, 1):
> > + case RT766_MUTE_REG(UAJ, USER_FU41, 2):
> > + case RT766_VOLUME_REG(UAJ, USER_FU41, 1):
> > + case RT766_VOLUME_REG(UAJ, USER_FU41, 2):
> > + case RT766_MUTE_REG(UAJ, USER_FU36, 1):
> > + case RT766_MUTE_REG(UAJ, USER_FU36, 2):
> > + case RT766_VOLUME_REG(UAJ, USER_FU36, 1):
> > + case RT766_VOLUME_REG(UAJ, USER_FU36, 2):
> > + case RT766_PDE_REQ_REG(UAJ, PDE47):
> > + case RT766_PDE_REQ_REG(UAJ, PDE34):
> > + case RT766_SDCA_CTL(UAJ, CS41,
> SDCA_CTL_CS_SAMPLERATEINDEX):
> > + case RT766_SDCA_CTL(UAJ, CS36,
> SDCA_CTL_CS_SAMPLERATEINDEX):
> > + /* 0x40480000 */
> > + case RT766_FUNC_STATUS_REG(UAJ):
> > + /* 0x40481400 */
> > + case RT766_PDE_ACTUAL_REG(UAJ, PDE47):
> > + /* 0x40481480 */
>
> these single line registers listed in comments confuse me, not clear for
> example if this one applies to the following line...
Sorry these address comments caused any confusion. These address comments help me put the code in address order.
I will apply single address comment to the following line.
> > + case RT766_PDE_ACTUAL_REG(UAJ, PDE34):
>
> but then why not adde the values for the next 3?
Those registers have the comment in the rt766_sdca_defaults.
> > + case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1):
> > + case RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2):
> > + case RT766_SDCA_CTL(UAJ, GE49,
> SDCA_CTL_GE_SELECTED_MODE):
> > + /* 0x40600490 */
> > + case RT766_SDCA_CTL(UAJ, GE49,
> SDCA_CTL_GE_DETECTED_MODE):
> > + case RT766_PDE_REQ_REG(MIC, PDE11):
> > + case RT766_MUTE_REG(MIC, USER_FU113, 1):
> > + case RT766_MUTE_REG(MIC, USER_FU113, 2):
> > + case RT766_MUTE_REG(MIC, USER_FU113, 3):
> > + case RT766_MUTE_REG(MIC, USER_FU113, 4):
> > + case RT766_VOLUME_REG(MIC, USER_FU113, 1):
> > + case RT766_VOLUME_REG(MIC, USER_FU113, 2):
> > + case RT766_VOLUME_REG(MIC, USER_FU113, 3):
> > + case RT766_VOLUME_REG(MIC, USER_FU113, 4):
> > + /* 0x40880000 */
> > + case RT766_FUNC_STATUS_REG(MIC):
>
> same here, are 0x40880000 and RT766_FUNC_STATUS_REG equivalent? If
> yes maybe move the comment to the right side?
>
> > + case RT766_SDCA_CTL(MIC, CS113,
> SDCA_CTL_CS_SAMPLERATEINDEX):
> > + /* 0x40881500 */
> > + case RT766_PDE_ACTUAL_REG(MIC, PDE11):
> > + /* 0x40c80000 */
> > + case RT766_FUNC_STATUS_REG(HID):
> > + /* 0x40c80080 - 0x40c80098 */
> > + case RT766_SDCA_CTL(HID, HID101,
> SDCA_CTL_HIDE_HIDTX_CURRENTOWNER) ...
> > + RT766_SDCA_CTL(HID, HID101,
> SDCA_CTL_HIDE_HIDTX_MESSAGELENGTH):
> > + case RT766_MUTE_REG(AMP, USER_FU21, 1):
> > + case RT766_MUTE_REG(AMP, USER_FU21, 2):
> > + case RT766_VOLUME_REG(AMP, USER_FU21, 1):
> > + case RT766_VOLUME_REG(AMP, USER_FU21, 2):
> > + case RT766_PDE_REQ_REG(AMP, PDE23):
> > + /* 0x41080000 */
> > + case RT766_FUNC_STATUS_REG(AMP):
> > + case RT766_SDCA_CTL(AMP, PPU21,
> SDCA_CTL_PPU_POSTURENUMBER):
> > + case RT766_SDCA_CTL(AMP, CS21,
> SDCA_CTL_CS_SAMPLERATEINDEX):
> > + /* 0x41081980 */
> > + case RT766_PDE_ACTUAL_REG(AMP, PDE23):
> > + case RT766_BUF_ADDR_HID1 ... RT766_BUF_ADDR_HID2:
> > + return true;
> > + default:
> > + return false;
> > + }
> > +}
>
> > +static const struct reg_default rt766_sdca_defaults[] = {
> > + /* 0x40400289 - 0x4040028a */
> > + { RT766_MUTE_REG(UAJ, USER_FU41, 1), 0x01 },
> > + { RT766_MUTE_REG(UAJ, USER_FU41, 2), 0x01 },
>
> things are much clearer/organized here!
>
> > + /* 0x40400291 - 0x40400292 */
> > + { RT766_VOLUME_REG(UAJ, USER_FU41, 1), 0x0000 },
> > + { RT766_VOLUME_REG(UAJ, USER_FU41, 2), 0x0000 },
> > + /* 0x40400789 - 0x4040078a */
> > + { RT766_MUTE_REG(UAJ, USER_FU36, 1), 0x01 },
> > + { RT766_MUTE_REG(UAJ, USER_FU36, 2), 0x01 },
> > + /* 0x40400791 - 0x40400792 */
> > + { RT766_VOLUME_REG(UAJ, USER_FU36, 1), 0x0000 },
> > + { RT766_VOLUME_REG(UAJ, USER_FU36, 2), 0x0000 },
> > + /* 0x40401408 */
> > + { RT766_PDE_REQ_REG(UAJ, PDE47), 0x03 },
> > + /* 0x40401488 */
> > + { RT766_PDE_REQ_REG(UAJ, PDE34), 0x03 },
> > + /* 0x40480080 */
> > + { RT766_SDCA_CTL(UAJ, CS41, SDCA_CTL_CS_SAMPLERATEINDEX),
> 0x09 },
> > + /* 0x40480880 */
> > + { RT766_SDCA_CTL(UAJ, CS36, SDCA_CTL_CS_SAMPLERATEINDEX),
> 0x09 },
> > + /* 0x40600259 - 0x4060025a */
> > + { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 1), 0xfe00 },
> > + { RT766_GAIN_REG(UAJ, PLATFORM_FU33, 2), 0xfe00 },
> > + /* 0x40600488 */
> > + { RT766_SDCA_CTL(UAJ, GE49, SDCA_CTL_GE_SELECTED_MODE),
> 0x00 },
>
> > +static struct sdca_entity *rt766_find_entity_by_label(struct
> sdca_function_data *func,
> > + const char *label)
> > +{
> > + struct sdca_entity *entity = NULL;
>
> useless initialization?
Will export find_sdca_entity_by_label() instead.
> > + int idx;
> > +
> > + for (idx = 0; idx < func->num_entities; idx++) {
> > + entity = &func->entities[idx];
> > +
> > + if (!strcmp(entity->label, label))
> > + return entity;
> > + }
> > +
> > + return NULL;
> > +}
>
> > +static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
> > + struct
> sdca_function_data *function,
> > + struct
> snd_soc_component *component,
> > + struct
> sdca_interrupt_info *info,
> > + bool
> enabled)
> > +{
> > + struct device *dev = &rt766->slave->dev;
> > + struct sdca_interrupt *interrupt;
> > + struct sdca_control *control;
> > + struct sdca_entity *entity;
> > + irq_handler_t handler;
> > + int i, j, irq, ret;
> > +
> > + for (i = 0; i < function->num_entities; i++) {
> > + entity = &function->entities[i];
> > +
> > + for (j = 0; j < entity->num_controls; j++) {
> > + control = &entity->controls[j];
> > + irq = control->interrupt_position;
> > +
> > + switch (SDCA_CTL_TYPE(entity->type,
> control->sel)) {
> > + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
> > + handler = rt766_sdca_irq_jd_handler;
> > + break;
> > + case SDCA_CTL_TYPE_S(HIDE,
> HIDTX_CURRENTOWNER):
> > + handler = rt766_sdca_irq_btn_handler;
> > + break;
> > + default:
> > + continue;
> > + }
> > +
> > + interrupt = &info->irqs[irq];
> > +
> > + if (enabled) {
> > + ret = sdca_irq_data_populate(dev,
> rt766->regmap, component,
> > +
> function, entity, control,
> > +
> interrupt);
> > + if (ret)
> > + return ret;
> > +
> > + interrupt->priv = rt766;
> > + ret = sdca_irq_request(dev, info, irq,
> interrupt->name,
> > +
> handler,
> > + interrupt);
>
> There was a proposal from Charles to do an irq_populate_early, is there a
> conflict between the two patchsets? see "ASoC: SDCA: Populate IRQ data
> earlier"
Will check with Charles.
> > + if (ret) {
> > + dev_err(dev, "failed to
> request irq %s: %d\n",
> > + interrupt->name,
> ret);
> > + return ret;
> > + }
> > + dev_dbg(dev, "Requesting IRQ %d
> InterruptName=%s\n", irq, interrupt->name);
> > + } else {
> > + sdca_irq_free(dev, info, irq,
> interrupt->name, interrupt);
> > + dev_dbg(dev, "Freeing IRQ %d\n", irq);
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
>
> > +static int rt766_sdca_set_fu41_playback_ctl(struct rt766_sdca_priv
> > +*rt766) {
> > + int err;
> > + unsigned int ch_01, ch_02;
>
> nit-pick: likely copy/paste from other drivers, but flipping the two lines would
> be nicer. there are a couple of similar patterns in this driver.
Sure, will fix.
> > +
> > + ch_01 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_l_mute) ?
> 0x01 : 0x00;
> > + ch_02 = (rt766->fu41_dapm_mute || rt766->fu41_mixer_r_mute) ?
> > + 0x01 : 0x00;
> > +
> > + err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ,
> USER_FU41, 1), ch_01);
> > + if (err < 0)
> > + return err;
> > +
> > + err = regmap_write(rt766->regmap, RT766_MUTE_REG(UAJ,
> USER_FU41, 2), ch_02);
> > + if (err < 0)
> > + return err;
> > +
> > + return 0;
> > +}
>
> > +static int rt766_sdca_pde47_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 rt766_sdca_priv *rt766 =
> snd_soc_component_get_drvdata(component);
> > + const struct sdca_entity *entity = NULL;
>
> useless init?
Will fix.
> > + unsigned char ps0 = 0x0, ps3 = 0x3;
> > + int from_ps, to_ps;
> > + int ret;
> > +
> > + switch (event) {
> > + case SND_SOC_DAPM_POST_PMU:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE47), ps0);
> > + from_ps = ps3;
> > + to_ps = ps0;
> > + break;
> > + case SND_SOC_DAPM_PRE_PMD:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE47), ps3);
> > + from_ps = ps0;
> > + to_ps = ps3;
> > + break;
> > + }
> > +
> > + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
> > + ret = sdca_asoc_pde_poll_actual_ps(component->dev,
> rt766->regmap,
> > + RT766_FUNC_NUM_UAJ,
> > + RT766_SDCA_ENT_PDE47,
> > + from_ps, to_ps,
> > + entity ? entity->pde.max_delay :
> NULL,
> > + entity ?
> entity->pde.num_max_delay : 0);
> > + if (ret)
> > + dev_err(component->dev, "%s: PDE transition %x -> %x
> failed, err=%d\n",
> > + __func__, from_ps, to_ps, ret);
> > +
> > + return ret;
> > +}
>
> > +static int rt766_sdca_pde34_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 rt766_sdca_priv *rt766 =
> snd_soc_component_get_drvdata(component);
> > + const struct sdca_entity *entity = NULL;
>
> useless init? Same comment for all PDE events.
>
> > + unsigned char ps0 = 0x0, ps3 = 0x3;
> > + int from_ps, to_ps;
> > + int ret;
> > +
> > + switch (event) {
> > + case SND_SOC_DAPM_POST_PMU:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE34), ps0);
> > + from_ps = ps3;
> > + to_ps = ps0;
> > + break;
> > + case SND_SOC_DAPM_PRE_PMD:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE34), ps3);
> > + from_ps = ps0;
> > + to_ps = ps3;
> > + break;
> > + }
> > +
> > + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 34");
> > + ret = sdca_asoc_pde_poll_actual_ps(component->dev,
> rt766->regmap,
> > + RT766_FUNC_NUM_UAJ,
> > + RT766_SDCA_ENT_PDE34,
> > + from_ps, to_ps,
> > + entity ? entity->pde.max_delay :
> NULL,
> > + entity ?
> entity->pde.num_max_delay : 0);
> > + if (ret)
> > + dev_err(component->dev, "%s: PDE transition %x -> %x
> failed, err=%d\n",
> > + __func__, from_ps, to_ps, ret);
> > +
> > + return ret;
> > +}
>
>
> > +static int rt766_sdca_probe(struct snd_soc_component *component) {
> > + struct rt766_sdca_priv *rt766 =
> snd_soc_component_get_drvdata(component);
> > + struct device *dev = &rt766->slave->dev;
> > + int ret;
> > +
> > + rt766->component = component;
> > +
> > + ret = pm_runtime_resume(component->dev);
> > + if (ret < 0 && ret != -EACCES)
> > + return ret;
> > +
> > + if (rt766->uaj_func_data) {
> > + dev_dbg(dev, "%s : irq %d\n", __func__,
> > + rt766->slave->irq);
> > +
> > + rt766->irq_info = sdca_irq_allocate(dev, rt766->regmap,
> rt766->slave->irq);
> > + if (IS_ERR(rt766->irq_info))
> > + return PTR_ERR(rt766->irq_info);
> > +
> > + ret = rt766_sdca_irq_ctl(rt766, rt766->uaj_func_data,
> > + component, rt766->irq_info, true);
>
> same question on IRQ allocate/request for Charles.
>
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to request UAJ SDCA IRQ:
> %d\n", ret);
> > + return ret;
> > + }
> > +
> > + if (rt766->hid_func_data) {
> > + ret = rt766_sdca_irq_ctl(rt766,
> rt766->hid_func_data,
> > + component, rt766->irq_info, true);
> > + if (ret < 0) {
> > + dev_err(dev, "Failed to request HID
> SDCA IRQ: %d\n", ret);
> > + return ret;
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
>
> > +static unsigned int rt766_find_dt_rates(struct device *dev, struct
> sdca_function_data *function,
> > + const char
> > +*label) {
> > + struct sdca_entity *entity = NULL;
>
> useless init?
>
> > + unsigned int rates;
> > + int i, ret;
> > +
> > + for (i = 0; i < function->num_entities; i++) {
> > + entity = &function->entities[i];
> > +
> > + if (strcmp(entity->label, label))
> > + continue;
> > +
> > + /* Can't check earlier as only terminals have an iot
> member. */
> > + if (!entity->iot.is_dataport)
> > + continue;
> > +
> > + ret = rt766_parse_rates(dev, function, entity, &rates);
> > + if (ret < 0) {
> > + dev_dbg(dev, "%s: failed to parse rates for entity
> %s\n",
> > + __func__, entity->label);
> > + return 0;
> > + }
> > +
> > + dev_dbg(dev, "%s: %s supports rates 0x%08x\n", __func__,
> entity->label, rates);
> > + }
> > +
> > + return rates;
> > +}
> > +
> > +int rt766_sdca_init(struct device *dev, struct regmap *regmap, struct
> > +sdw_slave *slave) {
> > + struct sdca_function_data *func_data_ptr;
> > + struct rt766_sdca_priv *rt766;
> > + unsigned int rates;
> > + int ret;
> > + int i;
> > +
> > + rt766 = devm_kzalloc(dev, sizeof(*rt766), GFP_KERNEL);
> > + if (!rt766)
> > + return -ENOMEM;
> > +
> > + dev_set_drvdata(dev, rt766);
> > + rt766->slave = slave;
> > + rt766->regmap = regmap;
> > +
> > + regcache_cache_only(rt766->regmap, true);
> > +
> > + mutex_init(&rt766->disable_irq_lock);
> > +
> > + /*
> > + * Mark hw_init to false
> > + * HW init will be performed when device reports present
> > + */
> > + rt766->hw_init = false;
> > + rt766->first_hw_init = false;
> > + rt766->fu41_dapm_mute = true;
> > + rt766->fu41_mixer_l_mute = rt766->fu41_mixer_r_mute = false;
> > + rt766->fu36_dapm_mute = true;
> > + rt766->fu36_mixer_l_mute = rt766->fu36_mixer_r_mute = true;
> > + rt766->fu21_dapm_mute = true;
> > + rt766->fu21_mixer_l_mute = rt766->fu21_mixer_r_mute = false;
> > + rt766->fu113_dapm_mute = true;
> > + rt766->fu113_mixer_mute[0] = rt766->fu113_mixer_mute[1] =
> > + rt766->fu113_mixer_mute[2] =
> rt766->fu113_mixer_mute[3]
> > + = true;
> > +
> > + /* get SDCA function data */
> > + dev_dbg(dev, "SDCA functions found: %d",
> slave->sdca_data.num_functions);
> > + for (i = 0; i < slave->sdca_data.num_functions; i++) {
> > + func_data_ptr = devm_kzalloc(dev, sizeof(*func_data_ptr),
> GFP_KERNEL);
> > + if (!func_data_ptr)
> > + return dev_err_probe(dev, -ENOMEM, "failed to
> > + allocate function data");
> > +
> > + func_data_ptr->desc = &slave->sdca_data.function[i];
> > + ret = sdca_parse_function(dev, slave, func_data_ptr);
> > + if (ret) {
> > + devm_kfree(dev, func_data_ptr);
> > + return ret;
> > + }
> > + dev_dbg(dev, "Function type=%d, num_entities=%d",
> > + slave->sdca_data.function[i].type,
> > + func_data_ptr->num_entities);
> > +
> > + switch (slave->sdca_data.function[i].type) {
> > + case SDCA_FUNCTION_TYPE_UAJ:
> > + rt766->uaj_func_data = func_data_ptr;
> > + /*
> > + * Some machines may only support a subset of
> the sample rates supported by the codec.
> > + * Therefore, we need to parse the supported
> sample rates from the DisCo table and
> > + * configure them in the DAI. If the DisCo table
> does not provide sample rate information,
> > + * we will fall back to the default supported rates
> defined in the codec driver.
> > + */
> > + rates = rt766_find_dt_rates(dev, func_data_ptr,
> "IT 41");
> > + if (rates)
> > + rt766_sdca_dai[0].playback.rates =
> > + rates;
> > +
> > + rates = rt766_find_dt_rates(dev, func_data_ptr,
> "OT 36");
> > + if (rates)
> > + rt766_sdca_dai[0].capture.rates =
> rates;
> > + break;
> > + case SDCA_FUNCTION_TYPE_SMART_AMP:
> > + rt766->sa_func_data = func_data_ptr;
> > + rates = rt766_find_dt_rates(dev, func_data_ptr,
> "IT 21");
> > + if (rates)
> > + rt766_sdca_dai[1].playback.rates =
> rates;
> > + break;
> > + case SDCA_FUNCTION_TYPE_SMART_MIC:
> > + rt766->sm_func_data = func_data_ptr;
> > + rates = rt766_find_dt_rates(dev, func_data_ptr,
> "OT 113");
> > + if (rates)
> > + rt766_sdca_dai[2].capture.rates =
> rates;
>
> are the sdca_dai[index] related to the function? Wondering if we can use e.g
> sdca_dai[MIC] instead?
Sure, will use the enumeration value.
> > + break;
> > + case SDCA_FUNCTION_TYPE_HID:
> > + rt766->hid_func_data = func_data_ptr;
> > + break;
> > + default:
> > + dev_dbg(dev, "Unexpected SDCA function type
> found: %d",
> > + slave->sdca_data.function[i].type);
> > + }
> > + }
> > +
> > + ret = devm_snd_soc_register_component(dev,
> > + &soc_sdca_dev_rt766, rt766_sdca_dai,
> ARRAY_SIZE(rt766_sdca_dai));
> > + if (ret < 0)
> > + return ret;
> > +
> > + /* set autosuspend parameters */
> > + pm_runtime_set_autosuspend_delay(dev, 3000);
> > + pm_runtime_use_autosuspend(dev);
> > +
> > + /* make sure the device does not suspend immediately */
> > + pm_runtime_mark_last_busy(dev);
> > +
> > + pm_runtime_enable(dev);
> > +
> > + dev_dbg(dev, "%s\n", __func__);
> > +
> > + return 0;
> > +}
>
^ permalink raw reply [flat|nested] 7+ messages in thread
* RE: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-20 16:21 ` Charles Keepax
@ 2026-07-21 7:10 ` Shuming [范書銘]
2026-07-21 10:29 ` Charles Keepax
2026-07-22 13:19 ` Charles Keepax
0 siblings, 2 replies; 7+ messages in thread
From: Shuming [范書銘] @ 2026-07-21 7:10 UTC (permalink / raw)
To: Charles Keepax
Cc: broonie@kernel.org, lgirdwood@gmail.com,
linux-sound@vger.kernel.org, lars@metafoo.de, Flove(HsinFu),
Oder Chiou, Jack Yu, Derek [方德義],
Pierre-Louis Bossart
> > +static struct sdca_entity *rt766_find_entity_by_label(struct
> sdca_function_data *func,
> > + const char *label)
> > +{
> > + struct sdca_entity *entity = NULL;
> > + int idx;
> > +
> > + for (idx = 0; idx < func->num_entities; idx++) {
> > + entity = &func->entities[idx];
> > +
> > + if (!strcmp(entity->label, label))
> > + return entity;
> > + }
> > +
> > + return NULL;
> > +}
>
> Probably better to export find_sdca_entity_by_label() if you need this from the
> driver.
Thanks for the review.
I will try to export find_sdca_entity_by_label() and populate_rate_format().
> > +static int rt766_sdca_irq_ctl(struct rt766_sdca_priv *rt766,
> > + struct
> sdca_function_data *function,
> > + struct
> snd_soc_component *component,
> > + struct
> sdca_interrupt_info *info,
> > + bool
> enabled)
> > +{
> > + struct device *dev = &rt766->slave->dev;
> > + struct sdca_interrupt *interrupt;
> > + struct sdca_control *control;
> > + struct sdca_entity *entity;
> > + irq_handler_t handler;
> > + int i, j, irq, ret;
> > +
> > + for (i = 0; i < function->num_entities; i++) {
> > + entity = &function->entities[i];
> > +
> > + for (j = 0; j < entity->num_controls; j++) {
> > + control = &entity->controls[j];
> > + irq = control->interrupt_position;
> > +
> > + switch (SDCA_CTL_TYPE(entity->type,
> control->sel)) {
> > + case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
> > + handler = rt766_sdca_irq_jd_handler;
> > + break;
> > + case SDCA_CTL_TYPE_S(HIDE,
> HIDTX_CURRENTOWNER):
> > + handler = rt766_sdca_irq_btn_handler;
> > + break;
> > + default:
> > + continue;
> > + }
> > +
> > + interrupt = &info->irqs[irq];
> > +
> > + if (enabled) {
> > + ret = sdca_irq_data_populate(dev,
> rt766->regmap, component,
> > +
> function, entity, control,
> > +
> interrupt);
> > + if (ret)
> > + return ret;
> > +
> > + interrupt->priv = rt766;
> > + ret = sdca_irq_request(dev, info, irq,
> interrupt->name,
> > +
> handler, interrupt);
> > + if (ret) {
> > + dev_err(dev, "failed to
> request irq %s: %d\n",
> > + interrupt->name,
> ret);
> > + return ret;
> > + }
> > + dev_dbg(dev, "Requesting IRQ %d
> InterruptName=%s\n", irq, interrupt->name);
> > + } else {
> > + sdca_irq_free(dev, info, irq,
> interrupt->name, interrupt);
> > + dev_dbg(dev, "Freeing IRQ %d\n", irq);
> > + }
> > + }
> > + }
> > +
> > + return 0;
> > +}
>
> Hmm... yeah this is moving in a slightly different direction from where I was
> going with the core code. Let me think about that and update tomorrow.
Sorry, I don't quite follow the point.
Do you mean that it will not work with this patch (ASoC: SDCA: Populate IRQ data earlier)?
> > +static int rt766_sdca_pde47_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 rt766_sdca_priv *rt766 =
> snd_soc_component_get_drvdata(component);
> > + const struct sdca_entity *entity = NULL;
> > + unsigned char ps0 = 0x0, ps3 = 0x3;
> > + int from_ps, to_ps;
> > + int ret;
> > +
> > + switch (event) {
> > + case SND_SOC_DAPM_POST_PMU:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE47), ps0);
> > + from_ps = ps3;
> > + to_ps = ps0;
> > + break;
> > + case SND_SOC_DAPM_PRE_PMD:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(UAJ,
> PDE47), ps3);
> > + from_ps = ps0;
> > + to_ps = ps3;
> > + break;
> > + }
> > +
> > + entity = rt766_find_entity_by_label(rt766->uaj_func_data, "PDE 47");
> > + ret = sdca_asoc_pde_poll_actual_ps(component->dev,
> rt766->regmap,
> > + RT766_FUNC_NUM_UAJ,
> > + RT766_SDCA_ENT_PDE47,
> > + from_ps, to_ps,
> > + entity ? entity->pde.max_delay :
> NULL,
> > + entity ?
> entity->pde.num_max_delay : 0);
> > + if (ret)
> > + dev_err(component->dev, "%s: PDE transition %x -> %x
> failed, err=%d\n",
> > + __func__, from_ps, to_ps, ret);
>
> Does this actually run in cases where the entity doesn't exist? I would be
> inclined to error out if you didn't find the entity.
Ok, will fix
> > +static int rt766_sdca_pde11_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 rt766_sdca_priv *rt766 =
> snd_soc_component_get_drvdata(component);
> > + const struct sdca_entity *entity = NULL;
> > + unsigned char ps0 = 0x0, ps3 = 0x3;
> > + int from_ps, to_ps;
> > + int ret;
> > +
> > + switch (event) {
> > + case SND_SOC_DAPM_POST_PMU:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC,
> PDE11), ps0);
> > + from_ps = ps3;
> > + to_ps = ps0;
> > + break;
> > + case SND_SOC_DAPM_PRE_PMD:
> > + regmap_write(rt766->regmap, RT766_PDE_REQ_REG(MIC,
> PDE11), ps3);
> > + from_ps = ps0;
> > + to_ps = ps3;
> > + break;
> > + }
> > +
> > + entity = rt766_find_entity_by_label(rt766->sm_func_data, "PDE 11");
> > + ret = sdca_asoc_pde_poll_actual_ps(component->dev,
> rt766->regmap,
> > + RT766_FUNC_NUM_MIC,
> > + RT766_SDCA_ENT_PDE11,
> > + from_ps, to_ps,
> > + entity ? entity->pde.max_delay :
> NULL,
> > + entity ?
> entity->pde.num_max_delay : 0);
> > + if (ret)
> > + dev_err(component->dev, "%s: PDE transition %x -> %x
> failed, err=%d\n",
> > + __func__, from_ps, to_ps, ret);
> > +
> > + return ret;
> > +}
>
> I feel like lot of these put/get and event helpers would benefit from a
> parameterised helper. You have loads of functions doing the same thing,
> would it be nicer to have a helper that takes the required registers etc.
Sure, will create helper functions for put/get and event.
> > +static int rt766_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 rt766_sdca_priv *rt766 =
> 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;
> > + int retval, port, num_channels;
> > + unsigned int sampling_rate;
> > +
> > + dev_dbg(dai->dev, "%s %s id %d", __func__, dai->name, dai->id);
> > + sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
> > +
> > + if (!sdw_stream)
> > + return -EINVAL;
> > +
> > + if (!rt766->slave)
> > + return -EINVAL;
> > +
> > + /* SoundWire specific configuration */
> > + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> > + direction = SDW_DATA_DIR_RX;
> > + if (dai->id == RT766_AIF1)
> > + port = 3;
> > + else if (dai->id == RT766_AIF2)
> > + port = 1;
> > + else
> > + return -EINVAL;
> > + } else {
> > + direction = SDW_DATA_DIR_TX;
> > + if (dai->id == RT766_AIF1)
> > + port = 12;
> > + else if (dai->id == RT766_AIF3)
> > + port = 8;
> > + else
> > + return -EINVAL;
> > + }
> > +
> > + stream_config.frame_rate = params_rate(params);
> > + stream_config.ch_count = params_channels(params);
> > + stream_config.bps =
> snd_pcm_format_width(params_format(params));
> > + stream_config.direction = direction;
> > +
> > + num_channels = params_channels(params);
> > + port_config.ch_mask = GENMASK(num_channels - 1, 0);
> > + port_config.num = port;
>
> Can you use snd_sdw_params_to_config here?
OK, will fix
> > +static int rt766_parse_rates(struct device *dev,
> > + struct sdca_function_data *function,
> > + struct sdca_entity *entity,
> > + unsigned int *out_rates) {
> > + struct sdca_control_range *range;
> > + unsigned int sample_rate;
> > + unsigned int clock_rates = 0;
> > + unsigned int rates = 0;
> > + int sel, i;
> > +
> > + switch (entity->type) {
> > + case SDCA_ENTITY_TYPE_IT:
> > + sel = SDCA_CTL_IT_USAGE;
> > + break;
> > + case SDCA_ENTITY_TYPE_OT:
> > + sel = SDCA_CTL_OT_USAGE;
> > + break;
> > + default:
> > + dev_err(dev, "%s: entity type has no usage control\n",
> > + entity->label);
> > + return -EINVAL;
> > + }
> > +
> > + if (entity->iot.clock) {
> > + range = sdca_selector_find_range(dev, entity->iot.clock,
> > +
> SDCA_CTL_CS_SAMPLERATEINDEX,
> > +
> SDCA_SAMPLERATEINDEX_NCOLS, 0);
> > + if (!range)
> > + return -EINVAL;
> > +
> > + for (i = 0; i < range->rows; i++) {
> > + sample_rate = sdca_range(range,
> SDCA_SAMPLERATEINDEX_RATE, i);
> > + clock_rates |= rate_find_mask(dev,
> sample_rate);
> > + }
> > + } else {
> > + clock_rates = UINT_MAX;
> > + }
> > +
> > + range = sdca_selector_find_range(dev, entity, sel,
> SDCA_USAGE_NCOLS, 0);
> > + if (!range)
> > + return -EINVAL;
> > +
> > + for (i = 0; i < range->rows; i++) {
> > + sample_rate = sdca_range(range,
> SDCA_USAGE_SAMPLE_RATE, i);
> > + sample_rate = rate_find_mask(dev, sample_rate);
> > +
> > + if (sample_rate & clock_rates)
> > + rates |= sample_rate;
> > + }
> > +
> > + *out_rates = rates;
> > +
> > + dev_dbg(dev, "%s: entity %s supports rates 0x%08x,
> clock_rates=0x%08x\n",
> > + __func__, entity->label, rates, clock_rates);
> > +
> > + return 0;
> > +}
>
> This is mostly just populate_rate_format(), the values end up in the
> snd_soc_pcm_stream, can we export that and use it here?
Sure, will try to export populate_rate_format() and use it here.
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-21 7:10 ` Shuming [范書銘]
@ 2026-07-21 10:29 ` Charles Keepax
2026-07-22 13:19 ` Charles Keepax
1 sibling, 0 replies; 7+ messages in thread
From: Charles Keepax @ 2026-07-21 10:29 UTC (permalink / raw)
To: Shuming [范書銘]
Cc: broonie@kernel.org, lgirdwood@gmail.com,
linux-sound@vger.kernel.org, lars@metafoo.de, Flove(HsinFu),
Oder Chiou, Jack Yu, Derek [方德義],
Pierre-Louis Bossart
On Tue, Jul 21, 2026 at 07:10:00AM +0000, Shuming [范書銘] wrote:
> > > + if (ret)
> > > + return ret;
> > > +
> > > + interrupt->priv = rt766;
> > > + ret = sdca_irq_request(dev, info, irq,
> > interrupt->name,
> > > +
> > handler, interrupt);
> > > + if (ret) {
> > > + dev_err(dev, "failed to
> > request irq %s: %d\n",
> > > + interrupt->name,
> > ret);
> > > + return ret;
> > > + }
> > > + dev_dbg(dev, "Requesting IRQ %d
> > InterruptName=%s\n", irq, interrupt->name);
> > > + } else {
> > > + sdca_irq_free(dev, info, irq,
> > interrupt->name, interrupt);
> > > + dev_dbg(dev, "Freeing IRQ %d\n", irq);
> > > + }
> > > + }
> > > + }
> > > +
> > > + return 0;
> > > +}
> >
> > Hmm... yeah this is moving in a slightly different direction from where I was
> > going with the core code. Let me think about that and update tomorrow.
>
> Sorry, I don't quite follow the point.
> Do you mean that it will not work with this patch (ASoC: SDCA: Populate IRQ data earlier)?
Mostly, the issue here is you are calling things at different
times to the class driver, so the devm allocations will probably
leak but I think I will update my patch chain to drop the devm
allocations to keep things flexible. There are some minor updates
required to this patch for my core changes but that is the only
one that doesn't work I think.
I will send out a new spin later today and CC you on it.
Thanks,
Charles
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver
2026-07-21 7:10 ` Shuming [范書銘]
2026-07-21 10:29 ` Charles Keepax
@ 2026-07-22 13:19 ` Charles Keepax
1 sibling, 0 replies; 7+ messages in thread
From: Charles Keepax @ 2026-07-22 13:19 UTC (permalink / raw)
To: Shuming [范書銘]
Cc: broonie@kernel.org, lgirdwood@gmail.com,
linux-sound@vger.kernel.org, lars@metafoo.de, Flove(HsinFu),
Oder Chiou, Jack Yu, Derek [方德義],
Pierre-Louis Bossart
On Tue, Jul 21, 2026 at 07:10:00AM +0000, Shuming [范書銘] wrote:
> > > +
> > > + *out_rates = rates;
> > > +
> > > + dev_dbg(dev, "%s: entity %s supports rates 0x%08x,
> > clock_rates=0x%08x\n",
> > > + __func__, entity->label, rates, clock_rates);
> > > +
> > > + return 0;
> > > +}
> >
> > This is mostly just populate_rate_format(), the values end up in the
> > snd_soc_pcm_stream, can we export that and use it here?
>
> Sure, will try to export populate_rate_format() and use it here.
Looking a bit more is there really anything that would stop you
just using sdca_asoc_populate_dais(), which is already exported?
You would probably want to fixup the channels_max at the end and
the naming of the DAIs would end up being slightly more SDCA,
but those don't seem like huge problems.
Thanks,
Charles
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-07-22 13:20 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20 9:06 [PATCH] ASoC: rt766: add RT766/RT767 SDCA driver shumingf
2026-07-20 14:38 ` Pierre-Louis Bossart
2026-07-21 7:09 ` Shuming [范書銘]
2026-07-20 16:21 ` Charles Keepax
2026-07-21 7:10 ` Shuming [范書銘]
2026-07-21 10:29 ` Charles Keepax
2026-07-22 13:19 ` Charles Keepax
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.