18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2014 Lucas Stach <l.stach@pengutronix.de>, Pengutronix
48c2ecf20Sopenharmony_ci */
58c2ecf20Sopenharmony_ci
68c2ecf20Sopenharmony_ci#include <linux/clk.h>
78c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
88c2ecf20Sopenharmony_ci#include <linux/export.h>
98c2ecf20Sopenharmony_ci#include <linux/slab.h>
108c2ecf20Sopenharmony_ci#include "clk.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistruct clk_cpu {
138c2ecf20Sopenharmony_ci	struct clk_hw	hw;
148c2ecf20Sopenharmony_ci	struct clk	*div;
158c2ecf20Sopenharmony_ci	struct clk	*mux;
168c2ecf20Sopenharmony_ci	struct clk	*pll;
178c2ecf20Sopenharmony_ci	struct clk	*step;
188c2ecf20Sopenharmony_ci};
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic inline struct clk_cpu *to_clk_cpu(struct clk_hw *hw)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	return container_of(hw, struct clk_cpu, hw);
238c2ecf20Sopenharmony_ci}
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistatic unsigned long clk_cpu_recalc_rate(struct clk_hw *hw,
268c2ecf20Sopenharmony_ci					 unsigned long parent_rate)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	struct clk_cpu *cpu = to_clk_cpu(hw);
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	return clk_get_rate(cpu->div);
318c2ecf20Sopenharmony_ci}
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic long clk_cpu_round_rate(struct clk_hw *hw, unsigned long rate,
348c2ecf20Sopenharmony_ci			       unsigned long *prate)
358c2ecf20Sopenharmony_ci{
368c2ecf20Sopenharmony_ci	struct clk_cpu *cpu = to_clk_cpu(hw);
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	return clk_round_rate(cpu->pll, rate);
398c2ecf20Sopenharmony_ci}
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_cistatic int clk_cpu_set_rate(struct clk_hw *hw, unsigned long rate,
428c2ecf20Sopenharmony_ci			    unsigned long parent_rate)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	struct clk_cpu *cpu = to_clk_cpu(hw);
458c2ecf20Sopenharmony_ci	int ret;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	/* switch to PLL bypass clock */
488c2ecf20Sopenharmony_ci	ret = clk_set_parent(cpu->mux, cpu->step);
498c2ecf20Sopenharmony_ci	if (ret)
508c2ecf20Sopenharmony_ci		return ret;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	/* reprogram PLL */
538c2ecf20Sopenharmony_ci	ret = clk_set_rate(cpu->pll, rate);
548c2ecf20Sopenharmony_ci	if (ret) {
558c2ecf20Sopenharmony_ci		clk_set_parent(cpu->mux, cpu->pll);
568c2ecf20Sopenharmony_ci		return ret;
578c2ecf20Sopenharmony_ci	}
588c2ecf20Sopenharmony_ci	/* switch back to PLL clock */
598c2ecf20Sopenharmony_ci	clk_set_parent(cpu->mux, cpu->pll);
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	/* Ensure the divider is what we expect */
628c2ecf20Sopenharmony_ci	clk_set_rate(cpu->div, rate);
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	return 0;
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic const struct clk_ops clk_cpu_ops = {
688c2ecf20Sopenharmony_ci	.recalc_rate	= clk_cpu_recalc_rate,
698c2ecf20Sopenharmony_ci	.round_rate	= clk_cpu_round_rate,
708c2ecf20Sopenharmony_ci	.set_rate	= clk_cpu_set_rate,
718c2ecf20Sopenharmony_ci};
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistruct clk_hw *imx_clk_hw_cpu(const char *name, const char *parent_name,
748c2ecf20Sopenharmony_ci		struct clk *div, struct clk *mux, struct clk *pll,
758c2ecf20Sopenharmony_ci		struct clk *step)
768c2ecf20Sopenharmony_ci{
778c2ecf20Sopenharmony_ci	struct clk_cpu *cpu;
788c2ecf20Sopenharmony_ci	struct clk_hw *hw;
798c2ecf20Sopenharmony_ci	struct clk_init_data init;
808c2ecf20Sopenharmony_ci	int ret;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	cpu = kzalloc(sizeof(*cpu), GFP_KERNEL);
838c2ecf20Sopenharmony_ci	if (!cpu)
848c2ecf20Sopenharmony_ci		return ERR_PTR(-ENOMEM);
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	cpu->div = div;
878c2ecf20Sopenharmony_ci	cpu->mux = mux;
888c2ecf20Sopenharmony_ci	cpu->pll = pll;
898c2ecf20Sopenharmony_ci	cpu->step = step;
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	init.name = name;
928c2ecf20Sopenharmony_ci	init.ops = &clk_cpu_ops;
938c2ecf20Sopenharmony_ci	init.flags = CLK_IS_CRITICAL;
948c2ecf20Sopenharmony_ci	init.parent_names = &parent_name;
958c2ecf20Sopenharmony_ci	init.num_parents = 1;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	cpu->hw.init = &init;
988c2ecf20Sopenharmony_ci	hw = &cpu->hw;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	ret = clk_hw_register(NULL, hw);
1018c2ecf20Sopenharmony_ci	if (ret) {
1028c2ecf20Sopenharmony_ci		kfree(cpu);
1038c2ecf20Sopenharmony_ci		return ERR_PTR(ret);
1048c2ecf20Sopenharmony_ci	}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci	return hw;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(imx_clk_hw_cpu);
109