drivers/interconnect/qcom/bcm-voter.c | 34 ++++++++++++++++++++ drivers/interconnect/qcom/bcm-voter.h | 1 + drivers/interconnect/qcom/icc-rpmh.c | 60 ++++++++++++++++++++++++++++++++++- 3 files changed, 94 insertions(+), 1 deletion(-)
Since we can actually read back the APPS rpmh interconnect
BCM votes we can actually implement the get_bw() callback
and provide a coherent average and peak bandwidth at probe time.
The benefits of that are:
- keep disabled BCMs disabled
- avoid voting unused BCMs to INT_MAX
If the interconnects are correctly described for a platform,
all the required BCMs would be voted to the maximum bandwidth
until sync_state is reached.
Since we only get the BCM vote, we need to redistribute
the vote values to the associated nodes. The initial BCM
votes are read back at probe time in order to be ready when
the get_bw() is called when a node is added.
Tested-by: Georgi Djakov <djakov@kernel.org> #db845c
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
---
Changes in v3:
- Dropped unneeded rpmh_invalidate()
- Link to v2: https://patch.msgid.link/20260908-topic-sm8x50-icc-read-rpmh-v2-1-b6f1c4205450@linaro.org
Changes in v2:
- Added tested-by
- Rebased on v7.3-rc1 now the rpmh_read() is merged
- Link to v1: https://patch.msgid.link/20251106-topic-sm8x50-icc-read-rpmh-v1-1-d03a2e5ca5f7@linaro.org
---
drivers/interconnect/qcom/bcm-voter.c | 34 ++++++++++++++++++++
drivers/interconnect/qcom/bcm-voter.h | 1 +
drivers/interconnect/qcom/icc-rpmh.c | 60 ++++++++++++++++++++++++++++++++++-
3 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/drivers/interconnect/qcom/bcm-voter.c b/drivers/interconnect/qcom/bcm-voter.c
index a2d437a05a11..c15abb57cd24 100644
--- a/drivers/interconnect/qcom/bcm-voter.c
+++ b/drivers/interconnect/qcom/bcm-voter.c
@@ -261,6 +261,40 @@ void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm)
}
EXPORT_SYMBOL_GPL(qcom_icc_bcm_voter_add);
+/**
+ * qcom_icc_bcm_get_bw - get current bcm vote
+ * @voter: voter used to query bcm
+ * @bcm: bcm to get current vote from
+ */
+void qcom_icc_bcm_get_bw(struct bcm_voter *voter,
+ struct qcom_icc_bcm *bcm)
+{
+ struct tcs_cmd cmd = { .addr = bcm->addr };
+ int ret, i;
+ u64 x, y;
+
+ mutex_lock(&voter->lock);
+
+ ret = rpmh_read(voter->dev, &cmd);
+ if (ret) {
+ pr_err("Error sending AMC RPMH requests (%d)\n", ret);
+ goto out;
+ }
+
+ x = FIELD_GET(BCM_TCS_CMD_VOTE_X_MASK, cmd.data);
+ y = FIELD_GET(BCM_TCS_CMD_VOTE_Y_MASK, cmd.data);
+
+ /* For boot-up, fill the AMC vote in all buckets */
+ for (i = 0; i < QCOM_ICC_NUM_BUCKETS; i++) {
+ bcm->vote_x[i] = x;
+ bcm->vote_y[i] = y;
+ }
+
+out:
+ mutex_unlock(&voter->lock);
+}
+EXPORT_SYMBOL_GPL(qcom_icc_bcm_get_bw);
+
/**
* qcom_icc_bcm_voter_commit - generates and commits tcs cmds based on bcms
* @voter: voter that needs flushing
diff --git a/drivers/interconnect/qcom/bcm-voter.h b/drivers/interconnect/qcom/bcm-voter.h
index b4d36e349f3c..fc75d457dcc7 100644
--- a/drivers/interconnect/qcom/bcm-voter.h
+++ b/drivers/interconnect/qcom/bcm-voter.h
@@ -13,6 +13,7 @@
#include "icc-rpmh.h"
struct bcm_voter *of_bcm_voter_get(struct device *dev, const char *name);
+void qcom_icc_bcm_get_bw(struct bcm_voter *voter, struct qcom_icc_bcm *bcm);
void qcom_icc_bcm_voter_add(struct bcm_voter *voter, struct qcom_icc_bcm *bcm);
int qcom_icc_bcm_voter_commit(struct bcm_voter *voter);
diff --git a/drivers/interconnect/qcom/icc-rpmh.c b/drivers/interconnect/qcom/icc-rpmh.c
index 3b445acefece..7f2b5673629b 100644
--- a/drivers/interconnect/qcom/icc-rpmh.c
+++ b/drivers/interconnect/qcom/icc-rpmh.c
@@ -136,6 +136,61 @@ int qcom_icc_set(struct icc_node *src, struct icc_node *dst)
}
EXPORT_SYMBOL_GPL(qcom_icc_set);
+static int qcom_icc_get_bw(struct icc_node *node, u32 *avg, u32 *peak)
+{
+ struct qcom_icc_node *qn = node->data;
+ u32 avg_max = 0;
+ u32 peak_max = 0;
+ u64 x, y;
+ int i;
+
+ if (!qn->num_bcms) {
+ *avg = INT_MAX;
+ *peak = INT_MAX;
+
+ return 0;
+ }
+
+ for (i = 0; i < qn->num_bcms; ++i) {
+ struct qcom_icc_bcm *bcm = qn->bcms[i];
+
+ /* Use AMC vote for boot-up */
+ x = bcm->vote_x[QCOM_ICC_BUCKET_AMC];
+ y = bcm->vote_y[QCOM_ICC_BUCKET_AMC];
+
+ /* Consider enable mask and convert to INT_MAX */
+ if (bcm->enable_mask) {
+ if (x & bcm->enable_mask)
+ avg_max = INT_MAX;
+ if (y & bcm->enable_mask)
+ peak_max = INT_MAX;
+ } else {
+ if (x) {
+ x *= bcm->aux_data.unit;
+ do_div(x, bcm->vote_scale);
+ x *= qn->buswidth * qn->channels;
+ do_div(x, bcm->aux_data.width);
+
+ avg_max = max(avg_max, x);
+ }
+
+ if (y) {
+ y *= bcm->aux_data.unit;
+ do_div(y, bcm->vote_scale);
+ y *= qn->buswidth;
+ do_div(y, bcm->aux_data.width);
+
+ peak_max = max(peak_max, y);
+ }
+ }
+ }
+
+ *avg = avg_max;
+ *peak = peak_max;
+
+ return 0;
+}
+
/**
* qcom_icc_bcm_init - populates bcm aux data and connect qnodes
* @bcm: bcm to be initialized
@@ -255,6 +310,7 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev)
provider = &qp->provider;
provider->dev = dev;
provider->set = qcom_icc_set;
+ provider->get_bw = qcom_icc_get_bw;
provider->pre_aggregate = qcom_icc_pre_aggregate;
provider->aggregate = qcom_icc_aggregate;
provider->xlate_extended = qcom_icc_xlate_extended;
@@ -272,8 +328,10 @@ int qcom_icc_rpmh_probe(struct platform_device *pdev)
if (IS_ERR(qp->voter))
return PTR_ERR(qp->voter);
- for (i = 0; i < qp->num_bcms; i++)
+ for (i = 0; i < qp->num_bcms; i++) {
qcom_icc_bcm_init(qp->bcms[i], dev);
+ qcom_icc_bcm_get_bw(qp->voter, qp->bcms[i]);
+ }
for (i = 0; i < num_nodes; i++) {
qn = qnodes[i];
---
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
change-id: 20251106-topic-sm8x50-icc-read-rpmh-eba461a452e7
Best regards,
--
Neil Armstrong <neil.armstrong@linaro.org>
On Wed, Sep 09, 2026 at 05:26:29PM +0200, Neil Armstrong wrote: > Since we can actually read back the APPS rpmh interconnect > BCM votes we can actually implement the get_bw() callback > and provide a coherent average and peak bandwidth at probe time. > > The benefits of that are: > - keep disabled BCMs disabled > - avoid voting unused BCMs to INT_MAX > > If the interconnects are correctly described for a platform, > all the required BCMs would be voted to the maximum bandwidth > until sync_state is reached. > > Since we only get the BCM vote, we need to redistribute > the vote values to the associated nodes. The initial BCM > votes are read back at probe time in order to be ready when > the get_bw() is called when a node is added. > > Tested-by: Georgi Djakov <djakov@kernel.org> #db845c > Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org> This causes a linux-next regression on Nord platforms. I guess it's more than just Nord. [ 1.729012] Internal error: synchronous external abort: 0000000096001610 [#1] SMP [ 1.729014] Modules linked in: [ 1.729019] CPU: 13 UID: 0 PID: 156 Comm: kworker/u75:1 Tainted: G M 7.3.0-rc4-next-20260921-00010-gb1438d2a2208 #248 PREEMPT(full) [ 1.729196] vreg_l8a_1p8: Setting 1800000-1800000uV [ 1.729375] vreg_s1a_vdd2h_l: Setting 904000-1096000uV [ 1.729553] vreg_s3a_1p8: Setting 1800000-1800000uV [ 1.729733] vreg_s5a_mv: Setting 1328000-1368000uV [ 1.729913] vreg_s6a_vddq_l: Setting 504000-568000uV [ 1.734224] vreg_l3f_vdd1: Setting 1800000-1800000uV [ 1.739052] Tainted: [M]=MACHINE_CHECK [ 1.739052] Hardware name: Qualcomm Technologies, Inc. SA8797P Ride Embedded (DT) [ 1.739053] Workqueue: events_unbound deferred_probe_work_func [ 1.744610] vreg_s7f_lv_sub: Setting 1040000-1136000uV [ 1.749261] [ 1.749262] pstate: 214000c5 (nzCv daIF +PAN -UAO -TCO +DIT -SSBS BTYPE=--) [ 1.749263] pc : regmap_mmio_read32le+0xc/0xa4 [ 1.754473] vreg_s8f_vddq_h: Setting 504000-568000uV [ 1.759295] lr : regmap_mmio_read+0x44/0x70 [ 1.759297] sp : ffff80008153b9f0 [ 1.759298] x29: ffff80008153b9f0 x28: 0000000000000001 x27: ffffd5167fb5c7f0 [ 1.875707] x26: ffffd5167fb5c678 x25: 0000000000000000 x24: 0000000000000000 [ 1.883040] x23: 0000000001000000 x22: ffff000890e5a800 x21: ffff80008153ba84 [ 1.890372] x20: 0000000000057008 x19: ffff00088deff380 x18: 0000000000000000 [ 1.897705] x17: 000000000000000c x16: 0000000000000000 x15: 0000000000000018 [ 1.905037] x14: 0000000000000000 x13: 7463656e6e6f6372 x12: 65746e692e303030 [ 1.912369] x11: ffff8000828f4000 x10: 000000000006c9e8 x9 : 0000000000000004 [ 1.919702] x8 : 0101010101010101 x7 : 0000000000000000 x6 : 0000000000000000 [ 1.927034] x5 : ffffd5167f1a0678 x4 : 0000000000057008 x3 : 0000000000057008 [ 1.934366] x2 : ffffd5167f1a0e4c x1 : 0000000000057008 x0 : ffff8000828d7008 [ 1.941699] Call trace: [ 1.944220] regmap_mmio_read32le+0xc/0xa4 (P) [ 1.948789] _regmap_bus_reg_read+0x70/0xb0 [ 1.953097] _regmap_read+0x60/0xd8 [ 1.956683] _regmap_update_bits+0xfc/0x14c [ 1.960990] regmap_update_bits_base+0x64/0x98 [ 1.965558] qcom_icc_rpmh_probe+0x44c/0x540 [ 1.969954] platform_probe+0x5c/0x9c [ 1.973725] really_probe+0xbc/0x29c [ 1.977409] __driver_probe_device+0x16c/0x19c [ 1.981977] driver_probe_device+0x3c/0x114 [ 1.986285] __device_attach_driver+0xb8/0x118 [ 1.990853] bus_for_each_drv+0x88/0xe8 [ 1.994796] __device_attach+0xa0/0x190 [ 1.998739] device_initial_probe+0x50/0x54 [ 2.003046] bus_probe_device+0x38/0xa0 [ 2.006989] deferred_probe_work_func+0x88/0xc0 [ 2.011653] process_one_work+0x180/0x2dc [ 2.015784] worker_thread+0x184/0x2fc [ 2.019639] kthread+0x118/0x124 [ 2.022959] ret_from_fork+0x10/0x20 [ 2.026647] Code: d65f03c0 f9400000 8b214000 d503201f (b9400000) [ 2.032913] ---[ end trace 0000000000000000 ]--- I sent a fixup as: https://lore.kernel.org/all/20260922020623.398913-1-shengchao.guo@oss.qualcomm.com/ Shawn
On 26-09-09 17:26:29, Neil Armstrong wrote: > Since we can actually read back the APPS rpmh interconnect > BCM votes we can actually implement the get_bw() callback > and provide a coherent average and peak bandwidth at probe time. > > The benefits of that are: > - keep disabled BCMs disabled > - avoid voting unused BCMs to INT_MAX > > If the interconnects are correctly described for a platform, > all the required BCMs would be voted to the maximum bandwidth > until sync_state is reached. > > Since we only get the BCM vote, we need to redistribute > the vote values to the associated nodes. The initial BCM > votes are read back at probe time in order to be ready when > the get_bw() is called when a node is added. > > Tested-by: Georgi Djakov <djakov@kernel.org> #db845c > Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org> Reviewed-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
© 2016 - 2026 Red Hat, Inc.