alistair23-linux/drivers/clk/meson/clk-cpu-dyndiv.c
Neil Armstrong 26d34431ad clk: meson: add g12a cpu dynamic divider driver
Add a clock driver for the cpu dynamic divider, this divider needs
to have a flag set before setting the divider value then removed
while writing the new value to the register.

This drivers implements this behavior and will be used essentially
on the Amlogic G12A and G12B SoCs for cpu clock trees.

Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
Reviewed-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com>
Signed-off-by: Jerome Brunet <jbrunet@baylibre.com>
2019-08-09 12:10:03 +02:00

74 lines
2.2 KiB
C

// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Copyright (c) 2019 BayLibre, SAS.
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
#include <linux/clk-provider.h>
#include <linux/module.h>
#include "clk-regmap.h"
#include "clk-cpu-dyndiv.h"
static inline struct meson_clk_cpu_dyndiv_data *
meson_clk_cpu_dyndiv_data(struct clk_regmap *clk)
{
return (struct meson_clk_cpu_dyndiv_data *)clk->data;
}
static unsigned long meson_clk_cpu_dyndiv_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
return divider_recalc_rate(hw, prate,
meson_parm_read(clk->map, &data->div),
NULL, 0, data->div.width);
}
static long meson_clk_cpu_dyndiv_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
return divider_round_rate(hw, rate, prate, NULL, data->div.width, 0);
}
static int meson_clk_cpu_dyndiv_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyndiv_data *data = meson_clk_cpu_dyndiv_data(clk);
unsigned int val;
int ret;
ret = divider_get_val(rate, parent_rate, NULL, data->div.width, 0);
if (ret < 0)
return ret;
val = (unsigned int)ret << data->div.shift;
/* Write the SYS_CPU_DYN_ENABLE bit before changing the divider */
meson_parm_write(clk->map, &data->dyn, 1);
/* Update the divider while removing the SYS_CPU_DYN_ENABLE bit */
return regmap_update_bits(clk->map, data->div.reg_off,
SETPMASK(data->div.width, data->div.shift) |
SETPMASK(data->dyn.width, data->dyn.shift),
val);
};
const struct clk_ops meson_clk_cpu_dyndiv_ops = {
.recalc_rate = meson_clk_cpu_dyndiv_recalc_rate,
.round_rate = meson_clk_cpu_dyndiv_round_rate,
.set_rate = meson_clk_cpu_dyndiv_set_rate,
};
EXPORT_SYMBOL_GPL(meson_clk_cpu_dyndiv_ops);
MODULE_DESCRIPTION("Amlogic CPU Dynamic Clock divider");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("GPL v2");