diff options
Diffstat (limited to 'drivers/clk/imx/clk-cpu.c')
-rw-r--r-- | drivers/clk/imx/clk-cpu.c | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/drivers/clk/imx/clk-cpu.c b/drivers/clk/imx/clk-cpu.c new file mode 100644 index 000000000..cb6ca4cf0 --- /dev/null +++ b/drivers/clk/imx/clk-cpu.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 Lucas Stach <l.stach@pengutronix.de>, Pengutronix + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/export.h> +#include <linux/slab.h> +#include "clk.h" + +struct clk_cpu { + struct clk_hw hw; + struct clk *div; + struct clk *mux; + struct clk *pll; + struct clk *step; +}; + +static inline struct clk_cpu *to_clk_cpu(struct clk_hw *hw) +{ + return container_of(hw, struct clk_cpu, hw); +} + +static unsigned long clk_cpu_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_cpu *cpu = to_clk_cpu(hw); + + return clk_get_rate(cpu->div); +} + +static long clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + struct clk_cpu *cpu = to_clk_cpu(hw); + + return clk_round_rate(cpu->pll, rate); +} + +static int clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_cpu *cpu = to_clk_cpu(hw); + int ret; + + /* switch to PLL bypass clock */ + ret = clk_set_parent(cpu->mux, cpu->step); + if (ret) + return ret; + + /* reprogram PLL */ + ret = clk_set_rate(cpu->pll, rate); + if (ret) { + clk_set_parent(cpu->mux, cpu->pll); + return ret; + } + /* switch back to PLL clock */ + clk_set_parent(cpu->mux, cpu->pll); + + /* Ensure the divider is what we expect */ + clk_set_rate(cpu->div, rate); + + return 0; +} + +static const struct clk_ops clk_cpu_ops = { + .recalc_rate = clk_cpu_recalc_rate, + .round_rate = clk_cpu_round_rate, + .set_rate = clk_cpu_set_rate, +}; + +struct clk_hw *imx_clk_hw_cpu(const char *name, const char *parent_name, + struct clk *div, struct clk *mux, struct clk *pll, + struct clk *step) +{ + struct clk_cpu *cpu; + struct clk_hw *hw; + struct clk_init_data init; + int ret; + + cpu = kzalloc(sizeof(*cpu), GFP_KERNEL); + if (!cpu) + return ERR_PTR(-ENOMEM); + + cpu->div = div; + cpu->mux = mux; + cpu->pll = pll; + cpu->step = step; + + init.name = name; + init.ops = &clk_cpu_ops; + init.flags = CLK_IS_CRITICAL; + init.parent_names = &parent_name; + init.num_parents = 1; + + cpu->hw.init = &init; + hw = &cpu->hw; + + ret = clk_hw_register(NULL, hw); + if (ret) { + kfree(cpu); + return ERR_PTR(ret); + } + + return hw; +} +EXPORT_SYMBOL_GPL(imx_clk_hw_cpu); |