18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: (GPL-2.0 OR MIT)
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (c) 2019 BayLibre, SAS.
48c2ecf20Sopenharmony_ci * Author: Neil Armstrong <narmstrong@baylibre.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/clk-provider.h>
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include "clk-regmap.h"
118c2ecf20Sopenharmony_ci#include "clk-cpu-dyndiv.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_cistatic inline struct meson_clk_cpu_dyndiv_data *
148c2ecf20Sopenharmony_cimeson_clk_cpu_dyndiv_data(struct clk_regmap *clk)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	return (struct meson_clk_cpu_dyndiv_data *)clk->data;
178c2ecf20Sopenharmony_ci}
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_cistatic unsigned long meson_clk_cpu_dyndiv_recalc_rate(struct clk_hw *hw,
208c2ecf20Sopenharmony_ci						      unsigned long prate)
218c2ecf20Sopenharmony_ci{
228c2ecf20Sopenharmony_ci	struct clk_regmap *clk = to_clk_regmap(hw);
238c2ecf20Sopenharmony_ci	struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	return divider_recalc_rate(hw, prate,
268c2ecf20Sopenharmony_ci				   meson_parm_read(clk->map, &data->div),
278c2ecf20Sopenharmony_ci				   NULL, 0, data->div.width);
288c2ecf20Sopenharmony_ci}
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_cistatic long meson_clk_cpu_dyndiv_round_rate(struct clk_hw *hw,
318c2ecf20Sopenharmony_ci					    unsigned long rate,
328c2ecf20Sopenharmony_ci					    unsigned long *prate)
338c2ecf20Sopenharmony_ci{
348c2ecf20Sopenharmony_ci	struct clk_regmap *clk = to_clk_regmap(hw);
358c2ecf20Sopenharmony_ci	struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	return divider_round_rate(hw, rate, prate, NULL, data->div.width, 0);
388c2ecf20Sopenharmony_ci}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int meson_clk_cpu_dyndiv_set_rate(struct clk_hw *hw, unsigned long rate,
418c2ecf20Sopenharmony_ci					  unsigned long parent_rate)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct clk_regmap *clk = to_clk_regmap(hw);
448c2ecf20Sopenharmony_ci	struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
458c2ecf20Sopenharmony_ci	unsigned int val;
468c2ecf20Sopenharmony_ci	int ret;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	ret = divider_get_val(rate, parent_rate, NULL, data->div.width, 0);
498c2ecf20Sopenharmony_ci	if (ret < 0)
508c2ecf20Sopenharmony_ci		return ret;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	val = (unsigned int)ret << data->div.shift;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	/* Write the SYS_CPU_DYN_ENABLE bit before changing the divider */
558c2ecf20Sopenharmony_ci	meson_parm_write(clk->map, &data->dyn, 1);
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	/* Update the divider while removing the SYS_CPU_DYN_ENABLE bit */
588c2ecf20Sopenharmony_ci	return regmap_update_bits(clk->map, data->div.reg_off,
598c2ecf20Sopenharmony_ci				  SETPMASK(data->div.width, data->div.shift) |
608c2ecf20Sopenharmony_ci				  SETPMASK(data->dyn.width, data->dyn.shift),
618c2ecf20Sopenharmony_ci				  val);
628c2ecf20Sopenharmony_ci};
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ciconst struct clk_ops meson_clk_cpu_dyndiv_ops = {
658c2ecf20Sopenharmony_ci	.recalc_rate = meson_clk_cpu_dyndiv_recalc_rate,
668c2ecf20Sopenharmony_ci	.round_rate = meson_clk_cpu_dyndiv_round_rate,
678c2ecf20Sopenharmony_ci	.set_rate = meson_clk_cpu_dyndiv_set_rate,
688c2ecf20Sopenharmony_ci};
698c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(meson_clk_cpu_dyndiv_ops);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Amlogic CPU Dynamic Clock divider");
728c2ecf20Sopenharmony_ciMODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
738c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
74