Files
2026-08-24 10:03:24 +08:00

299 lines
7.9 KiB
C

// SPDX-License-Identifier: (GPL-2.0 OR MIT)
/*
* Copyright (c) 2019 BayLibre, SAS.
* Author: Neil Armstrong <[email protected]>
*/
#include <linux/clk.h>
#include <linux/module.h>
#include <linux/amlogic/arm-smccc.h>
#include "clk-regmap.h"
#include "clk-cpu-dyndiv.h"
#define CPU_DYN_SEL_MASK BIT(10)
#define SYS_CLK_SEL_MASK BIT(15)
static inline struct meson_clk_cpu_dyn_data *
meson_clk_cpu_dyn_data(struct clk_regmap *clk)
{
return (struct meson_clk_cpu_dyn_data *)clk->data;
}
static unsigned long meson_clk_cpu_dyn_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
unsigned int val, pindex, div;
unsigned long rate, nprate = 0;
struct arm_smccc_res res;
/* get cpu register value */
if (data->smc_id) {
arm_smccc_smc(data->smc_id, data->secid_dyn_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
} else {
regmap_read(clk->map, data->offset, &val);
}
/* 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_clk_cpu_dyn_round_rate(struct clk_hw *hw,
unsigned long rate,
unsigned long *prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_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_cpu_dyn_set(struct clk_hw *hw, u16 dyn_pre_mux, u16 dyn_post_mux, u16 dyn_div)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
unsigned int control;
unsigned int cnt = 0;
// Make sure not busy from last setting and we currently match the last setting
do {
regmap_read(clk->map, data->offset, &control);
udelay(1);
cnt++;
if (cnt > 100) {
pr_err("cpu fsm is busy, offset=0x%x\n", data->offset);
break;
}
} while ((control & (1 << 28)));
control = control | (1 << 26);// Enable
if (control & (1 << 10)) { // if using Dyn mux1, set dyn mux 0
// Toggle bit[10] indicating a dynamic mux change
control = (control & (~((1 << 10) | (0x3f << 4) | (1 << 2) | (0x3 << 0))))
| ((0 << 10)
| (dyn_div << 4)
| (dyn_post_mux << 2)
| (dyn_pre_mux << 0));
} else {
// Toggle bit[10] indicating a dynamic mux change
control = (control & (~((1 << 10) | (0x3f << 20) | (1 << 18) | (0x3 << 16))))
| ((1 << 10)
| (dyn_div << 20)
| (dyn_post_mux << 18)
| (dyn_pre_mux << 16));
}
/* Do not touch bit 11, it belongs to the CPU mux clk */
regmap_write(clk->map, data->offset, control);
return 0;
}
static int meson_clk_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_clk_cpu_dyn_data *data = meson_clk_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];
break;
}
}
nrate = table->rate;
/* For set more than 1G, need to set additional parent frequency */
if (nrate > 1000000000 && !strcmp(clk_hw_get_name(hw), "dsu_dyn_clk")) {
if (clk_get_rate(hw->clk) > 1000000000) {
/* switch dsu to fix div2 */
if (data->smc_id)
arm_smccc_smc(data->smc_id, data->secid_dyn,
1, 0, 0, 0, 0, 0, &res);
else
meson_cpu_dyn_set(hw, 1, 0, 0);
/* udelay(50); */
}
clk_set_rate(clk_hw_get_parent_by_index(hw, 3)->clk, nrate);
}
if (data->smc_id)
arm_smccc_smc(data->smc_id, data->secid_dyn,
table->dyn_pre_mux, table->dyn_post_mux, table->dyn_div,
0, 0, 0, &res);
else
meson_cpu_dyn_set(hw, table->dyn_pre_mux, table->dyn_post_mux, table->dyn_div);
return 0;
}
static u8 meson_clk_cpu_dyn_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
u32 pre_shift, val;
struct arm_smccc_res res;
if (data->smc_id) {
arm_smccc_smc(data->smc_id, data->secid_dyn_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
} else {
regmap_read(clk->map, data->offset, &val);
}
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;
}
/* set the cpu fixed clk as one level clk */
const struct clk_ops meson_clk_cpu_dyn_ops = {
.recalc_rate = meson_clk_cpu_dyn_recalc_rate,
.round_rate = meson_clk_cpu_dyn_round_rate,
.set_rate = meson_clk_cpu_dyn_set_rate,
.get_parent = meson_clk_cpu_dyn_get_parent
};
EXPORT_SYMBOL_GPL(meson_clk_cpu_dyn_ops);
static u8 meson_sec_sys_clk_get_parent(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
u32 pre_shift, val;
struct arm_smccc_res res;
arm_smccc_smc(data->smc_id, data->secid_dyn_rd,
0, 0, 0, 0, 0, 0, &res);
val = res.a0;
if (val & SYS_CLK_SEL_MASK)
pre_shift = 26;
else
pre_shift = 10;
val = val >> pre_shift;
val &= 0x7;
if (val >= clk_hw_get_num_parents(hw))
return -EINVAL;
return val;
}
static unsigned long meson_sec_sys_clk_recalc_rate(struct clk_hw *hw,
unsigned long prate)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
unsigned int val, pindex, div;
unsigned long nprate = 0;
struct arm_smccc_res res;
/* get cpu register value */
arm_smccc_smc(data->smc_id, 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 & SYS_CLK_SEL_MASK) {
pindex = (val >> 26) & 0x7;
nprate = clk_hw_get_rate(clk_hw_get_parent_by_index(hw, pindex));
div = (val >> 16) & 0x3ff;
} else {
/* Get parent rate */
pindex = (val >> 10) & 0x7;
nprate = clk_hw_get_rate(clk_hw_get_parent_by_index(hw, pindex));
div = (val >> 0) & 0x3ff;
}
return DIV_ROUND_UP_ULL((u64)nprate, div + 1);
}
static int meson_sec_sys_clk_save_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
data->saved_rate = meson_sec_sys_clk_recalc_rate(hw,
clk_hw_get_rate(clk_hw_get_parent(hw)));
return 0;
}
static void meson_sec_sys_clk_restore_context(struct clk_hw *hw)
{
struct clk_regmap *clk = to_clk_regmap(hw);
struct meson_clk_cpu_dyn_data *data = meson_clk_cpu_dyn_data(clk);
meson_clk_cpu_dyn_set_rate(hw, data->saved_rate,
clk_hw_get_rate(clk_hw_get_parent(hw)));
}
const struct clk_ops meson_sec_sys_clk_ops = {
.recalc_rate = meson_sec_sys_clk_recalc_rate,
.round_rate = meson_clk_cpu_dyn_round_rate,
.set_rate = meson_clk_cpu_dyn_set_rate,
.get_parent = meson_sec_sys_clk_get_parent,
.save_context = meson_sec_sys_clk_save_context,
.restore_context = meson_sec_sys_clk_restore_context
};
EXPORT_SYMBOL_GPL(meson_sec_sys_clk_ops);
MODULE_DESCRIPTION("Amlogic CPU Dynamic Clock divider");
MODULE_AUTHOR("Neil Armstrong <[email protected]>");
MODULE_LICENSE("GPL v2");