Merge tag 'clk-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/clk/linux
[linux-2.6-block.git] / drivers / clk / clk-scmi.c
index 8cbe24789c24bbb1db6213501fc2a21f45a49f40..d86a02563f6c8c329a820748dd0966ba2bc0992e 100644 (file)
@@ -2,9 +2,10 @@
 /*
  * System Control and Power Interface (SCMI) Protocol based clock driver
  *
- * Copyright (C) 2018-2022 ARM Ltd.
+ * Copyright (C) 2018-2024 ARM Ltd.
  */
 
+#include <linux/bits.h>
 #include <linux/clk-provider.h>
 #include <linux/device.h>
 #include <linux/err.h>
 #define NOT_ATOMIC     false
 #define ATOMIC         true
 
+enum scmi_clk_feats {
+       SCMI_CLK_ATOMIC_SUPPORTED,
+       SCMI_CLK_STATE_CTRL_SUPPORTED,
+       SCMI_CLK_RATE_CTRL_SUPPORTED,
+       SCMI_CLK_PARENT_CTRL_SUPPORTED,
+       SCMI_CLK_DUTY_CYCLE_SUPPORTED,
+       SCMI_CLK_FEATS_COUNT
+};
+
+#define SCMI_MAX_CLK_OPS       BIT(SCMI_CLK_FEATS_COUNT)
+
 static const struct scmi_clk_proto_ops *scmi_proto_clk_ops;
 
 struct scmi_clk {
@@ -158,41 +170,44 @@ static int scmi_clk_atomic_is_enabled(struct clk_hw *hw)
        return !!enabled;
 }
 
-/*
- * We can provide enable/disable/is_enabled atomic callbacks only if the
- * underlying SCMI transport for an SCMI instance is configured to handle
- * SCMI commands in an atomic manner.
- *
- * When no SCMI atomic transport support is available we instead provide only
- * the prepare/unprepare API, as allowed by the clock framework when atomic
- * calls are not available.
- *
- * Two distinct sets of clk_ops are provided since we could have multiple SCMI
- * instances with different underlying transport quality, so they cannot be
- * shared.
- */
-static const struct clk_ops scmi_clk_ops = {
-       .recalc_rate = scmi_clk_recalc_rate,
-       .round_rate = scmi_clk_round_rate,
-       .set_rate = scmi_clk_set_rate,
-       .prepare = scmi_clk_enable,
-       .unprepare = scmi_clk_disable,
-       .set_parent = scmi_clk_set_parent,
-       .get_parent = scmi_clk_get_parent,
-       .determine_rate = scmi_clk_determine_rate,
-};
+static int scmi_clk_get_duty_cycle(struct clk_hw *hw, struct clk_duty *duty)
+{
+       int ret;
+       u32 val;
+       struct scmi_clk *clk = to_scmi_clk(hw);
 
-static const struct clk_ops scmi_atomic_clk_ops = {
-       .recalc_rate = scmi_clk_recalc_rate,
-       .round_rate = scmi_clk_round_rate,
-       .set_rate = scmi_clk_set_rate,
-       .enable = scmi_clk_atomic_enable,
-       .disable = scmi_clk_atomic_disable,
-       .is_enabled = scmi_clk_atomic_is_enabled,
-       .set_parent = scmi_clk_set_parent,
-       .get_parent = scmi_clk_get_parent,
-       .determine_rate = scmi_clk_determine_rate,
-};
+       ret = scmi_proto_clk_ops->config_oem_get(clk->ph, clk->id,
+                                                SCMI_CLOCK_CFG_DUTY_CYCLE,
+                                                &val, NULL, false);
+       if (!ret) {
+               duty->num = val;
+               duty->den = 100;
+       } else {
+               dev_warn(clk->dev,
+                        "Failed to get duty cycle for clock ID %d\n", clk->id);
+       }
+
+       return ret;
+}
+
+static int scmi_clk_set_duty_cycle(struct clk_hw *hw, struct clk_duty *duty)
+{
+       int ret;
+       u32 val;
+       struct scmi_clk *clk = to_scmi_clk(hw);
+
+       /* SCMI OEM Duty Cycle is expressed as a percentage */
+       val = (duty->num * 100) / duty->den;
+       ret = scmi_proto_clk_ops->config_oem_set(clk->ph, clk->id,
+                                                SCMI_CLOCK_CFG_DUTY_CYCLE,
+                                                val, false);
+       if (ret)
+               dev_warn(clk->dev,
+                        "Failed to set duty cycle(%u/%u) for clock ID %d\n",
+                        duty->num, duty->den, clk->id);
+
+       return ret;
+}
 
 static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
                             const struct clk_ops *scmi_ops)
@@ -230,17 +245,153 @@ static int scmi_clk_ops_init(struct device *dev, struct scmi_clk *sclk,
        return ret;
 }
 
+/**
+ * scmi_clk_ops_alloc() - Alloc and configure clock operations
+ * @dev: A device reference for devres
+ * @feats_key: A bitmap representing the desired clk_ops capabilities
+ *
+ * Allocate and configure a proper set of clock operations depending on the
+ * specifically required SCMI clock features.
+ *
+ * Return: A pointer to the allocated and configured clk_ops on success,
+ *        or NULL on allocation failure.
+ */
+static const struct clk_ops *
+scmi_clk_ops_alloc(struct device *dev, unsigned long feats_key)
+{
+       struct clk_ops *ops;
+
+       ops = devm_kzalloc(dev, sizeof(*ops), GFP_KERNEL);
+       if (!ops)
+               return NULL;
+       /*
+        * We can provide enable/disable/is_enabled atomic callbacks only if the
+        * underlying SCMI transport for an SCMI instance is configured to
+        * handle SCMI commands in an atomic manner.
+        *
+        * When no SCMI atomic transport support is available we instead provide
+        * only the prepare/unprepare API, as allowed by the clock framework
+        * when atomic calls are not available.
+        */
+       if (feats_key & BIT(SCMI_CLK_STATE_CTRL_SUPPORTED)) {
+               if (feats_key & BIT(SCMI_CLK_ATOMIC_SUPPORTED)) {
+                       ops->enable = scmi_clk_atomic_enable;
+                       ops->disable = scmi_clk_atomic_disable;
+               } else {
+                       ops->prepare = scmi_clk_enable;
+                       ops->unprepare = scmi_clk_disable;
+               }
+       }
+
+       if (feats_key & BIT(SCMI_CLK_ATOMIC_SUPPORTED))
+               ops->is_enabled = scmi_clk_atomic_is_enabled;
+
+       /* Rate ops */
+       ops->recalc_rate = scmi_clk_recalc_rate;
+       ops->round_rate = scmi_clk_round_rate;
+       ops->determine_rate = scmi_clk_determine_rate;
+       if (feats_key & BIT(SCMI_CLK_RATE_CTRL_SUPPORTED))
+               ops->set_rate = scmi_clk_set_rate;
+
+       /* Parent ops */
+       ops->get_parent = scmi_clk_get_parent;
+       if (feats_key & BIT(SCMI_CLK_PARENT_CTRL_SUPPORTED))
+               ops->set_parent = scmi_clk_set_parent;
+
+       /* Duty cycle */
+       if (feats_key & BIT(SCMI_CLK_DUTY_CYCLE_SUPPORTED)) {
+               ops->get_duty_cycle = scmi_clk_get_duty_cycle;
+               ops->set_duty_cycle = scmi_clk_set_duty_cycle;
+       }
+
+       return ops;
+}
+
+/**
+ * scmi_clk_ops_select() - Select a proper set of clock operations
+ * @sclk: A reference to an SCMI clock descriptor
+ * @atomic_capable: A flag to indicate if atomic mode is supported by the
+ *                 transport
+ * @atomic_threshold_us: Platform atomic threshold value in microseconds:
+ *                      clk_ops are atomic when clock enable latency is less
+ *                      than this threshold
+ * @clk_ops_db: A reference to the array used as a database to store all the
+ *             created clock operations combinations.
+ * @db_size: Maximum number of entries held by @clk_ops_db
+ *
+ * After having built a bitmap descriptor to represent the set of features
+ * needed by this SCMI clock, at first use it to lookup into the set of
+ * previously allocated clk_ops to check if a suitable combination of clock
+ * operations was already created; when no match is found allocate a brand new
+ * set of clk_ops satisfying the required combination of features and save it
+ * for future references.
+ *
+ * In this way only one set of clk_ops is ever created for each different
+ * combination that is effectively needed by a driver instance.
+ *
+ * Return: A pointer to the allocated and configured clk_ops on success, or
+ *        NULL otherwise.
+ */
+static const struct clk_ops *
+scmi_clk_ops_select(struct scmi_clk *sclk, bool atomic_capable,
+                   unsigned int atomic_threshold_us,
+                   const struct clk_ops **clk_ops_db, size_t db_size)
+{
+       const struct scmi_clock_info *ci = sclk->info;
+       unsigned int feats_key = 0;
+       const struct clk_ops *ops;
+
+       /*
+        * Note that when transport is atomic but SCMI protocol did not
+        * specify (or support) an enable_latency associated with a
+        * clock, we default to use atomic operations mode.
+        */
+       if (atomic_capable && ci->enable_latency <= atomic_threshold_us)
+               feats_key |= BIT(SCMI_CLK_ATOMIC_SUPPORTED);
+
+       if (!ci->state_ctrl_forbidden)
+               feats_key |= BIT(SCMI_CLK_STATE_CTRL_SUPPORTED);
+
+       if (!ci->rate_ctrl_forbidden)
+               feats_key |= BIT(SCMI_CLK_RATE_CTRL_SUPPORTED);
+
+       if (!ci->parent_ctrl_forbidden)
+               feats_key |= BIT(SCMI_CLK_PARENT_CTRL_SUPPORTED);
+
+       if (ci->extended_config)
+               feats_key |= BIT(SCMI_CLK_DUTY_CYCLE_SUPPORTED);
+
+       if (WARN_ON(feats_key >= db_size))
+               return NULL;
+
+       /* Lookup previously allocated ops */
+       ops = clk_ops_db[feats_key];
+       if (ops)
+               return ops;
+
+       /* Did not find a pre-allocated clock_ops */
+       ops = scmi_clk_ops_alloc(sclk->dev, feats_key);
+       if (!ops)
+               return NULL;
+
+       /* Store new ops combinations */
+       clk_ops_db[feats_key] = ops;
+
+       return ops;
+}
+
 static int scmi_clocks_probe(struct scmi_device *sdev)
 {
        int idx, count, err;
-       unsigned int atomic_threshold;
-       bool is_atomic;
+       unsigned int atomic_threshold_us;
+       bool transport_is_atomic;
        struct clk_hw **hws;
        struct clk_hw_onecell_data *clk_data;
        struct device *dev = &sdev->dev;
        struct device_node *np = dev->of_node;
        const struct scmi_handle *handle = sdev->handle;
        struct scmi_protocol_handle *ph;
+       const struct clk_ops *scmi_clk_ops_db[SCMI_MAX_CLK_OPS] = {};
 
        if (!handle)
                return -ENODEV;
@@ -264,7 +415,8 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
        clk_data->num = count;
        hws = clk_data->hws;
 
-       is_atomic = handle->is_transport_atomic(handle, &atomic_threshold);
+       transport_is_atomic = handle->is_transport_atomic(handle,
+                                                         &atomic_threshold_us);
 
        for (idx = 0; idx < count; idx++) {
                struct scmi_clk *sclk;
@@ -286,15 +438,17 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
                sclk->dev = dev;
 
                /*
-                * Note that when transport is atomic but SCMI protocol did not
-                * specify (or support) an enable_latency associated with a
-                * clock, we default to use atomic operations mode.
+                * Note that the scmi_clk_ops_db is on the stack, not global,
+                * because it cannot be shared between mulitple probe-sequences
+                * to avoid sharing the devm_ allocated clk_ops between multiple
+                * SCMI clk driver instances.
                 */
-               if (is_atomic &&
-                   sclk->info->enable_latency <= atomic_threshold)
-                       scmi_ops = &scmi_atomic_clk_ops;
-               else
-                       scmi_ops = &scmi_clk_ops;
+               scmi_ops = scmi_clk_ops_select(sclk, transport_is_atomic,
+                                              atomic_threshold_us,
+                                              scmi_clk_ops_db,
+                                              ARRAY_SIZE(scmi_clk_ops_db));
+               if (!scmi_ops)
+                       return -ENOMEM;
 
                /* Initialize clock parent data. */
                if (sclk->info->num_parents > 0) {
@@ -318,8 +472,7 @@ static int scmi_clocks_probe(struct scmi_device *sdev)
                } else {
                        dev_dbg(dev, "Registered clock:%s%s\n",
                                sclk->info->name,
-                               scmi_ops == &scmi_atomic_clk_ops ?
-                               " (atomic ops)" : "");
+                               scmi_ops->enable ? " (atomic ops)" : "");
                        hws[idx] = &sclk->hw;
                }
        }