[PATCH 1/4] interconnect: icc-clk: support N:1 master-to-slave topology
From: Kathiravan Thirumoorthy
Date: Wed Oct 07 2026 - 02:51:32 EST
icc_clk_register() assumes each icc_clk_data entry describes a unique
master/slave pair and creates both nodes for every entry. When several
masters share the same slave_id, the shared slave node is created and
added again on each later entry. icc_node_create() returns the existing
node, so icc_node_add() hits WARN_ON(node->provider):
WARNING: drivers/interconnect/core.c:1103 at icc_node_add+0x10c/0x120
Call trace:
icc_node_add+0x10c/0x120 (P)
icc_clk_register+0x1a4/0x214
devm_icc_clk_register+0x18/0x80
qcom_cc_really_probe+0x4c8/0x514
apss_ipq9650_probe+0xc0/0x108
This topology is needed on IPQ9650, where the silver and gold CPU
clusters both vote on the single L3 clock through a shared SLAVE_L3
node.
The onecell table is also filled in registration order, so xlate only
works when the node IDs happen to match that order. Size the table by
the highest node ID, index it by ID, and skip slave creation when the
node is already registered.
icc_clk_set() also passes only the requesting node's bandwidth to
clk_set_rate(), so the last writer wins. A low vote from one master can
then lower the clock below what another master asked for. Before setting
the rate, take the highest peak bandwidth across all nodes that share
the clock.
Signed-off-by: Kathiravan Thirumoorthy <kathiravan.thirumoorthy@xxxxxxxxxxxxxxxx>
---
drivers/interconnect/icc-clk.c | 45 +++++++++++++++++++++++++++++++++++-------
1 file changed, 38 insertions(+), 7 deletions(-)
diff --git a/drivers/interconnect/icc-clk.c b/drivers/interconnect/icc-clk.c
index 93c030608d3e..567d071fade8 100644
--- a/drivers/interconnect/icc-clk.c
+++ b/drivers/interconnect/icc-clk.c
@@ -24,7 +24,10 @@ struct icc_clk_provider {
static int icc_clk_set(struct icc_node *src, struct icc_node *dst)
{
+ unsigned long rate = icc_units_to_bps(src->peak_bw);
+ struct icc_provider *provider = src->provider;
struct icc_clk_node *qn = src->data;
+ struct icc_node *node;
int ret;
if (!qn || !qn->clk)
@@ -45,7 +48,21 @@ static int icc_clk_set(struct icc_node *src, struct icc_node *dst)
qn->enabled = true;
}
- return clk_set_rate(qn->clk, icc_units_to_bps(src->peak_bw));
+ /*
+ * Multiple master nodes can share the same underlying clock (N:1
+ * topology, e.g. several CPU clusters voting on one L3 clock).
+ * Aggregate the peak bandwidth across all of them before setting
+ * the rate, otherwise the last caller wins and can undervote what
+ * another master already asked for.
+ */
+ list_for_each_entry(node, &provider->nodes, node_list) {
+ struct icc_clk_node *n = node->data;
+
+ if (n && clk_is_match(n->clk, qn->clk))
+ rate = max(rate, icc_units_to_bps(node->peak_bw));
+ }
+
+ return clk_set_rate(qn->clk, rate);
}
static int icc_clk_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
@@ -82,12 +99,17 @@ struct icc_provider *icc_clk_register(struct device *dev,
struct icc_provider *provider;
struct icc_onecell_data *onecell;
struct icc_node *node;
- int ret, i, j;
+ unsigned int max_id = 0;
+ int ret, i;
- onecell = devm_kzalloc(dev, struct_size(onecell, nodes, 2 * num_clocks), GFP_KERNEL);
+ /* Find the highest node ID to size the xlate lookup table */
+ for (i = 0; i < num_clocks; i++)
+ max_id = max(max_id, max(data[i].master_id, data[i].slave_id));
+
+ onecell = devm_kzalloc(dev, struct_size(onecell, nodes, max_id + 1), GFP_KERNEL);
if (!onecell)
return ERR_PTR(-ENOMEM);
- onecell->num_nodes = 2 * num_clocks;
+ onecell->num_nodes = max_id + 1;
qp = devm_kzalloc(dev, struct_size(qp, clocks, num_clocks), GFP_KERNEL);
if (!qp)
@@ -106,7 +128,7 @@ struct icc_provider *icc_clk_register(struct device *dev,
icc_provider_init(provider);
- for (i = 0, j = 0; i < num_clocks; i++) {
+ for (i = 0; i < num_clocks; i++) {
qp->clocks[i].clk = data[i].clk;
node = icc_node_create(first_id + data[i].master_id);
@@ -126,7 +148,16 @@ struct icc_provider *icc_clk_register(struct device *dev,
icc_node_add(node, provider);
/* link to the next node, slave */
icc_link_create(node, first_id + data[i].slave_id);
- onecell->nodes[j++] = node;
+ onecell->nodes[data[i].master_id] = node;
+
+ /*
+ * Slave node may already be registered by an earlier
+ * master in an N:1 topology (multiple masters sharing
+ * the same slave, e.g. several CPU clusters voting on
+ * one L3 clock).
+ */
+ if (onecell->nodes[data[i].slave_id])
+ continue;
node = icc_node_create(first_id + data[i].slave_id);
if (IS_ERR(node)) {
@@ -143,7 +174,7 @@ struct icc_provider *icc_clk_register(struct device *dev,
/* no data for slave node */
icc_node_add(node, provider);
- onecell->nodes[j++] = node;
+ onecell->nodes[data[i].slave_id] = node;
}
ret = icc_provider_register(provider);
--
2.34.1