@@ -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;
}
@@ -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,
@@ -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,
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(-)