Merge branch 'mic' into combined

# Conflicts:
#	arch/arm64/boot/dts/qcom/milos-fairphone-fp6.dts
This commit is contained in:
Jorijn van der Graaf 2026-07-06 01:14:20 +02:00
commit 2931c83762
7 changed files with 2242 additions and 1 deletions

View file

@ -209,13 +209,34 @@
sound {
compatible = "qcom,milos-sndcard", "qcom,sm8450-sndcard";
model = "Fairphone (Gen. 6)";
// audio-routing = ...
audio-routing = "AMIC1", "MIC BIAS1",
"AMIC2", "MIC BIAS2",
"AMIC3", "MIC BIAS3",
"TX SWR_INPUT0", "ADC1_OUTPUT",
"TX SWR_INPUT1", "ADC2_OUTPUT",
"TX SWR_INPUT2", "ADC3_OUTPUT";
pinctrl-0 = <&lpi_i2s2_active>;
pinctrl-1 = <&lpi_i2s2_sleep>;
pinctrl-names = "default",
"sleep";
wcd-capture-dai-link {
link-name = "WCD Capture";
codec {
sound-dai = <&wcd9378 1>, <&swr2 0>, <&lpass_txmacro 0>;
};
cpu {
sound-dai = <&q6apmbedai TX_CODEC_DMA_TX_3>;
};
platform {
sound-dai = <&q6apm>;
};
};
i2s-dai-link {
link-name = "Senary MI2S Playback";
@ -233,6 +254,29 @@
};
};
wcd9378: audio-codec {
compatible = "qcom,wcd9378-codec";
pinctrl-0 = <&wcd_reset_n_active>;
pinctrl-names = "default";
reset-gpios = <&tlmm 162 GPIO_ACTIVE_LOW>;
vdd-buck-supply = <&vreg_l8b>;
vdd-rxtx-supply = <&vreg_l7b>;
vdd-io-supply = <&vreg_l7b>;
vdd-mic-bias-supply = <&vreg_bob>;
qcom,micbias1-microvolt = <1800000>;
qcom,micbias2-microvolt = <1800000>;
qcom,micbias3-microvolt = <1800000>;
qcom,rx-device = <&wcd9378_rx>;
qcom,tx-device = <&wcd9378_tx>;
#sound-dai-cells = <1>;
};
thermal-zones {
pm8008-thermal {
polling-delay-passive = <100>;
@ -1103,6 +1147,36 @@
};
};
&swr1 {
status = "okay";
/* WCD9378 RX */
wcd9378_rx: codec@0,4 {
compatible = "sdw20217011000";
reg = <0 4>;
qcom,rx-port-mapping = <1 2 3 4 5>;
};
};
&swr2 {
status = "okay";
/* WCD9378 TX */
wcd9378_tx: codec@0,3 {
compatible = "sdw20217011000";
reg = <0 3>;
/*
* WCD9378 TX port 1 (ADC1) <=> SWR2 port 1 (SWRM_TX1)
* WCD9378 TX port 2 (ADC2) <=> SWR2 port 1
* WCD9378 TX port 3 (ADC3) <=> SWR2 port 1
* WCD9378 TX port 4 (DMIC0,1, MBHC) <=> SWR2 port 2
* WCD9378 TX port 5 (DMIC2..5) <=> SWR2 port 3
*/
qcom,tx-port-mapping = <1 1 1 2 3>;
};
};
&tlmm {
gpio-reserved-ranges = <8 4>, /* Fingerprint SPI */
<13 1>, /* NC */
@ -1255,6 +1329,13 @@
drive-strength = <2>;
bias-pull-down;
};
wcd_reset_n_active: wcd-reset-n-active-state {
pins = "gpio162";
function = "gpio";
drive-strength = <16>;
output-high;
};
};
&uart5 {

View file

@ -976,6 +976,20 @@ static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus,
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
int ret, i, len;
if (msg->page) {
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page1,
msg->dev_num,
SDW_SCP_ADDRPAGE1);
if (ret)
return SDW_CMD_IGNORED;
ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->addr_page2,
msg->dev_num,
SDW_SCP_ADDRPAGE2);
if (ret)
return SDW_CMD_IGNORED;
}
if (msg->flags == SDW_MSG_FLAG_READ) {
for (i = 0; i < msg->len;) {
len = min(msg->len - i, QCOM_SWRM_MAX_RD_LEN);

View file

@ -2431,6 +2431,22 @@ config SND_SOC_WCD937X_SDW
via soundwire.
To compile this codec driver say Y or m.
config SND_SOC_WCD9378
depends on SND_SOC_WCD9378_SDW
tristate
depends on SOUNDWIRE || !SOUNDWIRE
select SND_SOC_WCD_COMMON
config SND_SOC_WCD9378_SDW
tristate "WCD9378 Codec - SDW"
select SND_SOC_WCD9378
depends on SOUNDWIRE
select REGMAP_SOUNDWIRE
help
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

View file

@ -356,6 +356,8 @@ 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-y := wcd9378.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 +798,11 @@ 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) += snd-soc-wcd9378.o
ifdef CONFIG_SND_SOC_WCD9378_SDW
# avoid link failure by forcing sdw code built-in when needed
obj-$(CONFIG_SND_SOC_WCD9378) += snd-soc-wcd9378-sdw.o
endif
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

View file

@ -0,0 +1,487 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2026, Jorijn van der Graaf
*
* SoundWire slave driver for the Qualcomm WCD9378 audio codec.
*
* The codec presents two SoundWire slaves (RX and TX, mfg 0x0217 part
* 0x0110); the SDCA control space is a 32-bit paged register map accessed
* through the TX slave.
*/
#include <linux/component.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw_type.h>
#include <sound/pcm_params.h>
#include "wcd-common.h"
#include "wcd9378.h"
#define WCD9378_SDW_CH(id, pn, cmask, mmask) \
[id] = { \
.port_num = pn, \
.ch_mask = cmask, \
.master_ch_mask = mmask, \
}
/*
* Each ADC sits alone on its own TX device port (channel 1); by default
* they land on channels 1/2/3 of the same master port (SWRM_TX1 on the
* FP6). DMIC/MBHC masks per the downstream qcom,tx_swr_ch_map.
*/
static struct wcd_sdw_ch_info wcd9378_sdw_tx_ch_info[] = {
WCD9378_SDW_CH(WCD9378_ADC1, WCD9378_ADC_1_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_ADC2, WCD9378_ADC_2_PORT, BIT(0), BIT(1)),
WCD9378_SDW_CH(WCD9378_ADC3, WCD9378_ADC_3_PORT, BIT(0), BIT(2)),
WCD9378_SDW_CH(WCD9378_DMIC0, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(0)),
WCD9378_SDW_CH(WCD9378_DMIC1, WCD9378_DMIC_0_1_MBHC_PORT, BIT(3), BIT(1)),
WCD9378_SDW_CH(WCD9378_MBHC, WCD9378_DMIC_0_1_MBHC_PORT, BIT(2), BIT(2)),
WCD9378_SDW_CH(WCD9378_DMIC2, WCD9378_DMIC_2_5_PORT, BIT(0), BIT(2)),
WCD9378_SDW_CH(WCD9378_DMIC3, WCD9378_DMIC_2_5_PORT, BIT(1), BIT(3)),
WCD9378_SDW_CH(WCD9378_DMIC4, WCD9378_DMIC_2_5_PORT, BIT(2), BIT(0)),
WCD9378_SDW_CH(WCD9378_DMIC5, WCD9378_DMIC_2_5_PORT, BIT(3), BIT(1)),
};
static struct wcd_sdw_ch_info wcd9378_sdw_rx_ch_info[] = {
WCD9378_SDW_CH(WCD9378_HPH_L, WCD9378_HPH_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_HPH_R, WCD9378_HPH_PORT, BIT(1), BIT(1)),
WCD9378_SDW_CH(WCD9378_CLSH, WCD9378_CLSH_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_COMP_L, WCD9378_COMP_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_COMP_R, WCD9378_COMP_PORT, BIT(1), BIT(1)),
WCD9378_SDW_CH(WCD9378_LO, WCD9378_LO_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_DSD_L, WCD9378_DSD_PORT, BIT(0), BIT(0)),
WCD9378_SDW_CH(WCD9378_DSD_R, WCD9378_DSD_PORT, BIT(1), BIT(1)),
};
static struct sdw_dpn_prop wcd9378_dpn_prop[WCD9378_MAX_SWR_PORTS] = {
{
.num = 1,
.type = SDW_DPN_SIMPLE,
.min_ch = 1,
.max_ch = 8,
.simple_ch_prep_sm = true,
}, {
.num = 2,
.type = SDW_DPN_SIMPLE,
.min_ch = 1,
.max_ch = 4,
.simple_ch_prep_sm = true,
}, {
.num = 3,
.type = SDW_DPN_SIMPLE,
.min_ch = 1,
.max_ch = 4,
.simple_ch_prep_sm = true,
}, {
.num = 4,
.type = SDW_DPN_SIMPLE,
.min_ch = 1,
.max_ch = 4,
.simple_ch_prep_sm = true,
}, {
.num = 5,
.type = SDW_DPN_SIMPLE,
.min_ch = 1,
.max_ch = 4,
.simple_ch_prep_sm = true,
}
};
int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS];
unsigned long ch_mask;
int i, j;
wcd->sconfig.ch_count = 1;
wcd->active_ports = 0;
for (i = 0; i < WCD9378_MAX_SWR_PORTS; i++) {
ch_mask = wcd->port_config[i].ch_mask;
if (!ch_mask)
continue;
for_each_set_bit(j, &ch_mask, 4)
wcd->sconfig.ch_count++;
port_config[wcd->active_ports] = wcd->port_config[i];
wcd->active_ports++;
}
wcd->sconfig.bps = 1;
wcd->sconfig.frame_rate = params_rate(params);
wcd->sconfig.direction = wcd->is_tx ? SDW_DATA_DIR_TX : SDW_DATA_DIR_RX;
wcd->sconfig.type = SDW_STREAM_PCM;
return sdw_stream_add_slave(wcd->sdev, &wcd->sconfig,
&port_config[0], wcd->active_ports,
wcd->sruntime);
}
EXPORT_SYMBOL_GPL(wcd9378_sdw_hw_params);
/*
* Tell the codec the bus clock: base 19.2 MHz plus a scale (div) per bank.
* The downstream driver writes these raw SCP registers on every capture
* start; here the bus_config callback covers bank switches.
*/
static int wcd9378_bus_config(struct sdw_slave *slave,
struct sdw_bus_params *params)
{
u8 scale;
switch (params->curr_dr_freq) {
case 4800000:
scale = WCD9378_SWRS_CLK_SCALE_DIV4;
break;
case 9600000:
default:
scale = WCD9378_SWRS_CLK_SCALE_DIV2;
break;
}
sdw_write(slave, WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(params->next_bank),
0x01);
sdw_write(slave, WCD9378_SWRS_SCP_BASE_CLK,
WCD9378_SWRS_BASE_CLK_19P2MHZ);
sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0, scale);
sdw_write(slave, WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1, scale);
return 0;
}
static const struct sdw_slave_ops wcd9378_slave_ops = {
.update_status = wcd_update_status,
.bus_config = wcd9378_bus_config,
};
static const struct reg_default wcd9378_defaults[] = {
{ WCD9378_ANA_BIAS, 0x00 },
{ WCD9378_ANA_TX_CH1, 0x20 },
{ WCD9378_ANA_TX_CH2, 0x00 },
{ WCD9378_ANA_TX_CH3, 0x20 },
{ WCD9378_ANA_TX_CH3_HPF, 0x00 },
{ WCD9378_ANA_MICB2_RAMP, 0x00 },
{ WCD9378_BIAS_VBG_FINE_ADJ, 0x55 },
{ WCD9378_MBHC_CTL_SPARE_1, 0x02 },
{ WCD9378_MICB1_TEST_CTL_2, 0x00 },
{ WCD9378_MICB2_TEST_CTL_2, 0x00 },
{ WCD9378_MICB3_TEST_CTL_2, 0x80 },
{ WCD9378_TX_COM_TXFE_DIV_CTL, 0x22 },
{ WCD9378_SLEEP_CTL, 0x16 },
{ WCD9378_TX_NEW_CH12_MUX, 0x11 },
{ WCD9378_TX_NEW_CH34_MUX, 0x23 },
{ WCD9378_TOP_CLK_CFG, 0x00 },
{ WCD9378_CDC_ANA_TX_CLK_CTL, 0x0e },
{ WCD9378_CDC_AMIC_CTL, 0x07 },
{ WCD9378_PDM_WD_CTL0, 0x0f },
{ WCD9378_PDM_WD_CTL1, 0x0f },
{ WCD9378_PLATFORM_CTL, 0x01 },
{ WCD9378_SYS_USAGE_CTRL, 0x00 },
{ WCD9378_HPH_UP_T0, 0x02 },
{ WCD9378_HPH_UP_T9, 0x02 },
{ WCD9378_HPH_DN_T0, 0x05 },
{ WCD9378_MICB_REMAP_TABLE_VAL_3, 0x00 },
{ WCD9378_MICB_REMAP_TABLE_VAL_4, 0x00 },
{ WCD9378_MICB_REMAP_TABLE_VAL_5, 0x00 },
{ WCD9378_SM0_MB_SEL, 0x00 },
{ WCD9378_SM1_MB_SEL, 0x00 },
{ WCD9378_SM2_MB_SEL, 0x00 },
{ WCD9378_MB_PULLUP_EN, 0x00 },
{ WCD9378_SMP_AMP_FUNC_ACT, 0x00 },
{ WCD9378_CMT_GRP_MASK, 0x00 },
{ WCD9378_SMP_JACK_IT31_MICB, 0x00 },
{ WCD9378_SMP_JACK_IT31_USAGE, 0x03 },
{ WCD9378_SMP_JACK_PDE34_REQ_PS, 0x03 },
{ WCD9378_SMP_JACK_FUNC_ACT, 0x00 },
{ WCD9378_SMP_MIC_IT11_MICB(0), 0x00 },
{ WCD9378_SMP_MIC_IT11_USAGE(0), 0x03 },
{ WCD9378_SMP_MIC_PDE11_REQ_PS(0), 0x03 },
{ WCD9378_SMP_MIC_FUNC_ACT(0), 0x00 },
{ WCD9378_SMP_MIC_IT11_MICB(1), 0x00 },
{ WCD9378_SMP_MIC_IT11_USAGE(1), 0x03 },
{ WCD9378_SMP_MIC_PDE11_REQ_PS(1), 0x03 },
{ WCD9378_SMP_MIC_FUNC_ACT(1), 0x00 },
{ WCD9378_SMP_MIC_IT11_MICB(2), 0x00 },
{ WCD9378_SMP_MIC_IT11_USAGE(2), 0x03 },
{ WCD9378_SMP_MIC_PDE11_REQ_PS(2), 0x03 },
{ WCD9378_SMP_MIC_FUNC_ACT(2), 0x00 },
};
static bool wcd9378_rdwr_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case WCD9378_ANA_BIAS:
case WCD9378_ANA_TX_CH1:
case WCD9378_ANA_TX_CH2:
case WCD9378_ANA_TX_CH3:
case WCD9378_ANA_TX_CH3_HPF:
case WCD9378_ANA_MICB2_RAMP:
case WCD9378_BIAS_VBG_FINE_ADJ:
case WCD9378_MBHC_CTL_SPARE_1:
case WCD9378_MICB1_TEST_CTL_2:
case WCD9378_MICB2_TEST_CTL_2:
case WCD9378_MICB3_TEST_CTL_2:
case WCD9378_TX_COM_TXFE_DIV_CTL:
case WCD9378_SLEEP_CTL:
case WCD9378_TX_NEW_CH12_MUX:
case WCD9378_TX_NEW_CH34_MUX:
case WCD9378_HPH_RDAC_GAIN_CTL:
case WCD9378_HPH_RDAC_HD2_CTL_L:
case WCD9378_HPH_RDAC_HD2_CTL_R:
case WCD9378_TOP_CLK_CFG:
case WCD9378_CDC_ANA_TX_CLK_CTL:
case WCD9378_CDC_AMIC_CTL:
case WCD9378_PDM_WD_CTL0:
case WCD9378_PDM_WD_CTL1:
case WCD9378_PLATFORM_CTL:
case WCD9378_SYS_USAGE_CTRL:
case WCD9378_HPH_UP_T0:
case WCD9378_HPH_UP_T9:
case WCD9378_HPH_DN_T0:
case WCD9378_MICB_REMAP_TABLE_VAL_3:
case WCD9378_MICB_REMAP_TABLE_VAL_4:
case WCD9378_MICB_REMAP_TABLE_VAL_5:
case WCD9378_SM0_MB_SEL:
case WCD9378_SM1_MB_SEL:
case WCD9378_SM2_MB_SEL:
case WCD9378_MB_PULLUP_EN:
case WCD9378_SMP_AMP_FUNC_STAT:
case WCD9378_SMP_AMP_FUNC_ACT:
case WCD9378_CMT_GRP_MASK:
case WCD9378_SMP_JACK_IT31_MICB:
case WCD9378_SMP_JACK_IT31_USAGE:
case WCD9378_SMP_JACK_PDE34_REQ_PS:
case WCD9378_SMP_JACK_FUNC_STAT:
case WCD9378_SMP_JACK_FUNC_ACT:
case WCD9378_SMP_MIC_IT11_MICB(0):
case WCD9378_SMP_MIC_IT11_USAGE(0):
case WCD9378_SMP_MIC_PDE11_REQ_PS(0):
case WCD9378_SMP_MIC_FUNC_STAT(0):
case WCD9378_SMP_MIC_FUNC_ACT(0):
case WCD9378_SMP_MIC_IT11_MICB(1):
case WCD9378_SMP_MIC_IT11_USAGE(1):
case WCD9378_SMP_MIC_PDE11_REQ_PS(1):
case WCD9378_SMP_MIC_FUNC_STAT(1):
case WCD9378_SMP_MIC_FUNC_ACT(1):
case WCD9378_SMP_MIC_IT11_MICB(2):
case WCD9378_SMP_MIC_IT11_USAGE(2):
case WCD9378_SMP_MIC_PDE11_REQ_PS(2):
case WCD9378_SMP_MIC_FUNC_STAT(2):
case WCD9378_SMP_MIC_FUNC_ACT(2):
return true;
}
return false;
}
static bool wcd9378_volatile_register(struct device *dev, unsigned int reg)
{
switch (reg) {
case WCD9378_FUNC_EXT_ID_0:
case WCD9378_FUNC_EXT_ID_1:
case WCD9378_FUNC_EXT_VER:
case WCD9378_FUNC_STAT:
case WCD9378_DEV_MANU_ID_0:
case WCD9378_DEV_MANU_ID_1:
case WCD9378_DEV_PART_ID_0:
case WCD9378_DEV_PART_ID_1:
case WCD9378_DEV_VER:
case WCD9378_EFUSE_REG_16:
case WCD9378_EFUSE_REG_29:
case WCD9378_SEQ_TX0_STAT:
case WCD9378_SEQ_TX1_STAT:
case WCD9378_SEQ_TX2_STAT:
case WCD9378_SMP_JACK_PDE34_ACT_PS:
case WCD9378_SMP_MIC_OT10_USAGE(0):
case WCD9378_SMP_MIC_PDE11_ACT_PS(0):
case WCD9378_SMP_MIC_OT10_USAGE(1):
case WCD9378_SMP_MIC_PDE11_ACT_PS(1):
case WCD9378_SMP_MIC_OT10_USAGE(2):
case WCD9378_SMP_MIC_PDE11_ACT_PS(2):
return true;
}
return false;
}
static bool wcd9378_readable_register(struct device *dev, unsigned int reg)
{
if (wcd9378_volatile_register(dev, reg))
return true;
return wcd9378_rdwr_register(dev, reg);
}
static const struct regmap_config wcd9378_regmap_config = {
.name = "wcd9378_csr",
.reg_bits = 32,
.val_bits = 8,
.cache_type = REGCACHE_MAPLE,
.reg_defaults = wcd9378_defaults,
.num_reg_defaults = ARRAY_SIZE(wcd9378_defaults),
.max_register = WCD9378_MAX_REGISTER,
.readable_reg = wcd9378_readable_register,
.writeable_reg = wcd9378_rdwr_register,
.volatile_reg = wcd9378_volatile_register,
};
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;
u8 master_ch_mask[WCD9378_MAX_SWR_CH_IDS];
int master_ch_mask_size = 0;
int ret, i;
wcd = devm_kzalloc(dev, sizeof(*wcd), GFP_KERNEL);
if (!wcd)
return -ENOMEM;
/* Port map index starts at 0, however the data ports start at index 1 */
if (of_property_present(dev->of_node, "qcom,tx-port-mapping")) {
wcd->is_tx = true;
ret = of_property_read_u32_array(dev->of_node, "qcom,tx-port-mapping",
&pdev->m_port_map[1],
WCD9378_MAX_TX_SWR_PORTS);
} else {
ret = of_property_read_u32_array(dev->of_node, "qcom,rx-port-mapping",
&pdev->m_port_map[1],
WCD9378_MAX_SWR_PORTS);
}
if (ret < 0)
dev_info(dev, "Error getting static port mapping for %s (%d)\n",
wcd->is_tx ? "TX" : "RX", ret);
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.lane_control_support = true;
pdev->prop.simple_clk_stop_capable = true;
/* The SDCA control space sits above the 16-bit address range */
pdev->prop.paging_support = true;
memset(master_ch_mask, 0, WCD9378_MAX_SWR_CH_IDS);
if (wcd->is_tx) {
master_ch_mask_size = of_property_count_u8_elems(dev->of_node,
"qcom,tx-channel-mapping");
if (master_ch_mask_size > 0)
ret = of_property_read_u8_array(dev->of_node,
"qcom,tx-channel-mapping",
master_ch_mask,
master_ch_mask_size);
} else {
master_ch_mask_size = of_property_count_u8_elems(dev->of_node,
"qcom,rx-channel-mapping");
if (master_ch_mask_size > 0)
ret = of_property_read_u8_array(dev->of_node,
"qcom,rx-channel-mapping",
master_ch_mask,
master_ch_mask_size);
}
if (wcd->is_tx) {
pdev->prop.source_ports = GENMASK(WCD9378_MAX_TX_SWR_PORTS, 1);
pdev->prop.src_dpn_prop = wcd9378_dpn_prop;
wcd->ch_info = &wcd9378_sdw_tx_ch_info[0];
for (i = 0; i < master_ch_mask_size; i++)
wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]);
pdev->prop.wake_capable = true;
wcd->regmap = devm_regmap_init_sdw(pdev, &wcd9378_regmap_config);
if (IS_ERR(wcd->regmap))
return dev_err_probe(dev, PTR_ERR(wcd->regmap),
"Regmap init failed\n");
/* Start in cache-only until device is enumerated */
regcache_cache_only(wcd->regmap, true);
} else {
pdev->prop.sink_ports = GENMASK(WCD9378_MAX_SWR_PORTS, 1);
pdev->prop.sink_dpn_prop = wcd9378_dpn_prop;
wcd->ch_info = &wcd9378_sdw_rx_ch_info[0];
for (i = 0; i < master_ch_mask_size; i++)
wcd->ch_info[i].master_ch_mask = WCD9378_SWRM_CH_MASK(master_ch_mask[i]);
}
ret = component_add(dev, &wcd_sdw_component_ops);
if (ret)
return ret;
/* Set suspended until aggregate device is bind */
pm_runtime_set_suspended(dev);
return 0;
}
static void wcd9378_sdw_remove(struct sdw_slave *pdev)
{
struct device *dev = &pdev->dev;
component_del(dev, &wcd_sdw_component_ops);
}
static const struct sdw_device_id wcd9378_sdw_id[] = {
SDW_SLAVE_ENTRY(0x0217, 0x0110, 0),
{ },
};
MODULE_DEVICE_TABLE(sdw, wcd9378_sdw_id);
static int wcd9378_sdw_runtime_suspend(struct device *dev)
{
struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev);
if (wcd->regmap) {
regcache_cache_only(wcd->regmap, true);
regcache_mark_dirty(wcd->regmap);
}
return 0;
}
static int wcd9378_sdw_runtime_resume(struct device *dev)
{
struct wcd9378_sdw_priv *wcd = dev_get_drvdata(dev);
if (wcd->regmap) {
regcache_cache_only(wcd->regmap, false);
regcache_sync(wcd->regmap);
}
return 0;
}
static const struct dev_pm_ops wcd9378_sdw_pm_ops = {
RUNTIME_PM_OPS(wcd9378_sdw_runtime_suspend, wcd9378_sdw_runtime_resume, NULL)
};
static struct sdw_driver wcd9378_sdw_driver = {
.probe = wcd9378_sdw_probe,
.remove = wcd9378_sdw_remove,
.ops = &wcd9378_slave_ops,
.id_table = wcd9378_sdw_id,
.driver = {
.name = "wcd9378-sdw",
.pm = pm_ptr(&wcd9378_sdw_pm_ops),
}
};
module_sdw_driver(wcd9378_sdw_driver);
MODULE_DESCRIPTION("WCD9378 SDW codec driver");
MODULE_LICENSE("GPL");

1394
sound/soc/codecs/wcd9378.c Normal file

File diff suppressed because it is too large Load diff

242
sound/soc/codecs/wcd9378.h Normal file
View file

@ -0,0 +1,242 @@
/* 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; on top of that the chip adds SDCA-style functions
* (SmartMIC0/1/2, SmartJACK, SmartAMP) whose sequencers drive the analog
* power-up autonomously.
*/
#ifndef __WCD9378_H__
#define __WCD9378_H__
#include <linux/regmap.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include <sound/soc.h>
/* 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_BIAS_ANALOG_BIAS_EN BIT(7)
#define WCD9378_ANA_BIAS_PRECHRG_EN BIT(6)
#define WCD9378_ANA_RX_SUPPLIES 0x40180008
#define WCD9378_ANA_TX_CH1 0x4018000e
#define WCD9378_ANA_TX_CH2 0x4018000f
#define WCD9378_ANA_TX_CH2_HPF1_INIT BIT(6)
#define WCD9378_ANA_TX_CH2_HPF2_INIT BIT(5)
#define WCD9378_ANA_TX_CH3 0x40180010
#define WCD9378_ANA_TX_CH3_HPF 0x40180011
#define WCD9378_ANA_TX_CH3_HPF3_INIT BIT(6)
#define WCD9378_ANA_TX_GAIN_MASK GENMASK(4, 0)
#define WCD9378_ANA_MICB1 0x40180022
#define WCD9378_ANA_MICB2 0x40180023
#define WCD9378_ANA_MICB2_RAMP 0x40180024
#define WCD9378_ANA_MICB2_RAMP_SHIFT_CTL_MASK GENMASK(4, 2)
#define WCD9378_ANA_MICB2_RAMP_EN BIT(7)
#define WCD9378_ANA_MICB3 0x40180025
#define WCD9378_BIAS_VBG_FINE_ADJ 0x40180029
#define WCD9378_MBHC_CTL_SPARE_1 0x40180058
#define WCD9378_MICB1_TEST_CTL_2 0x4018006c
#define WCD9378_MICB2_TEST_CTL_2 0x4018006f
#define WCD9378_MICB3_TEST_CTL_2 0x40180072
#define WCD9378_TX_COM_TXFE_DIV_CTL 0x4018007b
#define WCD9378_TX_COM_TXFE_DIV_SEQ_BYPASS BIT(7)
#define WCD9378_SLEEP_CTL 0x40180103
#define WCD9378_SLEEP_CTL_BG_CTL_MASK GENMASK(3, 1)
#define WCD9378_SLEEP_CTL_BG_EN BIT(7)
#define WCD9378_SLEEP_CTL_LDOL_BG_SEL BIT(6)
#define WCD9378_TX_NEW_CH12_MUX 0x4018012e
#define WCD9378_TX_NEW_CH12_MUX_CH1_SEL_MASK GENMASK(2, 0)
#define WCD9378_TX_NEW_CH12_MUX_CH2_SEL_MASK GENMASK(5, 3)
#define WCD9378_TX_NEW_CH34_MUX 0x4018012f
#define WCD9378_TX_NEW_CH34_MUX_CH3_SEL_MASK GENMASK(2, 0)
#define WCD9378_HPH_RDAC_GAIN_CTL 0x40180132
#define WCD9378_HPH_RDAC_HD2_CTL_L 0x40180133
#define WCD9378_HPH_RDAC_HD2_CTL_R 0x40180136
/* Digital page */
#define WCD9378_TOP_CLK_CFG 0x40180407
#define WCD9378_CDC_ANA_TX_CLK_CTL 0x40180417
#define WCD9378_CDC_ANA_TXSCBIAS_CLK_EN BIT(0)
#define WCD9378_CDC_AMIC_CTL 0x4018045a
#define WCD9378_PDM_WD_CTL0 0x40180465
#define WCD9378_PDM_WD_CTL1 0x40180466
#define WCD9378_EFUSE_REG_16 0x401804c0
#define WCD9378_EFUSE_REG_29 0x401804cd
#define WCD9378_PLATFORM_CTL 0x401804f0
/* Sequencer block (SEQR) */
#define WCD9378_SYS_USAGE_CTRL 0x40180501
#define WCD9378_SYS_USAGE_CTRL_MASK GENMASK(3, 0)
#define WCD9378_HPH_UP_T0 0x40180510
#define WCD9378_HPH_UP_T9 0x40180519
#define WCD9378_HPH_DN_T0 0x4018051b
#define WCD9378_SEQ_TX0_STAT 0x40180592
#define WCD9378_SEQ_TX1_STAT 0x40180593
#define WCD9378_SEQ_TX2_STAT 0x40180594
#define WCD9378_MICB_REMAP_TABLE_VAL_3 0x401805a3
#define WCD9378_MICB_REMAP_TABLE_VAL_4 0x401805a4
#define WCD9378_MICB_REMAP_TABLE_VAL_5 0x401805a5
#define WCD9378_SM0_MB_SEL 0x401805b0
#define WCD9378_SM1_MB_SEL 0x401805b1
#define WCD9378_SM2_MB_SEL 0x401805b2
#define WCD9378_SM_MB_SEL_MASK GENMASK(1, 0)
#define WCD9378_MB_PULLUP_EN 0x401805b3
/* SmartAMP SDCA function */
#define WCD9378_SMP_AMP_FUNC_STAT 0x40880000
#define WCD9378_SMP_AMP_FUNC_ACT 0x40880008
/* SmartJACK SDCA function (hosts ADC2 when fed from AMIC2) */
#define WCD9378_CMT_GRP_MASK 0x40c00008
#define WCD9378_SMP_JACK_IT31_MICB 0x40c00798
#define WCD9378_SMP_JACK_IT31_USAGE 0x40c007a0
#define WCD9378_SMP_JACK_PDE34_REQ_PS 0x40c00808
#define WCD9378_SMP_JACK_FUNC_STAT 0x40c80000
#define WCD9378_SMP_JACK_FUNC_ACT 0x40c80008
#define WCD9378_SMP_JACK_PDE34_ACT_PS 0x40c80800
/* SmartMIC0/1/2 SDCA functions (ADC1/ADC2/ADC3 sequencers) */
#define WCD9378_SMP_MIC_BASE(n) (0x41000000 + (n) * 0x400000)
#define WCD9378_SMP_MIC_IT11_MICB(n) (WCD9378_SMP_MIC_BASE(n) + 0x98)
#define WCD9378_SMP_MIC_IT11_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0xa0)
#define WCD9378_SMP_MIC_PDE11_REQ_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x108)
#define WCD9378_SMP_MIC_OT10_USAGE(n) (WCD9378_SMP_MIC_BASE(n) + 0x3a0)
#define WCD9378_SMP_MIC_FUNC_STAT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80000)
#define WCD9378_SMP_MIC_FUNC_ACT(n) (WCD9378_SMP_MIC_BASE(n) + 0x80008)
#define WCD9378_SMP_MIC_PDE11_ACT_PS(n) (WCD9378_SMP_MIC_BASE(n) + 0x80100)
#define WCD9378_MAX_REGISTER 0x41900070
/*
* Raw (16-bit, non-paged) Qualcomm slave SCP registers, written with
* sdw_write() directly. Bus clock indication towards the codec.
*/
#define WCD9378_SWRS_SCP_BASE_CLK 0x4d
#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK0 0x62
#define WCD9378_SWRS_SCP_BUSCLK_SCALE_BANK1 0x72
#define WCD9378_SWRS_SCP_HOST_CLK_DIV2_CTL(m) (0xe0 + 0x10 * (m))
#define WCD9378_SWRS_BASE_CLK_19P2MHZ 0x01
#define WCD9378_SWRS_CLK_SCALE_DIV2 0x02 /* 9.6 MHz */
#define WCD9378_SWRS_CLK_SCALE_DIV4 0x03 /* 4.8 MHz */
/* ITxx_USAGE ADC mode values */
#define WCD9378_ADC_USAGE_HIFI 0x01
#define WCD9378_ADC_USAGE_LO_HIF 0x02
#define WCD9378_ADC_USAGE_NORMAL 0x03
#define WCD9378_ADC_USAGE_LP 0x05
#define WCD9378_ADC_USAGE_OFF 0x00
/* ITxx_MICB usage values */
#define WCD9378_MICB_USAGE_OFF 0x00
#define WCD9378_MICB_USAGE_PULL_DOWN 0x01
#define WCD9378_MICB_USAGE_1P2V 0x02
#define WCD9378_MICB_USAGE_1P8V_OR_PULLUP 0x03
#define WCD9378_MICB_USAGE_2P5V 0x04
#define WCD9378_MICB_USAGE_2P75V 0x05
#define WCD9378_MICB_USAGE_2P2V 0xf0
#define WCD9378_MICB_USAGE_2P7V 0xf1
#define WCD9378_MICB_USAGE_2P8V 0xf2
#define WCD9378_MICB_USAGE_REMAP_TABLE_3 0xf3
#define WCD9378_MICB_USAGE_REMAP_TABLE_4 0xf4
#define WCD9378_MICB_USAGE_REMAP_TABLE_5 0xf5
/* PDExx_REQ_PS power states */
#define WCD9378_PDE_PS0_ON 0x00
#define WCD9378_PDE_PS3_OFF 0x03
#define WCD9378_MAX_MICBIAS 3
#define WCD9378_MAX_SWR_CH_IDS 15
#define WCD9378_SWRM_CH_MASK(ch_idx) BIT((ch_idx) - 1)
enum wcd9378_tx_sdw_ports {
WCD9378_ADC_1_PORT = 1,
WCD9378_ADC_2_PORT,
WCD9378_ADC_3_PORT,
WCD9378_DMIC_0_1_MBHC_PORT,
WCD9378_DMIC_2_5_PORT,
WCD9378_MAX_TX_SWR_PORTS = WCD9378_DMIC_2_5_PORT,
};
enum wcd9378_rx_sdw_ports {
WCD9378_HPH_PORT = 1,
WCD9378_CLSH_PORT,
WCD9378_COMP_PORT,
WCD9378_LO_PORT,
WCD9378_DSD_PORT,
WCD9378_MAX_SWR_PORTS = WCD9378_DSD_PORT,
};
enum wcd9378_tx_sdw_channels {
WCD9378_ADC1,
WCD9378_ADC2,
WCD9378_ADC3,
WCD9378_DMIC0,
WCD9378_DMIC1,
WCD9378_MBHC,
WCD9378_DMIC2,
WCD9378_DMIC3,
WCD9378_DMIC4,
WCD9378_DMIC5,
};
enum wcd9378_rx_sdw_channels {
WCD9378_HPH_L,
WCD9378_HPH_R,
WCD9378_CLSH,
WCD9378_COMP_L,
WCD9378_COMP_R,
WCD9378_LO,
WCD9378_DSD_L,
WCD9378_DSD_R,
};
struct wcd9378_priv;
struct wcd9378_sdw_priv {
struct sdw_slave *sdev;
struct sdw_stream_config sconfig;
struct sdw_stream_runtime *sruntime;
struct sdw_port_config port_config[WCD9378_MAX_SWR_PORTS];
struct wcd_sdw_ch_info *ch_info;
bool port_enable[WCD9378_MAX_SWR_CH_IDS];
unsigned int master_channel_map[SDW_MAX_PORTS];
int active_ports;
bool is_tx;
struct wcd9378_priv *wcd9378;
struct regmap *regmap;
};
#if IS_ENABLED(CONFIG_SND_SOC_WCD9378_SDW)
int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai);
#else
static inline int wcd9378_sdw_hw_params(struct wcd9378_sdw_priv *wcd,
struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
return -EOPNOTSUPP;
}
#endif
#endif /* __WCD9378_H__ */