mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-02-05 15:23:03 +08:00
279 lines
8.0 KiB
C
Executable File
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);
|
|
}
|