From 11a44c6087c64087c99d5d90313473eceb51296c Mon Sep 17 00:00:00 2001 From: Neil Armstrong Date: Wed, 9 Sep 2026 17:26:29 +0200 Subject: [PATCH] interconnect: qcom: implement get_bw with rpmh_read 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 #db845c Signed-off-by: Neil Armstrong Reviewed-by: Abel Vesa Link: https://patch.msgid.link/20260909-topic-sm8x50-icc-read-rpmh-v3-1-2bc65d7b25e5@linaro.org Signed-off-by: Georgi Djakov --- 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];