blob: 310ecec98a0910f95ca06eadc06e40097f4d8e18 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Copyright (c) 2019 BayLibre, SAS.
* Author: Neil Armstrong <narmstrong@baylibre.com>
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/module.h>
#include <linux/arm-smccc.h>
#include "clk-regmap.h"
#include "clk-cpu-dyndiv.h"
#define CPU_DYN_SEL_MASK BIT(10)
#define SECURE_CPU_CLK 0x82000099
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 inline struct meson_sec_cpu_dyn_data *
meson_sec_cpu_dyn_data(struct clk_regmap *clk)
{
return (struct meson_sec_cpu_dyn_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);
static unsigned long meson_sec_cpu_dyn_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_sec_cpu_dyn_data *data = meson_sec_cpu_dyn_data(clk);
unsigned int val, pindex, div;
unsigned long rate, nprate = 0;
struct arm_smccc_res res;
/* get cpu register value */
arm_smccc_smc(SECURE_CPU_CLK, data->secid_dyn_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
/* Confirm Now cpu is on final0 or final1 , bit10 = 0 or 1 */
if (val & CPU_DYN_SEL_MASK) {
pindex = (val >> 16) & 0x3;
nprate = clk_hw_get_rate(clk_hw_get_parent_by_index(hw, pindex));
if ((val >> 18) & 0x1) {
div = (val >> 20) & 0x3f;
rate = DIV_ROUND_UP_ULL((u64)nprate, div + 1);
} else {
rate = nprate;
}
} else {
/* Get parent rate */
pindex = val & 0x3;
nprate = clk_hw_get_rate(clk_hw_get_parent_by_index(hw, pindex));
if ((val >> 2) & 0x1) {
div = (val >> 4) & 0x3f;
rate = DIV_ROUND_UP_ULL((u64)nprate, div + 1);
} else {
rate = nprate;
}
}
return rate;
}
/* find the best rate near to target rate */
static long meson_sec_cpu_dyn_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_sec_cpu_dyn_data *data = meson_sec_cpu_dyn_data(clk);
struct cpu_dyn_table *table = (struct cpu_dyn_table *)data->table;
unsigned long min, max;
unsigned int i, cnt = data->table_cnt;
min = table[0].rate;
max = table[cnt - 1].rate;
if (rate < min)
return min;
if (rate > max)
return max;
for (i = 0; i < data->table_cnt; i++) {
if (rate <= table[i].rate)
return table[i].rate;
}
return min;
}
static int meson_sec_cpu_dyn_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_sec_cpu_dyn_data *data = meson_sec_cpu_dyn_data(clk);
struct cpu_dyn_table *table = (struct cpu_dyn_table *)data->table;
struct arm_smccc_res res;
unsigned int nrate, i;
for (i = 0; i < data->table_cnt; i++) {
if (rate == table[i].rate)
table = &table[i];
}
nrate = table->rate;
/* For set more than 1G, need to set additional parent frequency */
if (!strcmp(clk_hw_get_name(hw), "dsu_dyn_clk") && nrate > 1000000000) {
if (clk_get_rate(hw->clk) > 1000000000) {
/* switch dsu to fix div2 */
arm_smccc_smc(SECURE_CPU_CLK, data->secid_dyn,
1, 0, 0, 0, 0, 0, &res);
/* udelay(50); */
}
clk_set_rate(clk_hw_get_parent_by_index(hw, 3)->clk, nrate);
}
arm_smccc_smc(SECURE_CPU_CLK, data->secid_dyn,
table->dyn_pre_mux, table->dyn_post_mux, table->dyn_div,
0, 0, 0, &res);
return 0;
}
static u8 meson_sec_cpu_dyn_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_sec_cpu_dyn_data *data = meson_sec_cpu_dyn_data(clk);
u32 pre_shift, val;
struct arm_smccc_res res;
arm_smccc_smc(SECURE_CPU_CLK, data->secid_dyn_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
if (val & CPU_DYN_SEL_MASK)
pre_shift = 16;
else
pre_shift = 0;
val = val >> pre_shift;
val &= 0x3;
if (val >= clk_hw_get_num_parents(hw))
return -EINVAL;
return val;
}
/*recalc the rate in kernel, And set the cpu fixed clk as one level clk
* due to everything is doing in bl31
*/
const struct clk_ops meson_sec_cpu_dyn_ops = {
.recalc_rate = meson_sec_cpu_dyn_recalc_rate,
.round_rate = meson_sec_cpu_dyn_round_rate,
.set_rate = meson_sec_cpu_dyn_set_rate,
.get_parent = meson_sec_cpu_dyn_get_parent
};
EXPORT_SYMBOL_GPL(meson_sec_cpu_dyn_ops);
MODULE_DESCRIPTION("Amlogic CPU Dynamic Clock divider");
MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
MODULE_LICENSE("GPL v2");