| // 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.h> | 
 | #include <linux/firmware/imx/svc/rm.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/pm_domain.h> | 
 | #include <linux/pm_runtime.h> | 
 | #include <linux/slab.h> | 
 | #include <xen/xen.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; | 
 | 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; | 
 | 	} | 
 |  | 
 | 	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_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; | 
 | } | 
 |  | 
 | 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, | 
 | 	.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, | 
 | 	.determine_rate = clk_scu_determine_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, | 
 | 	.determine_rate = clk_scu_determine_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_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_prepared(&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); | 
 | } | 
 |  | 
 | static bool imx_clk_is_resource_owned(u32 rsrc) | 
 | { | 
 | 	/* | 
 | 	 * A-core resources are special. SCFW reports they are not "owned" by | 
 | 	 * current partition but linux can still adjust them for cpufreq. | 
 | 	 */ | 
 | 	if (rsrc == IMX_SC_R_A53 || rsrc == IMX_SC_R_A72 || rsrc == IMX_SC_R_A35) | 
 | 		return true; | 
 |  | 
 | 	return imx_sc_rm_is_resource_owned(ccm_ipc_handle, rsrc); | 
 | } | 
 |  | 
 | 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); | 
 |  | 
 | 	if (!imx_clk_is_resource_owned(rsrc_id)) | 
 | 		return NULL; | 
 |  | 
 | 	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) | 
 | 		goto put_device; | 
 |  | 
 | 	ret = driver_set_override(&pdev->dev, &pdev->driver_override, | 
 | 				  "imx-scu-clk", strlen("imx-scu-clk")); | 
 | 	if (ret) | 
 | 		goto put_device; | 
 |  | 
 | 	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) | 
 | 		goto put_device; | 
 |  | 
 | 	/* For API backwards compatibility, simply return NULL for success */ | 
 | 	return NULL; | 
 |  | 
 | put_device: | 
 | 	platform_device_put(pdev); | 
 | 	return ERR_PTR(ret); | 
 | } | 
 |  | 
 | 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 int clk_gpr_div_scu_determine_rate(struct clk_hw *hw, | 
 | 					  struct clk_rate_request *req) | 
 | { | 
 | 	if (req->rate < req->best_parent_rate) | 
 | 		req->rate = req->best_parent_rate / 2; | 
 | 	else | 
 | 		req->rate = req->best_parent_rate; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | 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, | 
 | 	.determine_rate = clk_gpr_div_scu_determine_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 = { | 
 | 	.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); | 
 | 	} | 
 |  | 
 | 	if (!imx_clk_is_resource_owned(rsrc_id)) { | 
 | 		kfree(clk_node); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	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; | 
 | } |