linux/drivers/clk/imx/clk-scu.c

913 lines
21 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright 2018-2021 NXP
* Dong Aisheng <aisheng.dong@nxp.com>
*/
#include <dt-bindings/firmware/imx/rsrc.h>
#include <linux/arm-smccc.h>
#include <linux/bsearch.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_runtime.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;
static struct device_node *pd_np;
clk: imx: scu: fix build break when compiled as modules After commit e0d0d4d86c76 ("clk: imx8qxp: Support building i.MX8QXP clock driver as module"), clk-scu.c and clk-imx8qxp.c are complied in one module, thus there can be only one module_init() in those two files. Commit 77d8f3068c63 ("clk: imx: scu: add two cells binding support") introduced another module_init() in clk_scu.c which caused the errors below. To fix the issue, we can remove the unnecessary builtin_platform_driver from clk_scu.c and directly register the driver in imx_clk_scu_init(). CC [M] drivers/clk/imx/clk-scu.o In file included from ../include/linux/of_device.h:6, from ../include/linux/of_platform.h:12, from ../drivers/clk/imx/clk-scu.c:11: ../drivers/clk/imx/clk-scu.c: In function ‘imx_clk_scu_init’: ../drivers/clk/imx/clk-scu.c:176:35: error: ‘imx_clk_scu_driver’ undeclared (first use in this function); did you mean ‘imx_clk_scu_init’? 176 | return platform_driver_register(&imx_clk_scu_driver); | ^~~~~~~~~~~~~~~~~~ ../include/linux/platform_device.h:218:29: note: in definition of macro ‘platform_driver_register’ 218 | __platform_driver_register(drv, THIS_MODULE) | ^~~ ../drivers/clk/imx/clk-scu.c:176:35: note: each undeclared identifier is reported only once for each function it appears in 176 | return platform_driver_register(&imx_clk_scu_driver); | ^~~~~~~~~~~~~~~~~~ ../include/linux/platform_device.h:218:29: note: in definition of macro ‘platform_driver_register’ 218 | __platform_driver_register(drv, THIS_MODULE) | ^~~ ../drivers/clk/imx/clk-scu.c:177:1: error: control reaches end of non-void function [-Werror=return-type] 177 | } | ^ At top level: ../drivers/clk/imx/clk-scu.c:470:31: warning: ‘imx_clk_scu_driver’ defined but not used [-Wunused-variable] 470 | static struct platform_driver imx_clk_scu_driver = { Reported-by: kernel test robot <lkp@intel.com> Fixes: 77d8f3068c63 ("clk: imx: scu: add two cells binding support") Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com> Acked-by: Stephen Boyd <sboyd@kernel.org> Signed-off-by: Shawn Guo <shawnguo@kernel.org>
2020-10-30 15:37:33 +00:00
static struct platform_driver imx_clk_scu_driver;
static const struct imx_clk_scu_rsrc_table *rsrc_table;
struct imx_scu_clk_node {
const char *name;
u32 rsrc;
u8 clk_type;
const char * const *parents;
int num_parents;
struct clk_hw *hw;
struct list_head node;
};
struct list_head imx_scu_clks[IMX_SC_R_LAST];
/*
* 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;
/* for state save&restore */
struct clk_hw *parent;
u8 parent_index;
bool is_enabled;
u32 rate;
};
/*
* struct clk_gpr_scu - Description of one SCU GPR clock
* @hw: the common clk_hw
* @rsrc_id: resource ID of this SCU clock
* @gpr_id: GPR ID index to control the divider
*/
struct clk_gpr_scu {
struct clk_hw hw;
u16 rsrc_id;
u8 gpr_id;
u8 flags;
bool gate_invert;
};
#define to_clk_gpr_scu(_hw) container_of(_hw, struct clk_gpr_scu, hw)
/*
* 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);
}
static inline int imx_scu_clk_search_cmp(const void *rsrc, const void *rsrc_p)
{
return *(u32 *)rsrc - *(u32 *)rsrc_p;
}
static bool imx_scu_clk_is_valid(u32 rsrc_id)
{
void *p;
if (!rsrc_table)
return true;
p = bsearch(&rsrc_id, rsrc_table->rsrc, rsrc_table->num,
sizeof(rsrc_table->rsrc[0]), imx_scu_clk_search_cmp);
return p != NULL;
}
int imx_clk_scu_init(struct device_node *np,
const struct imx_clk_scu_rsrc_table *data)
{
u32 clk_cells;
int ret, i;
ret = imx_scu_get_handle(&ccm_ipc_handle);
if (ret)
return ret;
of_property_read_u32(np, "#clock-cells", &clk_cells);
if (clk_cells == 2) {
for (i = 0; i < IMX_SC_R_LAST; i++)
INIT_LIST_HEAD(&imx_scu_clks[i]);
/* pd_np will be used to attach power domains later */
pd_np = of_find_compatible_node(NULL, NULL, "fsl,scu-pd");
if (!pd_np)
return -EINVAL;
rsrc_table = data;
}
clk: imx: scu: fix build break when compiled as modules After commit e0d0d4d86c76 ("clk: imx8qxp: Support building i.MX8QXP clock driver as module"), clk-scu.c and clk-imx8qxp.c are complied in one module, thus there can be only one module_init() in those two files. Commit 77d8f3068c63 ("clk: imx: scu: add two cells binding support") introduced another module_init() in clk_scu.c which caused the errors below. To fix the issue, we can remove the unnecessary builtin_platform_driver from clk_scu.c and directly register the driver in imx_clk_scu_init(). CC [M] drivers/clk/imx/clk-scu.o In file included from ../include/linux/of_device.h:6, from ../include/linux/of_platform.h:12, from ../drivers/clk/imx/clk-scu.c:11: ../drivers/clk/imx/clk-scu.c: In function ‘imx_clk_scu_init’: ../drivers/clk/imx/clk-scu.c:176:35: error: ‘imx_clk_scu_driver’ undeclared (first use in this function); did you mean ‘imx_clk_scu_init’? 176 | return platform_driver_register(&imx_clk_scu_driver); | ^~~~~~~~~~~~~~~~~~ ../include/linux/platform_device.h:218:29: note: in definition of macro ‘platform_driver_register’ 218 | __platform_driver_register(drv, THIS_MODULE) | ^~~ ../drivers/clk/imx/clk-scu.c:176:35: note: each undeclared identifier is reported only once for each function it appears in 176 | return platform_driver_register(&imx_clk_scu_driver); | ^~~~~~~~~~~~~~~~~~ ../include/linux/platform_device.h:218:29: note: in definition of macro ‘platform_driver_register’ 218 | __platform_driver_register(drv, THIS_MODULE) | ^~~ ../drivers/clk/imx/clk-scu.c:177:1: error: control reaches end of non-void function [-Werror=return-type] 177 | } | ^ At top level: ../drivers/clk/imx/clk-scu.c:470:31: warning: ‘imx_clk_scu_driver’ defined but not used [-Wunused-variable] 470 | static struct platform_driver imx_clk_scu_driver = { Reported-by: kernel test robot <lkp@intel.com> Fixes: 77d8f3068c63 ("clk: imx: scu: add two cells binding support") Signed-off-by: Dong Aisheng <aisheng.dong@nxp.com> Acked-by: Stephen Boyd <sboyd@kernel.org> Signed-off-by: Shawn Guo <shawnguo@kernel.org>
2020-10-30 15:37:33 +00:00
return platform_driver_register(&imx_clk_scu_driver);
}
/*
* 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: imx: scu: Switch to determine_rate The iMX SCU clocks implements a mux with a set_parent hook, but doesn't provide a determine_rate implementation. This is a bit odd, since set_parent() is there to, as its name implies, change the parent of a clock. However, the most likely candidate to trigger that parent change is a call to clk_set_rate(), with determine_rate() figuring out which parent is the best suited for a given rate. The other trigger would be a call to clk_set_parent(), but it's far less used, and it doesn't look like there's any obvious user for that clock. So, the set_parent hook is effectively unused, possibly because of an oversight. However, it could also be an explicit decision by the original author to avoid any reparenting but through an explicit call to clk_set_parent(). The driver does implement round_rate() though, which means that we can change the rate of the clock, but we will never get to change the parent. However, It's hard to tell whether it's been done on purpose or not. Since we'll start mandating a determine_rate() implementation, let's convert the round_rate() implementation to a determine_rate(), which will also make the current behavior explicit. The round_rate() implementation being shared with other clocks, it's not removed. And if it was an oversight, the clock behaviour can be adjusted later on. Cc: Abel Vesa <abelvesa@kernel.org> Cc: Fabio Estevam <festevam@gmail.com> Cc: Peng Fan <peng.fan@nxp.com> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Cc: linux-arm-kernel@lists.infradead.org Cc: NXP Linux Team <linux-imx@nxp.com> Cc: Pengutronix Kernel Team <kernel@pengutronix.de> Signed-off-by: Maxime Ripard <maxime@cerno.tech> Link: https://lore.kernel.org/r/20221018-clk-range-checks-fixes-v4-58-971d5077e7d2@cerno.tech Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-05-05 11:26:00 +00:00
/*
* clk_scu_determine_rate - Returns the closest rate for a SCU clock
* @hw: clock to round rate for
* @req: clock rate request
*
* Returns 0 on success, a negative error on failure
*/
static int clk_scu_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
/*
* Assume we support all the requested rate and let the SCU firmware
* to handle the left work
*/
return 0;
}
/*
* 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 || clk->rsrc_id == IMX_SC_R_A53)
cluster_id = 0;
else if (clk->rsrc_id == IMX_SC_R_A72)
cluster_id = 1;
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;
}
clk->parent_index = msg.data.resp.parent;
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;
int ret;
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;
ret = imx_scu_call_rpc(ccm_ipc_handle, &msg, true);
if (ret) {
pr_err("%s: failed to set clock parent %d\n",
clk_hw_get_name(hw), ret);
return ret;
}
clk->parent_index = index;
return 0;
}
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,
clk: imx: scu: Switch to determine_rate The iMX SCU clocks implements a mux with a set_parent hook, but doesn't provide a determine_rate implementation. This is a bit odd, since set_parent() is there to, as its name implies, change the parent of a clock. However, the most likely candidate to trigger that parent change is a call to clk_set_rate(), with determine_rate() figuring out which parent is the best suited for a given rate. The other trigger would be a call to clk_set_parent(), but it's far less used, and it doesn't look like there's any obvious user for that clock. So, the set_parent hook is effectively unused, possibly because of an oversight. However, it could also be an explicit decision by the original author to avoid any reparenting but through an explicit call to clk_set_parent(). The driver does implement round_rate() though, which means that we can change the rate of the clock, but we will never get to change the parent. However, It's hard to tell whether it's been done on purpose or not. Since we'll start mandating a determine_rate() implementation, let's convert the round_rate() implementation to a determine_rate(), which will also make the current behavior explicit. The round_rate() implementation being shared with other clocks, it's not removed. And if it was an oversight, the clock behaviour can be adjusted later on. Cc: Abel Vesa <abelvesa@kernel.org> Cc: Fabio Estevam <festevam@gmail.com> Cc: Peng Fan <peng.fan@nxp.com> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Cc: linux-arm-kernel@lists.infradead.org Cc: NXP Linux Team <linux-imx@nxp.com> Cc: Pengutronix Kernel Team <kernel@pengutronix.de> Signed-off-by: Maxime Ripard <maxime@cerno.tech> Link: https://lore.kernel.org/r/20221018-clk-range-checks-fixes-v4-58-971d5077e7d2@cerno.tech Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-05-05 11:26:00 +00:00
.determine_rate = clk_scu_determine_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,
};
static const struct clk_ops clk_scu_pi_ops = {
.recalc_rate = clk_scu_recalc_rate,
.round_rate = clk_scu_round_rate,
.set_rate = clk_scu_set_rate,
};
struct clk_hw *__imx_clk_scu(struct device *dev, 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 || rsrc_id == IMX_SC_R_A53 || rsrc_id == IMX_SC_R_A72)
init.ops = &clk_scu_cpu_ops;
else if (rsrc_id == IMX_SC_R_PI_0_PLL)
init.ops = &clk_scu_pi_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(dev, hw);
if (ret) {
kfree(clk);
hw = ERR_PTR(ret);
return hw;
}
if (dev)
dev_set_drvdata(dev, clk);
return hw;
}
struct clk_hw *imx_scu_of_clk_src_get(struct of_phandle_args *clkspec,
void *data)
{
unsigned int rsrc = clkspec->args[0];
unsigned int idx = clkspec->args[1];
struct list_head *scu_clks = data;
struct imx_scu_clk_node *clk;
list_for_each_entry(clk, &scu_clks[rsrc], node) {
if (clk->clk_type == idx)
return clk->hw;
}
return ERR_PTR(-ENODEV);
}
static int imx_clk_scu_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct imx_scu_clk_node *clk = dev_get_platdata(dev);
struct clk_hw *hw;
int ret;
if (!((clk->rsrc == IMX_SC_R_A35) || (clk->rsrc == IMX_SC_R_A53) ||
(clk->rsrc == IMX_SC_R_A72))) {
pm_runtime_set_suspended(dev);
pm_runtime_set_autosuspend_delay(dev, 50);
pm_runtime_use_autosuspend(&pdev->dev);
pm_runtime_enable(dev);
ret = pm_runtime_resume_and_get(dev);
if (ret) {
pm_genpd_remove_device(dev);
pm_runtime_disable(dev);
return ret;
}
}
hw = __imx_clk_scu(dev, clk->name, clk->parents, clk->num_parents,
clk->rsrc, clk->clk_type);
if (IS_ERR(hw)) {
pm_runtime_disable(dev);
return PTR_ERR(hw);
}
clk->hw = hw;
list_add_tail(&clk->node, &imx_scu_clks[clk->rsrc]);
if (!((clk->rsrc == IMX_SC_R_A35) || (clk->rsrc == IMX_SC_R_A53) ||
(clk->rsrc == IMX_SC_R_A72))) {
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(&pdev->dev);
}
dev_dbg(dev, "register SCU clock rsrc:%d type:%d\n", clk->rsrc,
clk->clk_type);
return 0;
}
static int __maybe_unused imx_clk_scu_suspend(struct device *dev)
{
struct clk_scu *clk = dev_get_drvdata(dev);
u32 rsrc_id = clk->rsrc_id;
if ((rsrc_id == IMX_SC_R_A35) || (rsrc_id == IMX_SC_R_A53) ||
(rsrc_id == IMX_SC_R_A72))
return 0;
clk->parent = clk_hw_get_parent(&clk->hw);
/* DC SS needs to handle bypass clock using non-cached clock rate */
if (clk->rsrc_id == IMX_SC_R_DC_0_VIDEO0 ||
clk->rsrc_id == IMX_SC_R_DC_0_VIDEO1 ||
clk->rsrc_id == IMX_SC_R_DC_1_VIDEO0 ||
clk->rsrc_id == IMX_SC_R_DC_1_VIDEO1)
clk->rate = clk_scu_recalc_rate(&clk->hw, 0);
else
clk->rate = clk_hw_get_rate(&clk->hw);
clk->is_enabled = clk_hw_is_enabled(&clk->hw);
if (clk->parent)
dev_dbg(dev, "save parent %s idx %u\n", clk_hw_get_name(clk->parent),
clk->parent_index);
if (clk->rate)
dev_dbg(dev, "save rate %d\n", clk->rate);
if (clk->is_enabled)
dev_dbg(dev, "save enabled state\n");
return 0;
}
static int __maybe_unused imx_clk_scu_resume(struct device *dev)
{
struct clk_scu *clk = dev_get_drvdata(dev);
u32 rsrc_id = clk->rsrc_id;
int ret = 0;
if ((rsrc_id == IMX_SC_R_A35) || (rsrc_id == IMX_SC_R_A53) ||
(rsrc_id == IMX_SC_R_A72))
return 0;
if (clk->parent) {
ret = clk_scu_set_parent(&clk->hw, clk->parent_index);
dev_dbg(dev, "restore parent %s idx %u %s\n",
clk_hw_get_name(clk->parent),
clk->parent_index, !ret ? "success" : "failed");
}
if (clk->rate) {
ret = clk_scu_set_rate(&clk->hw, clk->rate, 0);
dev_dbg(dev, "restore rate %d %s\n", clk->rate,
!ret ? "success" : "failed");
}
if (clk->is_enabled && rsrc_id != IMX_SC_R_PI_0_PLL) {
ret = clk_scu_prepare(&clk->hw);
dev_dbg(dev, "restore enabled state %s\n",
!ret ? "success" : "failed");
}
return ret;
}
static const struct dev_pm_ops imx_clk_scu_pm_ops = {
SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_clk_scu_suspend,
imx_clk_scu_resume)
};
static struct platform_driver imx_clk_scu_driver = {
.driver = {
.name = "imx-scu-clk",
.suppress_bind_attrs = true,
.pm = &imx_clk_scu_pm_ops,
},
.probe = imx_clk_scu_probe,
};
static int imx_clk_scu_attach_pd(struct device *dev, u32 rsrc_id)
{
struct of_phandle_args genpdspec = {
.np = pd_np,
.args_count = 1,
.args[0] = rsrc_id,
};
if (rsrc_id == IMX_SC_R_A35 || rsrc_id == IMX_SC_R_A53 ||
rsrc_id == IMX_SC_R_A72)
return 0;
return of_genpd_add_device(&genpdspec, dev);
}
struct clk_hw *imx_clk_scu_alloc_dev(const char *name,
const char * const *parents,
int num_parents, u32 rsrc_id, u8 clk_type)
{
struct imx_scu_clk_node clk = {
.name = name,
.rsrc = rsrc_id,
.clk_type = clk_type,
.parents = parents,
.num_parents = num_parents,
};
struct platform_device *pdev;
int ret;
if (!imx_scu_clk_is_valid(rsrc_id))
return ERR_PTR(-EINVAL);
pdev = platform_device_alloc(name, PLATFORM_DEVID_NONE);
if (!pdev) {
pr_err("%s: failed to allocate scu clk dev rsrc %d type %d\n",
name, rsrc_id, clk_type);
return ERR_PTR(-ENOMEM);
}
ret = platform_device_add_data(pdev, &clk, sizeof(clk));
if (ret) {
platform_device_put(pdev);
return ERR_PTR(ret);
}
ret = driver_set_override(&pdev->dev, &pdev->driver_override,
"imx-scu-clk", strlen("imx-scu-clk"));
if (ret) {
platform_device_put(pdev);
return ERR_PTR(ret);
}
ret = imx_clk_scu_attach_pd(&pdev->dev, rsrc_id);
if (ret)
pr_warn("%s: failed to attached the power domain %d\n",
name, ret);
ret = platform_device_add(pdev);
if (ret) {
platform_device_put(pdev);
return ERR_PTR(ret);
}
/* For API backwards compatiblilty, simply return NULL for success */
return NULL;
}
void imx_clk_scu_unregister(void)
{
struct imx_scu_clk_node *clk, *n;
int i;
for (i = 0; i < IMX_SC_R_LAST; i++) {
list_for_each_entry_safe(clk, n, &imx_scu_clks[i], node) {
clk_hw_unregister(clk->hw);
kfree(clk);
}
}
}
static unsigned long clk_gpr_div_scu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
unsigned long rate = 0;
u32 val;
int err;
err = imx_sc_misc_get_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, &val);
rate = val ? parent_rate / 2 : parent_rate;
return err ? 0 : rate;
}
static long clk_gpr_div_scu_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
if (rate < *prate)
rate = *prate / 2;
else
rate = *prate;
return rate;
}
static int clk_gpr_div_scu_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
uint32_t val;
int err;
val = (rate < parent_rate) ? 1 : 0;
err = imx_sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, val);
return err ? -EINVAL : 0;
}
static const struct clk_ops clk_gpr_div_scu_ops = {
.recalc_rate = clk_gpr_div_scu_recalc_rate,
.round_rate = clk_gpr_div_scu_round_rate,
.set_rate = clk_gpr_div_scu_set_rate,
};
static u8 clk_gpr_mux_scu_get_parent(struct clk_hw *hw)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
u32 val = 0;
imx_sc_misc_get_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, &val);
return (u8)val;
}
static int clk_gpr_mux_scu_set_parent(struct clk_hw *hw, u8 index)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
return imx_sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, index);
}
static const struct clk_ops clk_gpr_mux_scu_ops = {
clk: imx: scu: Add a determine_rate hook The iMX SCU mux clock implements a mux with a set_parent hook, but doesn't provide a determine_rate implementation. This is a bit odd, since set_parent() is there to, as its name implies, change the parent of a clock. However, the most likely candidates to trigger that parent change are either the assigned-clock-parents device tree property or a call to clk_set_rate(), with determine_rate() figuring out which parent is the best suited for a given rate. The other trigger would be a call to clk_set_parent(), but it's far less used, and it doesn't look like there's any obvious user for that clock. Similarly, it doesn't look like the device tree using that clock driver uses any of the assigned-clock properties on that clock. So, the set_parent hook is effectively unused, possibly because of an oversight. However, it could also be an explicit decision by the original author to avoid any reparenting but through an explicit call to clk_set_parent(). The latter case would be equivalent to setting the determine_rate implementation to clk_hw_determine_rate_no_reparent(). Indeed, if no determine_rate implementation is provided, clk_round_rate() (through clk_core_round_rate_nolock()) will call itself on the parent if CLK_SET_RATE_PARENT is set, and will not change the clock rate otherwise. And if it was an oversight, then we are at least explicit about our behavior now and it can be further refined down the line. Cc: Abel Vesa <abelvesa@kernel.org> Cc: Fabio Estevam <festevam@gmail.com> Cc: Peng Fan <peng.fan@nxp.com> Cc: Sascha Hauer <s.hauer@pengutronix.de> Cc: Shawn Guo <shawnguo@kernel.org> Cc: linux-arm-kernel@lists.infradead.org Cc: NXP Linux Team <linux-imx@nxp.com> Cc: Pengutronix Kernel Team <kernel@pengutronix.de> Signed-off-by: Maxime Ripard <maxime@cerno.tech> Link: https://lore.kernel.org/r/20221018-clk-range-checks-fixes-v4-28-971d5077e7d2@cerno.tech | Reported-by: kernel test robot <lkp@intel.com>: Signed-off-by: Stephen Boyd <sboyd@kernel.org>
2023-05-05 11:25:30 +00:00
.determine_rate = clk_hw_determine_rate_no_reparent,
.get_parent = clk_gpr_mux_scu_get_parent,
.set_parent = clk_gpr_mux_scu_set_parent,
};
static int clk_gpr_gate_scu_prepare(struct clk_hw *hw)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
return imx_sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, !clk->gate_invert);
}
static void clk_gpr_gate_scu_unprepare(struct clk_hw *hw)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
int ret;
ret = imx_sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, clk->gate_invert);
if (ret)
pr_err("%s: clk unprepare failed %d\n", clk_hw_get_name(hw),
ret);
}
static int clk_gpr_gate_scu_is_prepared(struct clk_hw *hw)
{
struct clk_gpr_scu *clk = to_clk_gpr_scu(hw);
int ret;
u32 val;
ret = imx_sc_misc_get_control(ccm_ipc_handle, clk->rsrc_id,
clk->gpr_id, &val);
if (ret)
return ret;
return clk->gate_invert ? !val : val;
}
static const struct clk_ops clk_gpr_gate_scu_ops = {
.prepare = clk_gpr_gate_scu_prepare,
.unprepare = clk_gpr_gate_scu_unprepare,
.is_prepared = clk_gpr_gate_scu_is_prepared,
};
struct clk_hw *__imx_clk_gpr_scu(const char *name, const char * const *parent_name,
int num_parents, u32 rsrc_id, u8 gpr_id, u8 flags,
bool invert)
{
struct imx_scu_clk_node *clk_node;
struct clk_gpr_scu *clk;
struct clk_hw *hw;
struct clk_init_data init;
int ret;
if (rsrc_id >= IMX_SC_R_LAST || gpr_id >= IMX_SC_C_LAST)
return ERR_PTR(-EINVAL);
clk_node = kzalloc(sizeof(*clk_node), GFP_KERNEL);
if (!clk_node)
return ERR_PTR(-ENOMEM);
if (!imx_scu_clk_is_valid(rsrc_id)) {
kfree(clk_node);
return ERR_PTR(-EINVAL);
}
clk = kzalloc(sizeof(*clk), GFP_KERNEL);
if (!clk) {
kfree(clk_node);
return ERR_PTR(-ENOMEM);
}
clk->rsrc_id = rsrc_id;
clk->gpr_id = gpr_id;
clk->flags = flags;
clk->gate_invert = invert;
if (flags & IMX_SCU_GPR_CLK_GATE)
init.ops = &clk_gpr_gate_scu_ops;
if (flags & IMX_SCU_GPR_CLK_DIV)
init.ops = &clk_gpr_div_scu_ops;
if (flags & IMX_SCU_GPR_CLK_MUX)
init.ops = &clk_gpr_mux_scu_ops;
init.flags = 0;
init.name = name;
init.parent_names = parent_name;
init.num_parents = num_parents;
clk->hw.init = &init;
hw = &clk->hw;
ret = clk_hw_register(NULL, hw);
if (ret) {
kfree(clk);
kfree(clk_node);
hw = ERR_PTR(ret);
} else {
clk_node->hw = hw;
clk_node->clk_type = gpr_id;
list_add_tail(&clk_node->node, &imx_scu_clks[rsrc_id]);
}
return hw;
}