| /* |
| * Copyright (C) 2016 Freescale Semiconductor, Inc. |
| * Copyright 2017 NXP |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/clk.h> |
| #include <linux/clk-provider.h> |
| #include <linux/err.h> |
| #include <linux/io.h> |
| #include <linux/jiffies.h> |
| #include <linux/slab.h> |
| #include <soc/imx8/sc/sci.h> |
| |
| #include "clk-imx8.h" |
| |
| struct clk_divider_scu { |
| struct clk_divider div; |
| sc_rsrc_t rsrc_id; |
| sc_pm_clk_t clk_type; |
| }; |
| |
| struct clk_divider3_scu { |
| struct clk_divider div; |
| sc_rsrc_t rsrc_id; |
| sc_ctrl_t gpr_id; |
| }; |
| |
| static inline struct clk_divider3_scu *to_clk_divider3_scu(struct clk_hw *hw) |
| { |
| struct clk_divider *div = container_of(hw, struct clk_divider, hw); |
| |
| return container_of(div, struct clk_divider3_scu, div); |
| } |
| |
| static inline struct clk_divider_scu *to_clk_divider_scu(struct clk_hw *hw) |
| { |
| struct clk_divider *div = container_of(hw, struct clk_divider, hw); |
| |
| return container_of(div, struct clk_divider_scu, div); |
| } |
| |
| static unsigned long clk_divider_scu_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct clk_divider_scu *clk = to_clk_divider_scu(hw); |
| sc_err_t sci_err; |
| sc_pm_clock_rate_t rate = 0; |
| |
| if (!ccm_ipc_handle) |
| return 0; |
| |
| sci_err = sc_pm_get_clock_rate(ccm_ipc_handle, clk->rsrc_id, |
| clk->clk_type, &rate); |
| |
| return sci_err ? 0 : rate; |
| } |
| |
| static long clk_divider_scu_round_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long *prate) |
| { |
| /* TODO */ |
| *prate = rate; |
| |
| return rate; |
| } |
| |
| static int clk_divider_scu_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| struct clk_divider_scu *clk = to_clk_divider_scu(hw); |
| sc_err_t sci_err; |
| |
| if (!ccm_ipc_handle) { |
| return -EAGAIN; |
| } |
| |
| sci_err = sc_pm_set_clock_rate(ccm_ipc_handle, clk->rsrc_id, |
| clk->clk_type, (sc_pm_clock_rate_t *)&rate); |
| |
| return sci_err ? -EINVAL : 0; |
| } |
| |
| static struct clk_ops clk_divider_scu_ops = { |
| .recalc_rate = clk_divider_scu_recalc_rate, |
| .round_rate = clk_divider_scu_round_rate, |
| .set_rate = clk_divider_scu_set_rate, |
| }; |
| |
| struct clk *imx_clk_divider_scu(const char *name, |
| sc_rsrc_t rsrc_id, sc_pm_clk_t clk_type) |
| { |
| struct clk_divider_scu *div_clk; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| if (!imx8_clk_is_resource_owned(rsrc_id)) { |
| pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id); |
| return ERR_PTR(-ENODEV); |
| } |
| |
| div_clk = kzalloc(sizeof(*div_clk), GFP_KERNEL); |
| if (!div_clk) |
| return ERR_PTR(-ENOMEM); |
| |
| div_clk->rsrc_id = rsrc_id; |
| div_clk->clk_type = clk_type; |
| |
| init.name = name; |
| init.ops = &clk_divider_scu_ops; |
| init.flags = CLK_GET_RATE_NOCACHE; |
| init.num_parents = 0; |
| div_clk->div.hw.init = &init; |
| |
| clk = clk_register(NULL, &div_clk->div.hw); |
| if (IS_ERR(clk)) |
| kfree(div_clk); |
| |
| return clk; |
| } |
| |
| struct clk *imx_clk_divider2_scu(const char *name, const char *parent_name, |
| sc_rsrc_t rsrc_id, sc_pm_clk_t clk_type) |
| { |
| struct clk_divider_scu *div_clk; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| if (!imx8_clk_is_resource_owned(rsrc_id)) { |
| pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id); |
| return ERR_PTR(-ENODEV); |
| } |
| |
| div_clk = kzalloc(sizeof(*div_clk), GFP_KERNEL); |
| if (!div_clk) |
| return ERR_PTR(-ENOMEM); |
| |
| div_clk->rsrc_id = rsrc_id; |
| div_clk->clk_type = clk_type; |
| |
| init.name = name; |
| init.ops = &clk_divider_scu_ops; |
| init.flags = CLK_GET_RATE_NOCACHE; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| div_clk->div.hw.init = &init; |
| |
| clk = clk_register(NULL, &div_clk->div.hw); |
| if (IS_ERR(clk)) |
| kfree(div_clk); |
| |
| return clk; |
| } |
| |
| static unsigned long clk_divider3_scu_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| struct clk_divider3_scu *clk = to_clk_divider3_scu(hw); |
| uint32_t val; |
| sc_err_t sci_err; |
| sc_pm_clock_rate_t rate = 0; |
| |
| if (!ccm_ipc_handle) |
| return 0; |
| |
| sci_err = sc_misc_get_control(ccm_ipc_handle, clk->rsrc_id, |
| clk->gpr_id, &val); |
| |
| rate = (val) ? parent_rate / 2 : parent_rate; |
| |
| return sci_err ? 0 : rate; |
| } |
| |
| static long clk_divider3_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_divider3_scu_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| struct clk_divider3_scu *clk = to_clk_divider3_scu(hw); |
| uint32_t val; |
| sc_err_t sci_err; |
| |
| if (!ccm_ipc_handle) { |
| return -EAGAIN; |
| } |
| |
| val = (rate < parent_rate) ? 1 : 0; |
| sci_err = sc_misc_set_control(ccm_ipc_handle, clk->rsrc_id, |
| clk->gpr_id, val); |
| |
| return sci_err ? -EINVAL : 0; |
| } |
| |
| static struct clk_ops clk_divider3_scu_ops = { |
| .recalc_rate = clk_divider3_scu_recalc_rate, |
| .round_rate = clk_divider3_scu_round_rate, |
| .set_rate = clk_divider3_scu_set_rate, |
| }; |
| |
| struct clk *imx_clk_divider3_scu(const char *name, const char *parent_name, |
| sc_rsrc_t rsrc_id, sc_ctrl_t gpr_id) |
| { |
| struct clk_divider3_scu *div; |
| struct clk *clk; |
| struct clk_init_data init; |
| |
| if (!imx8_clk_is_resource_owned(rsrc_id)) { |
| pr_debug("skip clk %s rsrc %d not owned\n", name, rsrc_id); |
| return ERR_PTR(-ENODEV); |
| } |
| |
| div = kzalloc(sizeof(struct clk_divider3_scu), GFP_KERNEL); |
| if (!div) |
| return ERR_PTR(-ENOMEM); |
| |
| div->rsrc_id = rsrc_id; |
| div->gpr_id = gpr_id; |
| |
| init.name = name; |
| init.ops = &clk_divider3_scu_ops; |
| init.flags = 0; |
| init.parent_names = parent_name ? &parent_name : NULL; |
| init.num_parents = parent_name ? 1 : 0; |
| |
| div->div.hw.init = &init; |
| |
| clk = clk_register(NULL, &div->div.hw); |
| if (IS_ERR(clk)) |
| kfree(div); |
| |
| return clk; |
| } |