diff mbox series

[v2,2/2] firmware: arm_scmi: round rate bisecting in discrete rates

Message ID 20241203173908.3148794-3-etienne.carriere@foss.st.com (mailing list archive)
State New
Headers show
Series firmware: arm_scmi: unbound discrete rates, support round rate | expand

Commit Message

Etienne Carriere Dec. 3, 2024, 5:39 p.m. UTC
Implement clock round_rate operation for SCMI clocks that describe a
discrete rates list. Bisect into the supported rates when using SCMI
message CLOCK_DESCRIBE_RATES to optimize SCMI communication transfers.
Parse the rate list array when the target rate fit in the bounds
of the command response for simplicity.

If so some reason the sequence fails or if the SCMI driver has no
round_rate SCMI clock handler, then fallback to the legacy strategy that
returned the target rate value.

Operation handle scmi_clk_determine_rate() is change to get the effective
supported rounded rate when there is no clock re-parenting operation
supported. Otherwise, preserve the implementation that assumed any
clock rate could be obtained.

Signed-off-by: Etienne Carriere <etienne.carriere@foss.st.com>
---
Changes since patch series v1:
- New patch introduced in this v2 series.

---
 drivers/clk/clk-scmi.c            | 17 +++++-
 drivers/firmware/arm_scmi/clock.c | 93 +++++++++++++++++++++++++++++++
 include/linux/scmi_protocol.h     |  3 +
 3 files changed, 110 insertions(+), 3 deletions(-)
diff mbox series

Patch

diff --git a/drivers/clk/clk-scmi.c b/drivers/clk/clk-scmi.c
index 09ccd6cea7f2..7bbb2ee55f4f 100644
--- a/drivers/clk/clk-scmi.c
+++ b/drivers/clk/clk-scmi.c
@@ -61,13 +61,20 @@  static long scmi_clk_round_rate(struct clk_hw *hw, unsigned long rate,
 	struct scmi_clk *clk = to_scmi_clk(hw);
 
 	/*
-	 * We can't figure out what rate it will be, so just return the
+	 * In case we can't figure out what rate it will be when the clock
+	 * describes a list of discrete rates, then just return the
 	 * rate back to the caller. scmi_clk_recalc_rate() will be called
 	 * after the rate is set and we'll know what rate the clock is
 	 * running at then.
 	 */
-	if (clk->info->rate_discrete)
+	if (clk->info->rate_discrete) {
+		ftmp = rate;
+		if (scmi_proto_clk_ops->round_rate &&
+		    !scmi_proto_clk_ops->round_rate(clk->ph, clk->id, &ftmp))
+			return ftmp;
+
 		return rate;
+	}
 
 	fmin = clk->info->range.min_rate;
 	fmax = clk->info->range.max_rate;
@@ -122,9 +129,13 @@  static u8 scmi_clk_get_parent(struct clk_hw *hw)
 static int scmi_clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
 {
 	/*
-	 * Suppose all the requested rates are supported, and let firmware
+	 * If not several parents look into supported rates. Otherwise
+	 * suppose all the requested rates are supported, and let firmware
 	 * to handle the left work.
 	 */
+	if (to_scmi_clk(hw)->info->num_parents < 2)
+		req->rate = scmi_clk_round_rate(hw, req->rate, NULL);
+
 	return 0;
 }
 
diff --git a/drivers/firmware/arm_scmi/clock.c b/drivers/firmware/arm_scmi/clock.c
index 34fde0b88098..e416476dd336 100644
--- a/drivers/firmware/arm_scmi/clock.c
+++ b/drivers/firmware/arm_scmi/clock.c
@@ -999,6 +999,98 @@  static int scmi_clock_config_oem_get(const struct scmi_protocol_handle *ph,
 				    NULL, oem_val, atomic);
 }
 
+static int scmi_clock_round_rate(const struct scmi_protocol_handle *ph,
+				 u32 clk_id, u64 *rate)
+
+{
+	const struct scmi_msg_resp_clock_describe_rates *resp;
+	size_t index_low, index_high, index_tmp, count, i;
+	struct scmi_msg_clock_describe_rates *msg;
+	u64 rate_low, rate_high, target_rate;
+	struct scmi_xfer *xfer;
+	int ret;
+	struct clock_info *ci = ph->get_priv(ph);
+	struct scmi_clock_info *clk = ci->clk + clk_id;
+
+	if (clk_id >= ci->num_clocks ||
+	    WARN_ONCE(!clk->rate_discrete, "Unexpected linear rates"))
+		return -EINVAL;
+
+	target_rate = *rate;
+	index_low = 0;
+	index_high = clk->list.num_rates - 1;
+	rate_low = clk->list.min_rate;
+	rate_high = clk->list.max_rate;
+
+	if (target_rate <= rate_low) {
+		*rate = rate_low;
+		return 0;
+	}
+	if (target_rate >= rate_high) {
+		*rate = rate_high;
+		return 0;
+	}
+
+	ret = ph->xops->xfer_get_init(ph, CLOCK_DESCRIBE_RATES, sizeof(*msg), 0,
+				      &xfer);
+	if (ret)
+		return ret;
+
+	resp = xfer->rx.buf;
+	msg = xfer->tx.buf;
+	msg->id = cpu_to_le32(clk_id);
+
+	while (true) {
+		index_tmp = (index_low + index_high) / 2;
+
+		ph->xops->reset_rx_to_maxsz(ph, xfer);
+		msg->id = cpu_to_le32(clk_id);
+		msg->rate_index = cpu_to_le32(index_tmp);
+
+		ret = ph->xops->do_xfer(ph, xfer);
+		if (!ret && (!RATE_DISCRETE(resp->num_rates_flags) ||
+			     !NUM_RETURNED(resp->num_rates_flags)))
+			ret = -EPROTO;
+		if (ret)
+			break;
+
+		count = NUM_RETURNED(resp->num_rates_flags);
+
+		if (target_rate < RATE_TO_U64(resp->rate[0])) {
+			index_high = index_tmp;
+			rate_high = RATE_TO_U64(resp->rate[0]);
+		} else if (target_rate > RATE_TO_U64(resp->rate[count - 1])) {
+			index_low = index_tmp + count - 1;
+			rate_low = RATE_TO_U64(resp->rate[count - 1]);
+		} else {
+			for (i = 1; i < count; i++)
+				if (target_rate <= RATE_TO_U64(resp->rate[i]))
+					break;
+
+			index_low = index_tmp + i - 1;
+			rate_low = RATE_TO_U64(resp->rate[i - 1]);
+
+			if (i < count) {
+				index_high = index_tmp + i;
+				rate_high = RATE_TO_U64(resp->rate[i]);
+			}
+		}
+
+		if (index_high <= index_low + 1) {
+			if (target_rate - rate_low > rate_high - target_rate)
+				*rate = rate_high;
+			else
+				*rate = rate_low;
+
+			break;
+		}
+	}
+
+	ph->xops->xfer_put(ph, xfer);
+
+	return ret;
+}
+
 static int scmi_clock_count_get(const struct scmi_protocol_handle *ph)
 {
 	struct clock_info *ci = ph->get_priv(ph);
@@ -1027,6 +1119,7 @@  static const struct scmi_clk_proto_ops clk_proto_ops = {
 	.info_get = scmi_clock_info_get,
 	.rate_get = scmi_clock_rate_get,
 	.rate_set = scmi_clock_rate_set,
+	.round_rate = scmi_clock_round_rate,
 	.enable = scmi_clock_enable,
 	.disable = scmi_clock_disable,
 	.state_get = scmi_clock_state_get,
diff --git a/include/linux/scmi_protocol.h b/include/linux/scmi_protocol.h
index 240478bb8476..30cf373c3f8b 100644
--- a/include/linux/scmi_protocol.h
+++ b/include/linux/scmi_protocol.h
@@ -91,6 +91,7 @@  enum scmi_clock_oem_config {
  * @info_get: get the information of the specified clock
  * @rate_get: request the current clock rate of a clock
  * @rate_set: set the clock rate of a clock
+ * @round_rate: tell which is the nearest rate a clock supports (w/o setting it)
  * @enable: enables the specified clock
  * @disable: disables the specified clock
  * @state_get: get the status of the specified clock
@@ -108,6 +109,8 @@  struct scmi_clk_proto_ops {
 			u64 *rate);
 	int (*rate_set)(const struct scmi_protocol_handle *ph, u32 clk_id,
 			u64 rate);
+	int (*round_rate)(const struct scmi_protocol_handle *ph, u32 clk_id,
+			  u64 *rate);
 	int (*enable)(const struct scmi_protocol_handle *ph, u32 clk_id,
 		      bool atomic);
 	int (*disable)(const struct scmi_protocol_handle *ph, u32 clk_id,