[PATCH 2/3] soundwire: qcom: add multi-master support
From: Srinivas Kandagatla
Date: Fri Sep 11 2026 - 04:40:13 EST
v3.1.0+ Qualcomm SoundWire IPs can be paired to expose one logical
bus with more than two DATA lanes. One IP owns SWR_CLK and drives
the bus; the other contributes DATA lanes only.
Support this pairing on the driver side. A device link between the
two IPs enforces correct unbind ordering and keeps the peer resumed
while the primary is active.
Single-master deployments (no qcom,multi-master-mode in DT) are
unaffected.
Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@xxxxxxxxxxxxxxxx>
---
drivers/soundwire/qcom.c | 171 +++++++++++++++++++++++++++++++++++----
1 file changed, 156 insertions(+), 15 deletions(-)
diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c
index 35ffffd541bd..a5f39868161a 100644
--- a/drivers/soundwire/qcom.c
+++ b/drivers/soundwire/qcom.c
@@ -10,6 +10,7 @@
#include <linux/debugfs.h>
#include <linux/of.h>
#include <linux/of_irq.h>
+#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
@@ -111,6 +112,7 @@
#define SWRM_DPn_PORT_HCTRL_BANK(offset, n, m) (offset + 0x100 * (n - 1) + 0x40 * m)
#define SWRM_DPn_BLOCK_CTRL3_BANK(offset, n, m) (offset + 0x100 * (n - 1) + 0x40 * m)
#define SWRM_DPn_SAMPLECTRL2_BANK(offset, n, m) (offset + 0x100 * (n - 1) + 0x40 * m)
+#define SWRM_DPn_SLOT_STRIDE 0x100
#define SWR_V1_3_MSTR_MAX_REG_ADDR 0x1740
#define SWR_V2_0_MSTR_MAX_REG_ADDR 0x50ac
@@ -192,6 +194,13 @@ struct qcom_swrm_ctrl {
const unsigned int *reg_layout;
void __iomem *mmio;
struct reset_control *audio_cgcr;
+ u8 num_lanes;
+ bool is_dependent;
+ struct qcom_swrm_ctrl *peer_ctrl;
+ u8 peer_first_lane;
+ u8 num_peer_lanes;
+ bool is_primary;
+ u32 peer_dpn_offset;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs;
#endif
@@ -1040,9 +1049,41 @@ static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus)
u32p_replace_bits(&val, ctrl->cols_index, SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK);
u32p_replace_bits(&val, ctrl->rows_index, SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK);
+ if (ctrl->peer_ctrl)
+ ctrl->peer_ctrl->reg_write(ctrl->peer_ctrl, reg, val);
+
return ctrl->reg_write(ctrl, reg, val);
}
+static struct qcom_swrm_ctrl *
+qcom_swrm_port_target(struct qcom_swrm_ctrl *ctrl, u8 port_num, u32 *reg)
+{
+ u8 lane = ctrl->pconfig[port_num].lane_control;
+
+ if (ctrl->peer_ctrl && lane >= ctrl->peer_first_lane &&
+ lane < ctrl->peer_first_lane + ctrl->num_peer_lanes) {
+ *reg -= ctrl->peer_dpn_offset;
+ return ctrl->peer_ctrl;
+ }
+ return ctrl;
+}
+
+static int qcom_swrm_port_reg_write(struct qcom_swrm_ctrl *ctrl, u8 port_num,
+ u32 reg, u32 val)
+{
+ struct qcom_swrm_ctrl *tgt = qcom_swrm_port_target(ctrl, port_num, ®);
+
+ return tgt->reg_write(tgt, reg, val);
+}
+
+static int qcom_swrm_port_reg_read(struct qcom_swrm_ctrl *ctrl, u8 port_num,
+ u32 reg, u32 *val)
+{
+ struct qcom_swrm_ctrl *tgt = qcom_swrm_port_target(ctrl, port_num, ®);
+
+ return tgt->reg_read(tgt, reg, val);
+}
+
static int qcom_swrm_port_params(struct sdw_bus *bus,
struct sdw_port_params *p_params,
unsigned int bank)
@@ -1050,8 +1091,9 @@ static int qcom_swrm_port_params(struct sdw_bus *bus,
struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus);
u32 offset = ctrl->reg_layout[SWRM_OFFSET_DP_BLOCK_CTRL_1];
- return ctrl->reg_write(ctrl, SWRM_DPn_BLOCK_CTRL_1(offset, p_params->num),
- p_params->bps - 1);
+ return qcom_swrm_port_reg_write(ctrl, p_params->num,
+ SWRM_DPn_BLOCK_CTRL_1(offset, p_params->num),
+ p_params->bps - 1);
}
static int qcom_swrm_transport_params(struct sdw_bus *bus,
@@ -1072,7 +1114,7 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
value |= pcfg->off2 << SWRM_DP_PORT_CTRL_OFFSET2_SHFT;
value |= pcfg->si & 0xff;
- ret = ctrl->reg_write(ctrl, reg, value);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
if (ret)
goto err;
@@ -1081,7 +1123,7 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
value = (pcfg->si >> 8) & 0xff;
reg = SWRM_DPn_SAMPLECTRL2_BANK(offset, params->port_num, bank);
- ret = ctrl->reg_write(ctrl, reg, value);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
if (ret)
goto err;
}
@@ -1091,7 +1133,10 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
reg = SWRM_DPn_PORT_CTRL_2_BANK(offset, params->port_num, bank);
value = pcfg->lane_control;
- ret = ctrl->reg_write(ctrl, reg, value);
+ if (ctrl->peer_ctrl && value >= ctrl->peer_first_lane &&
+ value < ctrl->peer_first_lane + ctrl->num_peer_lanes)
+ value -= ctrl->peer_first_lane;
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
if (ret)
goto err;
}
@@ -1102,7 +1147,7 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
reg = SWRM_DPn_BLOCK_CTRL2_BANK(offset, params->port_num, bank);
value = pcfg->blk_group_count;
- ret = ctrl->reg_write(ctrl, reg, value);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
if (ret)
goto err;
}
@@ -1112,10 +1157,10 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
if (pcfg->hstart != SWR_INVALID_PARAM && pcfg->hstop != SWR_INVALID_PARAM) {
value = (pcfg->hstop << 4) | pcfg->hstart;
- ret = ctrl->reg_write(ctrl, reg, value);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
} else {
value = (SWR_HSTOP_MAX_VAL << 4) | SWR_HSTART_MIN_VAL;
- ret = ctrl->reg_write(ctrl, reg, value);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg, value);
}
if (ret)
@@ -1124,7 +1169,8 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus,
if (pcfg->bp_mode != SWR_INVALID_PARAM) {
offset = ctrl->reg_layout[SWRM_OFFSET_DP_BLOCK_CTRL3_BANK];
reg = SWRM_DPn_BLOCK_CTRL3_BANK(offset, params->port_num, bank);
- ret = ctrl->reg_write(ctrl, reg, pcfg->bp_mode);
+ ret = qcom_swrm_port_reg_write(ctrl, params->port_num, reg,
+ pcfg->bp_mode);
}
err:
@@ -1142,14 +1188,14 @@ static int qcom_swrm_port_enable(struct sdw_bus *bus,
reg = SWRM_DPn_PORT_CTRL_BANK(offset, enable_ch->port_num, bank);
- ctrl->reg_read(ctrl, reg, &val);
+ qcom_swrm_port_reg_read(ctrl, enable_ch->port_num, reg, &val);
if (enable_ch->enable)
val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
else
val &= ~(0xff << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT);
- return ctrl->reg_write(ctrl, reg, val);
+ return qcom_swrm_port_reg_write(ctrl, enable_ch->port_num, reg, val);
}
static const struct sdw_master_port_ops qcom_swrm_port_ops = {
@@ -1562,6 +1608,68 @@ static int swrm_reg_show(struct seq_file *s_file, void *data)
DEFINE_SHOW_ATTRIBUTE(swrm_reg);
#endif
+static int qcom_swrm_multi_master_setup(struct qcom_swrm_ctrl *ctrl)
+{
+ struct of_phandle_args args;
+ struct qcom_swrm_ctrl *peer;
+ struct platform_device *pdev;
+ u32 offset;
+ int ret;
+
+ ret = of_parse_phandle_with_fixed_args(ctrl->dev->of_node,
+ "qcom,multi-master-peer",
+ 1, 0, &args);
+ if (ret)
+ return -EINVAL;
+
+ pdev = of_find_device_by_node(args.np);
+ of_node_put(args.np);
+ if (!pdev)
+ return -EPROBE_DEFER;
+
+ if (pdev->dev.driver != ctrl->dev->driver) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ peer = platform_get_drvdata(pdev);
+ if (!peer || !peer->is_dependent) {
+ ret = -EPROBE_DEFER;
+ goto out;
+ }
+
+ if (!ctrl->num_lanes || !peer->num_lanes ||
+ ctrl->num_lanes + peer->num_lanes > SDW_MAX_LANES) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ offset = args.args[0];
+ if (offset < ctrl->num_lanes) {
+ dev_err(ctrl->dev, "qcom,multi-master-peer: offset %u < num-lanes %u\n",
+ offset, ctrl->num_lanes);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!device_link_add(ctrl->dev, &pdev->dev,
+ DL_FLAG_AUTOREMOVE_CONSUMER | DL_FLAG_PM_RUNTIME)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ctrl->peer_ctrl = peer;
+ peer->peer_ctrl = ctrl;
+ ctrl->peer_first_lane = ctrl->num_lanes;
+ ctrl->num_peer_lanes = peer->num_lanes;
+ ctrl->peer_dpn_offset = offset * SWRM_DPn_SLOT_STRIDE;
+
+ ret = 0;
+out:
+ put_device(&pdev->dev);
+ return ret;
+}
+
static int qcom_swrm_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -1576,6 +1684,9 @@ static int qcom_swrm_probe(struct platform_device *pdev)
if (!ctrl)
return -ENOMEM;
+ if (!of_property_read_u32(dev->of_node, "num-lanes", &val))
+ ctrl->num_lanes = val;
+
memset(ctrl->page1_cache, 0xff, sizeof(ctrl->page1_cache));
memset(ctrl->page2_cache, 0xff, sizeof(ctrl->page2_cache));
@@ -1636,9 +1747,19 @@ static int qcom_swrm_probe(struct platform_device *pdev)
ctrl->bus.compute_params = &qcom_swrm_compute_params;
ctrl->bus.clk_stop_timeout = 300;
- ret = qcom_swrm_get_port_config(ctrl);
- if (ret)
- goto err_clk;
+ if (of_property_match_string(dev->of_node, "qcom,multi-master-mode",
+ "dependent") < 0) {
+ ret = qcom_swrm_get_port_config(ctrl);
+ if (ret)
+ goto err_clk;
+ }
+
+ if (of_property_match_string(dev->of_node, "qcom,multi-master-mode", "primary") >= 0) {
+ ret = qcom_swrm_multi_master_setup(ctrl);
+ if (ret)
+ goto err_clk;
+ ctrl->is_primary = true;
+ }
params = &ctrl->bus.params;
params->max_dr_freq = DEFAULT_CLK_FREQ;
@@ -1689,6 +1810,16 @@ static int qcom_swrm_probe(struct platform_device *pdev)
ctrl->bus.controller_id = val;
}
+ if (of_property_match_string(dev->of_node, "qcom,multi-master-mode", "dependent") >= 0) {
+ ctrl->is_dependent = true;
+ dev_dbg(dev,
+ "Qualcomm SoundWire multi-master dependent v%x.%x.%x registered (%u lanes)\n",
+ (ctrl->version >> 24) & 0xff,
+ (ctrl->version >> 16) & 0xff,
+ ctrl->version & 0xffff, ctrl->num_lanes);
+ return 0;
+ }
+
ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode);
if (ret) {
dev_err(dev, "Failed to register Soundwire controller (%d)\n",
@@ -1696,7 +1827,11 @@ static int qcom_swrm_probe(struct platform_device *pdev)
goto err_clk;
}
+ if (ctrl->is_primary && ctrl->peer_ctrl)
+ qcom_swrm_init(ctrl->peer_ctrl);
+
qcom_swrm_init(ctrl);
+
wait_for_completion_timeout(&ctrl->enumeration,
msecs_to_jiffies(TIMEOUT_MS));
ret = qcom_swrm_register_dais(ctrl);
@@ -1733,7 +1868,13 @@ static void qcom_swrm_remove(struct platform_device *pdev)
{
struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev);
- sdw_bus_master_delete(&ctrl->bus);
+ if (ctrl->is_primary && ctrl->peer_ctrl) {
+ ctrl->peer_ctrl->peer_ctrl = NULL;
+ ctrl->peer_ctrl = NULL;
+ }
+
+ if (!ctrl->is_dependent)
+ sdw_bus_master_delete(&ctrl->bus);
clk_disable_unprepare(ctrl->hclk);
}
--
2.53.0