diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 269c31ce0814..18f489a3501b 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -2431,6 +2431,15 @@ config SND_SOC_WCD937X_SDW via soundwire. To compile this codec driver say Y or m. +config SND_SOC_WCD9378_SDW + tristate "WCD9378 Codec - SDW (bring-up skeleton)" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + help + Bring-up skeleton driver for the Qualcomm WCD9378 audio codec + connected via SoundWire, as found on SM7635 phones. + To compile this codec driver say Y or m. + config SND_SOC_WCD938X depends on SND_SOC_WCD938X_SDW tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 172861d17cfd..466d7bd1a514 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -356,6 +356,7 @@ snd-soc-wcd9335-y := wcd9335.o snd-soc-wcd934x-y := wcd934x.o snd-soc-wcd937x-y := wcd937x.o snd-soc-wcd937x-sdw-y := wcd937x-sdw.o +snd-soc-wcd9378-sdw-y := wcd9378-sdw.o snd-soc-wcd938x-y := wcd938x.o snd-soc-wcd938x-sdw-y := wcd938x-sdw.o snd-soc-wcd939x-y := wcd939x.o @@ -796,6 +797,7 @@ ifdef CONFIG_SND_SOC_WCD937X_SDW # avoid link failure by forcing sdw code built-in when needed obj-$(CONFIG_SND_SOC_WCD937X) += snd-soc-wcd937x-sdw.o endif +obj-$(CONFIG_SND_SOC_WCD9378_SDW) += snd-soc-wcd9378-sdw.o obj-$(CONFIG_SND_SOC_WCD938X) += snd-soc-wcd938x.o ifdef CONFIG_SND_SOC_WCD938X_SDW # avoid link failure by forcing sdw code built-in when needed diff --git a/sound/soc/codecs/wcd9378-sdw.c b/sound/soc/codecs/wcd9378-sdw.c new file mode 100644 index 000000000000..e613f6277113 --- /dev/null +++ b/sound/soc/codecs/wcd9378-sdw.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2026, Jorijn van der Graaf + * + * SoundWire slave driver for the Qualcomm WCD9378 audio codec. + * + * Bring-up skeleton: binds to the two WCD9378 SoundWire devices (TX and + * RX), maps the SDCA control space through regmap and verifies the device + * identity registers. No audio paths yet. + */ + +#include +#include +#include +#include +#include +#include + +#include "wcd9378.h" + +struct wcd9378_sdw_priv { + struct sdw_slave *sdev; + struct regmap *regmap; +}; + +static const struct regmap_config wcd9378_sdw_regmap_config = { + .name = "wcd9378_sdw", + .reg_bits = 32, + .val_bits = 8, + .cache_type = REGCACHE_NONE, + .max_register = WCD9378_MAX_REGISTER, +}; + +static void wcd9378_sdw_read_id(struct wcd9378_sdw_priv *wcd) +{ + struct device *dev = &wcd->sdev->dev; + static const struct { + const char *name; + unsigned int reg; + } id_regs[] = { + { "DEV_MANU_ID_0", WCD9378_DEV_MANU_ID_0 }, + { "DEV_MANU_ID_1", WCD9378_DEV_MANU_ID_1 }, + { "DEV_PART_ID_0", WCD9378_DEV_PART_ID_0 }, + { "DEV_PART_ID_1", WCD9378_DEV_PART_ID_1 }, + { "DEV_VER", WCD9378_DEV_VER }, + { "FUNC_EXT_ID_0", WCD9378_FUNC_EXT_ID_0 }, + { "FUNC_EXT_ID_1", WCD9378_FUNC_EXT_ID_1 }, + { "ANA_BIAS", WCD9378_ANA_BIAS }, + { "ANA_TX_CH1", WCD9378_ANA_TX_CH1 }, + { "ANA_MICB1", WCD9378_ANA_MICB1 }, + { "SYS_USAGE_CTRL", WCD9378_SYS_USAGE_CTRL }, + }; + unsigned int val, packed, pval; + int i, ret, pret; + + for (i = 0; i < ARRAY_SIZE(id_regs); i++) { + /* downstream WCD9378_REG() wire packing */ + packed = ((id_regs[i].reg & 0x0ff00000) >> 8) | + (id_regs[i].reg & 0xfff); + + ret = regmap_read(wcd->regmap, id_regs[i].reg, &val); + pret = regmap_read(wcd->regmap, packed, &pval); + dev_info(dev, "%s: virt %#010x = %#04x (ret %d), packed %#07x = %#04x (ret %d)\n", + id_regs[i].name, + id_regs[i].reg, ret ? 0xdead : val, ret, + packed, pret ? 0xdead : pval, pret); + } +} + +static int wcd9378_sdw_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct wcd9378_sdw_priv *wcd = dev_get_drvdata(&slave->dev); + + dev_info(&slave->dev, "status %d (dev_num %d)\n", status, + slave->dev_num); + + if (status == SDW_SLAVE_ATTACHED) + wcd9378_sdw_read_id(wcd); + + return 0; +} + +static const struct sdw_slave_ops wcd9378_sdw_ops = { + .update_status = wcd9378_sdw_update_status, +}; + +static int wcd9378_sdw_probe(struct sdw_slave *pdev, + const struct sdw_device_id *id) +{ + struct device *dev = &pdev->dev; + struct wcd9378_sdw_priv *wcd; + + wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL); + if (!wcd) + return -ENOMEM; + + wcd->sdev = pdev; + dev_set_drvdata(dev, wcd); + + pdev->prop.scp_int1_mask = SDW_SCP_INT1_IMPL_DEF | + SDW_SCP_INT1_BUS_CLASH | + SDW_SCP_INT1_PARITY; + pdev->prop.simple_clk_stop_capable = true; + /* The SDCA control space sits above the 16-bit address range */ + pdev->prop.paging_support = true; + + wcd->regmap = devm_regmap_init_sdw(pdev, &wcd9378_sdw_regmap_config); + if (IS_ERR(wcd->regmap)) + return dev_err_probe(dev, PTR_ERR(wcd->regmap), + "regmap init failed\n"); + + dev_info(dev, "wcd9378 sdw slave probed\n"); + + return 0; +} + +static const struct sdw_device_id wcd9378_sdw_id[] = { + SDW_SLAVE_ENTRY(0x0217, 0x0110, 0), + { }, +}; +MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id); + +static struct sdw_driver wcd9378_sdw_driver = { + .probe = wcd9378_sdw_probe, + .ops = &wcd9378_sdw_ops, + .id_table = wcd9378_sdw_id, + .driver = { + .name = "wcd9378-sdw", + }, +}; +module_sdw_driver(wcd9378_sdw_driver); + +MODULE_DESCRIPTION("WCD9378 SoundWire slave driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wcd9378.h b/sound/soc/codecs/wcd9378.h new file mode 100644 index 000000000000..380786f2765e --- /dev/null +++ b/sound/soc/codecs/wcd9378.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2026, Jorijn van der Graaf + * + * Register map for the Qualcomm WCD9378 audio codec. + * + * The codec exposes its control registers in the SoundWire SDCA control + * address space (bit 30 set, SDCA function number in bits 25:22), accessed + * through the TX SoundWire slave. The analog core registers (function 0, + * implementation-defined region at +0x180000) are layout-compatible with + * the WCD937x family. + */ + +#ifndef __WCD9378_H__ +#define __WCD9378_H__ + +#include +#include + +/* SDCA function 0 (extension unit): device identity */ +#define WCD9378_FUNC_EXT_ID_0 0x40000048 +#define WCD9378_FUNC_EXT_ID_1 0x40000049 +#define WCD9378_FUNC_EXT_VER 0x40000050 +#define WCD9378_FUNC_STAT 0x40080000 +#define WCD9378_DEV_MANU_ID_0 0x40100060 +#define WCD9378_DEV_MANU_ID_1 0x40100061 +#define WCD9378_DEV_PART_ID_0 0x40100068 +#define WCD9378_DEV_PART_ID_1 0x40100069 +#define WCD9378_DEV_VER 0x40100070 + +/* Analog core (WCD937x-compatible layout), function 0 + 0x180000 */ +#define WCD9378_ANA_PAGE 0x40180000 +#define WCD9378_ANA_BIAS 0x40180001 +#define WCD9378_ANA_RX_SUPPLIES 0x40180008 +#define WCD9378_ANA_TX_CH1 0x4018000e +#define WCD9378_ANA_TX_CH2 0x4018000f +#define WCD9378_ANA_TX_CH3 0x40180010 +#define WCD9378_ANA_MICB1 0x40180022 +#define WCD9378_ANA_MICB2 0x40180023 +#define WCD9378_ANA_MICB2_RAMP 0x40180024 +#define WCD9378_ANA_MICB3 0x40180025 + +/* Sequencer block (SEQR) */ +#define WCD9378_SYS_USAGE_CTRL 0x40180501 +#define WCD9378_SM0_MB_SEL 0x401805b0 +#define WCD9378_SM1_MB_SEL 0x401805b1 +#define WCD9378_SM2_MB_SEL 0x401805b2 + +/* SDCA function activation (one per function) */ +#define WCD9378_SMP_AMP_FUNC_STAT 0x40880000 +#define WCD9378_SMP_AMP_FUNC_ACT 0x40880008 +#define WCD9378_SMP_JACK_FUNC_STAT 0x40c80000 +#define WCD9378_SMP_JACK_FUNC_ACT 0x40c80008 +#define WCD9378_SMP_MIC_CTRL0_FUNC_STAT 0x41080000 +#define WCD9378_SMP_MIC_CTRL0_FUNC_ACT 0x41080008 +#define WCD9378_SMP_MIC_CTRL1_FUNC_STAT 0x41480000 +#define WCD9378_SMP_MIC_CTRL1_FUNC_ACT 0x41480008 +#define WCD9378_SMP_MIC_CTRL2_FUNC_STAT 0x41880000 +#define WCD9378_SMP_MIC_CTRL2_FUNC_ACT 0x41880008 + +#define WCD9378_MAX_REGISTER 0x41900070 + +#endif /* __WCD9378_H__ */