Files
rt-thread/bsp/rockchip/dm/clk/clk-rk-cpu.c
GuEe-GUI 45fe9ee96b [bsp][rockchip] Port to DM
RK3588/RK3576/RK356x/RK3308

Signed-off-by: GuEe-GUI <2991707448@qq.com>
2026-01-08 21:33:25 +08:00

279 lines
8.0 KiB
C
Executable File

/*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-3-08 GuEe-GUI the first version
*/
#include "clk-rk-cpu.h"
static rt_ubase_t rockchip_cpu_clk_recalc_rate(struct rt_clk_cell *cell, rt_ubase_t parent_rate)
{
void *base;
rt_uint32_t clksel0;
struct rockchip_cpu_clk_cell *cpu_clk_cell = cell_to_rockchip_cpu_cell(cell);
const struct rockchip_cpu_clk_reg_data *reg_data = cpu_clk_cell->reg_data;
base = cpu_clk_cell->rk_cell.provider->reg_base;
clksel0 = HWREG32(base + reg_data->core_reg[0]);
clksel0 >>= reg_data->div_core_shift[0];
clksel0 &= reg_data->div_core_mask[0];
return parent_rate / (clksel0 + 1);
}
const struct rt_clk_ops rockchip_cpu_clk_ops =
{
.recalc_rate = rockchip_cpu_clk_recalc_rate,
};
static const struct rockchip_cpu_clk_rate_table *rockchip_get_cpu_clk_settings(
struct rockchip_cpu_clk_cell *cpu_clk_cell, rt_ubase_t rate)
{
const struct rockchip_cpu_clk_rate_table *rate_table = cpu_clk_cell->rate_table;
for (int i = 0; i < cpu_clk_cell->rate_count; ++i)
{
if (rate == rate_table[i].prate)
{
return &rate_table[i];
}
}
return RT_NULL;
}
static void rockchip_cpu_clk_set_dividers(struct rockchip_cpu_clk_cell *cpu_clk_cell,
const struct rockchip_cpu_clk_rate_table *rate)
{
void *base = cpu_clk_cell->rk_cell.provider->reg_base;
/* Alternate parent is active now. set the dividers */
for (int i = 0; i < RT_ARRAY_SIZE(rate->divs); ++i)
{
const struct rockchip_cpu_clk_clksel *clksel = &rate->divs[i];
if (!clksel->reg)
{
continue;
}
HWREG32(base + clksel->reg) = clksel->val;
}
}
static void rockchip_cpu_clk_set_pre_muxs(struct rockchip_cpu_clk_cell *cpu_clk_cell,
const struct rockchip_cpu_clk_rate_table *rate)
{
void *base = cpu_clk_cell->rk_cell.provider->reg_base;
/* Alternate parent is active now. set the pre_muxs */
for (int i = 0; i < RT_ARRAY_SIZE(rate->pre_muxs); ++i)
{
const struct rockchip_cpu_clk_clksel *clksel = &rate->pre_muxs[i];
if (!clksel->reg)
{
break;
}
HWREG32(base + clksel->reg) = clksel->val;
}
}
static void rockchip_cpu_clk_set_post_muxs(struct rockchip_cpu_clk_cell *cpu_clk_cell,
const struct rockchip_cpu_clk_rate_table *rate)
{
void *base = cpu_clk_cell->rk_cell.provider->reg_base;
/* Alternate parent is active now. set the muxs */
for (int i = 0; i < RT_ARRAY_SIZE(rate->post_muxs); ++i)
{
const struct rockchip_cpu_clk_clksel *clksel = &rate->post_muxs[i];
if (!clksel->reg)
{
break;
}
HWREG32(base + clksel->reg) = clksel->val;
}
}
static int rockchip_cpu_clk_pre_rate_change(struct rockchip_cpu_clk_cell *cpu_clk_cell,
rt_ubase_t old_rate, rt_ubase_t new_rate)
{
rt_ubase_t alt_prate, alt_div;
void *base = cpu_clk_cell->rk_cell.provider->reg_base;
const struct rockchip_cpu_clk_reg_data *reg_data = cpu_clk_cell->reg_data;
const struct rockchip_cpu_clk_rate_table *rate;
/* Check validity of the new rate */
if (!(rate = rockchip_get_cpu_clk_settings(cpu_clk_cell, new_rate)))
{
return -RT_EINVAL;
}
alt_prate = rt_clk_cell_get_rate(&cpu_clk_cell->rk_cell_alt_parent->cell);
/*
* If the old parent clock speed is less than the clock speed
* of the alternate parent, then it should be ensured that at no point
* the armclk speed is more than the old_rate until the dividers are
* set.
*/
if (alt_prate > old_rate)
{
/* Calculate dividers */
alt_div = RT_DIV_ROUND_UP(alt_prate, old_rate) - 1;
if (alt_div > reg_data->div_core_mask[0])
{
alt_div = reg_data->div_core_mask[0];
}
/*
* Change parents and add dividers in a single transaction.
*
* NOTE: we do this in a single transaction so we're never
* dividing the primary parent by the extra dividers that were
* needed for the alt.
*/
for (int i = 0; i < reg_data->num_cores; ++i)
{
HWREG32(base + reg_data->core_reg[i]) = HIWORD_UPDATE(
alt_div,
reg_data->div_core_mask[i],
reg_data->div_core_shift[i]);
}
}
rockchip_cpu_clk_set_pre_muxs(cpu_clk_cell, rate);
/* select alternate parent */
if (reg_data->mux_core_reg)
{
HWREG32(base + reg_data->mux_core_reg) = HIWORD_UPDATE(
reg_data->mux_core_alt,
reg_data->mux_core_mask,
reg_data->mux_core_shift);
}
else
{
HWREG32(base + reg_data->core_reg[0]) = HIWORD_UPDATE(
reg_data->mux_core_alt,
reg_data->mux_core_mask,
reg_data->mux_core_shift);
}
return RT_EOK;
}
static rt_err_t rockchip_cpu_clk_post_rate_change(struct rockchip_cpu_clk_cell *cpu_clk_cell,
rt_ubase_t old_rate, rt_ubase_t new_rate)
{
void *base = cpu_clk_cell->rk_cell.provider->reg_base;
const struct rockchip_cpu_clk_rate_table *rate;
const struct rockchip_cpu_clk_reg_data *reg_data = cpu_clk_cell->reg_data;
if (!(rate = rockchip_get_cpu_clk_settings(cpu_clk_cell, new_rate)))
{
return -RT_EINVAL;
}
if (old_rate < new_rate)
{
rockchip_cpu_clk_set_dividers(cpu_clk_cell, rate);
}
/*
* post-rate change event, re-mux to primary parent and remove dividers.
*
* NOTE: we do this in a single transaction so we're never dividing the
* primary parent by the extra dividers that were needed for the alt.
*/
if (reg_data->mux_core_reg)
{
HWREG32(base + reg_data->mux_core_reg) = HIWORD_UPDATE(
reg_data->mux_core_main,
reg_data->mux_core_mask,
reg_data->mux_core_shift);
}
else
{
HWREG32(base + reg_data->core_reg[0]) = HIWORD_UPDATE(
reg_data->mux_core_main,
reg_data->mux_core_mask,
reg_data->mux_core_shift);
}
rockchip_cpu_clk_set_post_muxs(cpu_clk_cell, rate);
/* Remove dividers */
for (int i = 0; i < reg_data->num_cores; ++i)
{
HWREG32(base + reg_data->core_reg[i]) = HIWORD_UPDATE(
0,
reg_data->div_core_mask[i],
reg_data->div_core_shift[i]);
}
if (old_rate > new_rate)
{
rockchip_cpu_clk_set_dividers(cpu_clk_cell, rate);
}
return RT_EOK;
}
static rt_err_t rockchip_cpu_clk_notify(struct rt_clk_notifier *notifier,
rt_ubase_t msg, rt_ubase_t old_rate, rt_ubase_t new_rate)
{
rt_err_t err = RT_EOK;
struct rockchip_cpu_clk_cell *cpu_cell;
cpu_cell = rt_container_of(notifier, struct rockchip_cpu_clk_cell, notifier);
if (msg == RT_CLK_MSG_PRE_RATE_CHANGE)
{
err = rockchip_cpu_clk_pre_rate_change(cpu_cell, old_rate, new_rate);
}
else if (msg == RT_CLK_MSG_POST_RATE_CHANGE)
{
err = rockchip_cpu_clk_post_rate_change(cpu_cell, old_rate, new_rate);
}
return err;
}
void rockchip_cpu_clk_cell_init(struct rockchip_clk_cell *rk_cell)
{
struct rockchip_cpu_clk_cell *cpu_clk_cell = cell_to_rockchip_cpu_cell(&rk_cell->cell);
rk_cell->cell.parents_nr = 1;
rk_cell->cell.parent_name = cpu_clk_cell->rk_cell_parent->cell.name;
if (cpu_clk_cell->rate_count > 0)
{
rk_cell->cell.flags |= RT_CLK_F_SET_RATE_PARENT;
}
}
void rockchip_cpu_clk_cell_setup(struct rockchip_clk_cell *rk_cell)
{
struct rockchip_cpu_clk_cell *cpu_clk_cell = cell_to_rockchip_cpu_cell(&rk_cell->cell);
rt_clk_prepare_enable(rt_clk_cell_get_clk(&cpu_clk_cell->rk_cell_alt_parent->cell, RT_NULL));
cpu_clk_cell->notifier.callback = rockchip_cpu_clk_notify;
rt_clk_notifier_register(rt_clk_cell_get_clk(&cpu_clk_cell->rk_cell_parent->cell, RT_NULL),
&cpu_clk_cell->notifier);
}