summaryrefslogtreecommitdiffstats
path: root/drivers/clk/imx/clk-scu.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/imx/clk-scu.c')
-rw-r--r--drivers/clk/imx/clk-scu.c389
1 files changed, 389 insertions, 0 deletions
diff --git a/drivers/clk/imx/clk-scu.c b/drivers/clk/imx/clk-scu.c
new file mode 100644
index 000000000..b8b207274
--- /dev/null
+++ b/drivers/clk/imx/clk-scu.c
@@ -0,0 +1,389 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2018 NXP
+ * Dong Aisheng <aisheng.dong@nxp.com>
+ */
+
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/arm-smccc.h>
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+
+#include "clk-scu.h"
+
+#define IMX_SIP_CPUFREQ 0xC2000001
+#define IMX_SIP_SET_CPUFREQ 0x00
+
+static struct imx_sc_ipc *ccm_ipc_handle;
+
+/*
+ * struct clk_scu - Description of one SCU clock
+ * @hw: the common clk_hw
+ * @rsrc_id: resource ID of this SCU clock
+ * @clk_type: type of this clock resource
+ */
+struct clk_scu {
+ struct clk_hw hw;
+ u16 rsrc_id;
+ u8 clk_type;
+};
+
+/*
+ * struct imx_sc_msg_req_set_clock_rate - clock set rate protocol
+ * @hdr: SCU protocol header
+ * @rate: rate to set
+ * @resource: clock resource to set rate
+ * @clk: clk type of this resource
+ *
+ * This structure describes the SCU protocol of clock rate set
+ */
+struct imx_sc_msg_req_set_clock_rate {
+ struct imx_sc_rpc_msg hdr;
+ __le32 rate;
+ __le16 resource;
+ u8 clk;
+} __packed __aligned(4);
+
+struct req_get_clock_rate {
+ __le16 resource;
+ u8 clk;
+} __packed __aligned(4);
+
+struct resp_get_clock_rate {
+ __le32 rate;
+};
+
+/*
+ * struct imx_sc_msg_get_clock_rate - clock get rate protocol
+ * @hdr: SCU protocol header
+ * @req: get rate request protocol
+ * @resp: get rate response protocol
+ *
+ * This structure describes the SCU protocol of clock rate get
+ */
+struct imx_sc_msg_get_clock_rate {
+ struct imx_sc_rpc_msg hdr;
+ union {
+ struct req_get_clock_rate req;
+ struct resp_get_clock_rate resp;
+ } data;
+};
+
+/*
+ * struct imx_sc_msg_get_clock_parent - clock get parent protocol
+ * @hdr: SCU protocol header
+ * @req: get parent request protocol
+ * @resp: get parent response protocol
+ *
+ * This structure describes the SCU protocol of clock get parent
+ */
+struct imx_sc_msg_get_clock_parent {
+ struct imx_sc_rpc_msg hdr;
+ union {
+ struct req_get_clock_parent {
+ __le16 resource;
+ u8 clk;
+ } __packed __aligned(4) req;
+ struct resp_get_clock_parent {
+ u8 parent;
+ } resp;
+ } data;
+};
+
+/*
+ * struct imx_sc_msg_set_clock_parent - clock set parent protocol
+ * @hdr: SCU protocol header
+ * @req: set parent request protocol
+ *
+ * This structure describes the SCU protocol of clock set parent
+ */
+struct imx_sc_msg_set_clock_parent {
+ struct imx_sc_rpc_msg hdr;
+ __le16 resource;
+ u8 clk;
+ u8 parent;
+} __packed;
+
+/*
+ * struct imx_sc_msg_req_clock_enable - clock gate protocol
+ * @hdr: SCU protocol header
+ * @resource: clock resource to gate
+ * @clk: clk type of this resource
+ * @enable: whether gate off the clock
+ * @autog: HW auto gate enable
+ *
+ * This structure describes the SCU protocol of clock gate
+ */
+struct imx_sc_msg_req_clock_enable {
+ struct imx_sc_rpc_msg hdr;
+ __le16 resource;
+ u8 clk;
+ u8 enable;
+ u8 autog;
+} __packed __aligned(4);
+
+static inline struct clk_scu *to_clk_scu(struct clk_hw *hw)
+{
+ return container_of(hw, struct clk_scu, hw);
+}
+
+int imx_clk_scu_init(void)
+{
+ return imx_scu_get_handle(&ccm_ipc_handle);
+}
+
+/*
+ * clk_scu_recalc_rate - Get clock rate for a SCU clock
+ * @hw: clock to get rate for
+ * @parent_rate: parent rate provided by common clock framework, not used
+ *
+ * Gets the current clock rate of a SCU clock. Returns the current
+ * clock rate, or zero in failure.
+ */
+static unsigned long clk_scu_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ struct imx_sc_msg_get_clock_rate msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+ int ret;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_PM;
+ hdr->func = IMX_SC_PM_FUNC_GET_CLOCK_RATE;
+ hdr->size = 2;
+
+ msg.data.req.resource = cpu_to_le16(clk->rsrc_id);
+ msg.data.req.clk = clk->clk_type;
+
+ ret = imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
+ if (ret) {
+ pr_err("%s: failed to get clock rate %d\n",
+ clk_hw_get_name(hw), ret);
+ return 0;
+ }
+
+ return le32_to_cpu(msg.data.resp.rate);
+}
+
+/*
+ * clk_scu_round_rate - Round clock rate for a SCU clock
+ * @hw: clock to round rate for
+ * @rate: rate to round
+ * @parent_rate: parent rate provided by common clock framework, not used
+ *
+ * Returns the current clock rate, or zero in failure.
+ */
+static long clk_scu_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ /*
+ * Assume we support all the requested rate and let the SCU firmware
+ * to handle the left work
+ */
+ return rate;
+}
+
+static int clk_scu_atf_set_cpu_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ struct arm_smccc_res res;
+ unsigned long cluster_id;
+
+ if (clk->rsrc_id == IMX_SC_R_A35)
+ cluster_id = 0;
+ else
+ return -EINVAL;
+
+ /* CPU frequency scaling can ONLY be done by ARM-Trusted-Firmware */
+ arm_smccc_smc(IMX_SIP_CPUFREQ, IMX_SIP_SET_CPUFREQ,
+ cluster_id, rate, 0, 0, 0, 0, &res);
+
+ return 0;
+}
+
+/*
+ * clk_scu_set_rate - Set rate for a SCU clock
+ * @hw: clock to change rate for
+ * @rate: target rate for the clock
+ * @parent_rate: rate of the clock parent, not used for SCU clocks
+ *
+ * Sets a clock frequency for a SCU clock. Returns the SCU
+ * protocol status.
+ */
+static int clk_scu_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ struct imx_sc_msg_req_set_clock_rate msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_PM;
+ hdr->func = IMX_SC_PM_FUNC_SET_CLOCK_RATE;
+ hdr->size = 3;
+
+ msg.rate = cpu_to_le32(rate);
+ msg.resource = cpu_to_le16(clk->rsrc_id);
+ msg.clk = clk->clk_type;
+
+ return imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
+}
+
+static u8 clk_scu_get_parent(struct clk_hw *hw)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ struct imx_sc_msg_get_clock_parent msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+ int ret;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_PM;
+ hdr->func = IMX_SC_PM_FUNC_GET_CLOCK_PARENT;
+ hdr->size = 2;
+
+ msg.data.req.resource = cpu_to_le16(clk->rsrc_id);
+ msg.data.req.clk = clk->clk_type;
+
+ ret = imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
+ if (ret) {
+ pr_err("%s: failed to get clock parent %d\n",
+ clk_hw_get_name(hw), ret);
+ return 0;
+ }
+
+ return msg.data.resp.parent;
+}
+
+static int clk_scu_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ struct imx_sc_msg_set_clock_parent msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_PM;
+ hdr->func = IMX_SC_PM_FUNC_SET_CLOCK_PARENT;
+ hdr->size = 2;
+
+ msg.resource = cpu_to_le16(clk->rsrc_id);
+ msg.clk = clk->clk_type;
+ msg.parent = index;
+
+ return imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
+}
+
+static int sc_pm_clock_enable(struct imx_sc_ipc *ipc, u16 resource,
+ u8 clk, bool enable, bool autog)
+{
+ struct imx_sc_msg_req_clock_enable msg;
+ struct imx_sc_rpc_msg *hdr = &msg.hdr;
+
+ hdr->ver = IMX_SC_RPC_VERSION;
+ hdr->svc = IMX_SC_RPC_SVC_PM;
+ hdr->func = IMX_SC_PM_FUNC_CLOCK_ENABLE;
+ hdr->size = 3;
+
+ msg.resource = cpu_to_le16(resource);
+ msg.clk = clk;
+ msg.enable = enable;
+ msg.autog = autog;
+
+ return imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
+}
+
+/*
+ * clk_scu_prepare - Enable a SCU clock
+ * @hw: clock to enable
+ *
+ * Enable the clock at the DSC slice level
+ */
+static int clk_scu_prepare(struct clk_hw *hw)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+
+ return sc_pm_clock_enable(ccm_ipc_handle, clk->rsrc_id,
+ clk->clk_type, true, false);
+}
+
+/*
+ * clk_scu_unprepare - Disable a SCU clock
+ * @hw: clock to enable
+ *
+ * Disable the clock at the DSC slice level
+ */
+static void clk_scu_unprepare(struct clk_hw *hw)
+{
+ struct clk_scu *clk = to_clk_scu(hw);
+ int ret;
+
+ ret = sc_pm_clock_enable(ccm_ipc_handle, clk->rsrc_id,
+ clk->clk_type, false, false);
+ if (ret)
+ pr_warn("%s: clk unprepare failed %d\n", clk_hw_get_name(hw),
+ ret);
+}
+
+static const struct clk_ops clk_scu_ops = {
+ .recalc_rate = clk_scu_recalc_rate,
+ .round_rate = clk_scu_round_rate,
+ .set_rate = clk_scu_set_rate,
+ .get_parent = clk_scu_get_parent,
+ .set_parent = clk_scu_set_parent,
+ .prepare = clk_scu_prepare,
+ .unprepare = clk_scu_unprepare,
+};
+
+static const struct clk_ops clk_scu_cpu_ops = {
+ .recalc_rate = clk_scu_recalc_rate,
+ .round_rate = clk_scu_round_rate,
+ .set_rate = clk_scu_atf_set_cpu_rate,
+ .prepare = clk_scu_prepare,
+ .unprepare = clk_scu_unprepare,
+};
+
+struct clk_hw *__imx_clk_scu(const char *name, const char * const *parents,
+ int num_parents, u32 rsrc_id, u8 clk_type)
+{
+ struct clk_init_data init;
+ struct clk_scu *clk;
+ struct clk_hw *hw;
+ int ret;
+
+ clk = kzalloc(sizeof(*clk), GFP_KERNEL);
+ if (!clk)
+ return ERR_PTR(-ENOMEM);
+
+ clk->rsrc_id = rsrc_id;
+ clk->clk_type = clk_type;
+
+ init.name = name;
+ init.ops = &clk_scu_ops;
+ if (rsrc_id == IMX_SC_R_A35)
+ init.ops = &clk_scu_cpu_ops;
+ else
+ init.ops = &clk_scu_ops;
+ init.parent_names = parents;
+ init.num_parents = num_parents;
+
+ /*
+ * Note on MX8, the clocks are tightly coupled with power domain
+ * that once the power domain is off, the clock status may be
+ * lost. So we make it NOCACHE to let user to retrieve the real
+ * clock status from HW instead of using the possible invalid
+ * cached rate.
+ */
+ init.flags = CLK_GET_RATE_NOCACHE;
+ clk->hw.init = &init;
+
+ hw = &clk->hw;
+ ret = clk_hw_register(NULL, hw);
+ if (ret) {
+ kfree(clk);
+ hw = ERR_PTR(ret);
+ }
+
+ return hw;
+}