mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-08-05 16:54:27 +00:00
clk: spacemit: Add clock support for SpacemiT K1 SoC
The clock tree of K1 SoC contains three main types of clock hardware (PLL/DDN/MIX) and has control registers split into several multifunction devices: APBS (PLLs), MPMU, APBC and APMU. All register operations are done through regmap to ensure atomicity between concurrent operations of clock driver and reset, power-domain driver that will be introduced in the future. Signed-off-by: Haylen Chu <heylenay@4d2.org> Reviewed-by: Alex Elder <elder@riscstar.com> Reviewed-by: Inochi Amaoto <inochiama@outlook.com> Reviewed-by: Yixun Lan <dlan@gentoo.org> Link: https://lore.kernel.org/r/20250416135406.16284-4-heylenay@4d2.org Signed-off-by: Yixun Lan <dlan@gentoo.org>
This commit is contained in:
parent
8090804045
commit
1b72c59db0
12 changed files with 2087 additions and 0 deletions
|
@ -517,6 +517,7 @@ source "drivers/clk/samsung/Kconfig"
|
||||||
source "drivers/clk/sifive/Kconfig"
|
source "drivers/clk/sifive/Kconfig"
|
||||||
source "drivers/clk/socfpga/Kconfig"
|
source "drivers/clk/socfpga/Kconfig"
|
||||||
source "drivers/clk/sophgo/Kconfig"
|
source "drivers/clk/sophgo/Kconfig"
|
||||||
|
source "drivers/clk/spacemit/Kconfig"
|
||||||
source "drivers/clk/sprd/Kconfig"
|
source "drivers/clk/sprd/Kconfig"
|
||||||
source "drivers/clk/starfive/Kconfig"
|
source "drivers/clk/starfive/Kconfig"
|
||||||
source "drivers/clk/sunxi/Kconfig"
|
source "drivers/clk/sunxi/Kconfig"
|
||||||
|
|
|
@ -145,6 +145,7 @@ obj-$(CONFIG_COMMON_CLK_SAMSUNG) += samsung/
|
||||||
obj-$(CONFIG_CLK_SIFIVE) += sifive/
|
obj-$(CONFIG_CLK_SIFIVE) += sifive/
|
||||||
obj-y += socfpga/
|
obj-y += socfpga/
|
||||||
obj-y += sophgo/
|
obj-y += sophgo/
|
||||||
|
obj-y += spacemit/
|
||||||
obj-$(CONFIG_PLAT_SPEAR) += spear/
|
obj-$(CONFIG_PLAT_SPEAR) += spear/
|
||||||
obj-y += sprd/
|
obj-y += sprd/
|
||||||
obj-$(CONFIG_ARCH_STI) += st/
|
obj-$(CONFIG_ARCH_STI) += st/
|
||||||
|
|
18
drivers/clk/spacemit/Kconfig
Normal file
18
drivers/clk/spacemit/Kconfig
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
|
||||||
|
config SPACEMIT_CCU
|
||||||
|
tristate "Clock support for SpacemiT SoCs"
|
||||||
|
depends on ARCH_SPACEMIT || COMPILE_TEST
|
||||||
|
select MFD_SYSCON
|
||||||
|
help
|
||||||
|
Say Y to enable clock controller unit support for SpacemiT SoCs.
|
||||||
|
|
||||||
|
if SPACEMIT_CCU
|
||||||
|
|
||||||
|
config SPACEMIT_K1_CCU
|
||||||
|
tristate "Support for SpacemiT K1 SoC"
|
||||||
|
depends on ARCH_SPACEMIT || COMPILE_TEST
|
||||||
|
help
|
||||||
|
Support for clock controller unit in SpacemiT K1 SoC.
|
||||||
|
|
||||||
|
endif
|
5
drivers/clk/spacemit/Makefile
Normal file
5
drivers/clk/spacemit/Makefile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
||||||
|
obj-$(CONFIG_SPACEMIT_K1_CCU) = spacemit-ccu-k1.o
|
||||||
|
spacemit-ccu-k1-y = ccu_pll.o ccu_mix.o ccu_ddn.o
|
||||||
|
spacemit-ccu-k1-y += ccu-k1.o
|
1154
drivers/clk/spacemit/ccu-k1.c
Normal file
1154
drivers/clk/spacemit/ccu-k1.c
Normal file
File diff suppressed because it is too large
Load diff
48
drivers/clk/spacemit/ccu_common.h
Normal file
48
drivers/clk/spacemit/ccu_common.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CCU_COMMON_H_
|
||||||
|
#define _CCU_COMMON_H_
|
||||||
|
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
struct ccu_common {
|
||||||
|
struct regmap *regmap;
|
||||||
|
struct regmap *lock_regmap;
|
||||||
|
|
||||||
|
union {
|
||||||
|
/* For DDN and MIX */
|
||||||
|
struct {
|
||||||
|
u32 reg_ctrl;
|
||||||
|
u32 reg_fc;
|
||||||
|
u32 mask_fc;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* For PLL */
|
||||||
|
struct {
|
||||||
|
u32 reg_swcr1;
|
||||||
|
u32 reg_swcr3;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct clk_hw hw;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
return container_of(hw, struct ccu_common, hw);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ccu_read(c, reg) \
|
||||||
|
({ \
|
||||||
|
u32 tmp; \
|
||||||
|
regmap_read((c)->regmap, (c)->reg_##reg, &tmp); \
|
||||||
|
tmp; \
|
||||||
|
})
|
||||||
|
#define ccu_update(c, reg, mask, val) \
|
||||||
|
regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val)
|
||||||
|
|
||||||
|
#endif /* _CCU_COMMON_H_ */
|
83
drivers/clk/spacemit/ccu_ddn.c
Normal file
83
drivers/clk/spacemit/ccu_ddn.c
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*
|
||||||
|
* DDN stands for "Divider Denominator Numerator", it's M/N clock with a
|
||||||
|
* constant x2 factor. This clock hardware follows the equation below,
|
||||||
|
*
|
||||||
|
* numerator Fin
|
||||||
|
* 2 * ------------- = -------
|
||||||
|
* denominator Fout
|
||||||
|
*
|
||||||
|
* Thus, Fout could be calculated with,
|
||||||
|
*
|
||||||
|
* Fin denominator
|
||||||
|
* Fout = ----- * -------------
|
||||||
|
* 2 numerator
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
#include <linux/rational.h>
|
||||||
|
|
||||||
|
#include "ccu_ddn.h"
|
||||||
|
|
||||||
|
static unsigned long ccu_ddn_calc_rate(unsigned long prate,
|
||||||
|
unsigned long num, unsigned long den)
|
||||||
|
{
|
||||||
|
return prate * den / 2 / num;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ccu_ddn_calc_best_rate(struct ccu_ddn *ddn,
|
||||||
|
unsigned long rate, unsigned long prate,
|
||||||
|
unsigned long *num, unsigned long *den)
|
||||||
|
{
|
||||||
|
rational_best_approximation(rate, prate / 2,
|
||||||
|
ddn->den_mask >> ddn->den_shift,
|
||||||
|
ddn->num_mask >> ddn->num_shift,
|
||||||
|
den, num);
|
||||||
|
return ccu_ddn_calc_rate(prate, *num, *den);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long ccu_ddn_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long *prate)
|
||||||
|
{
|
||||||
|
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
|
||||||
|
unsigned long num, den;
|
||||||
|
|
||||||
|
return ccu_ddn_calc_best_rate(ddn, rate, *prate, &num, &den);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ccu_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate)
|
||||||
|
{
|
||||||
|
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
|
||||||
|
unsigned int val, num, den;
|
||||||
|
|
||||||
|
val = ccu_read(&ddn->common, ctrl);
|
||||||
|
|
||||||
|
num = (val & ddn->num_mask) >> ddn->num_shift;
|
||||||
|
den = (val & ddn->den_mask) >> ddn->den_shift;
|
||||||
|
|
||||||
|
return ccu_ddn_calc_rate(prate, num, den);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_ddn_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long prate)
|
||||||
|
{
|
||||||
|
struct ccu_ddn *ddn = hw_to_ccu_ddn(hw);
|
||||||
|
unsigned long num, den;
|
||||||
|
|
||||||
|
ccu_ddn_calc_best_rate(ddn, rate, prate, &num, &den);
|
||||||
|
|
||||||
|
ccu_update(&ddn->common, ctrl,
|
||||||
|
ddn->num_mask | ddn->den_mask,
|
||||||
|
(num << ddn->num_shift) | (den << ddn->den_shift));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_ddn_ops = {
|
||||||
|
.recalc_rate = ccu_ddn_recalc_rate,
|
||||||
|
.round_rate = ccu_ddn_round_rate,
|
||||||
|
.set_rate = ccu_ddn_set_rate,
|
||||||
|
};
|
48
drivers/clk/spacemit/ccu_ddn.h
Normal file
48
drivers/clk/spacemit/ccu_ddn.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CCU_DDN_H_
|
||||||
|
#define _CCU_DDN_H_
|
||||||
|
|
||||||
|
#include <linux/bitops.h>
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
|
||||||
|
#include "ccu_common.h"
|
||||||
|
|
||||||
|
struct ccu_ddn {
|
||||||
|
struct ccu_common common;
|
||||||
|
unsigned int num_mask;
|
||||||
|
unsigned int num_shift;
|
||||||
|
unsigned int den_mask;
|
||||||
|
unsigned int den_shift;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CCU_DDN_INIT(_name, _parent, _flags) \
|
||||||
|
CLK_HW_INIT_HW(#_name, &_parent.common.hw, &spacemit_ccu_ddn_ops, _flags)
|
||||||
|
|
||||||
|
#define CCU_DDN_DEFINE(_name, _parent, _reg_ctrl, _num_shift, _num_width, \
|
||||||
|
_den_shift, _den_width, _flags) \
|
||||||
|
static struct ccu_ddn _name = { \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
.hw.init = CCU_DDN_INIT(_name, _parent, _flags), \
|
||||||
|
}, \
|
||||||
|
.num_mask = GENMASK(_num_shift + _num_width - 1, _num_shift), \
|
||||||
|
.num_shift = _num_shift, \
|
||||||
|
.den_mask = GENMASK(_den_shift + _den_width - 1, _den_shift), \
|
||||||
|
.den_shift = _den_shift, \
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct ccu_ddn *hw_to_ccu_ddn(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
|
||||||
|
return container_of(common, struct ccu_ddn, common);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const struct clk_ops spacemit_ccu_ddn_ops;
|
||||||
|
|
||||||
|
#endif
|
268
drivers/clk/spacemit/ccu_mix.c
Normal file
268
drivers/clk/spacemit/ccu_mix.c
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*
|
||||||
|
* MIX clock type is the combination of mux, factor or divider, and gate
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
|
||||||
|
#include "ccu_mix.h"
|
||||||
|
|
||||||
|
#define MIX_FC_TIMEOUT_US 10000
|
||||||
|
#define MIX_FC_DELAY_US 5
|
||||||
|
|
||||||
|
static void ccu_gate_disable(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
|
||||||
|
ccu_update(&mix->common, ctrl, mix->gate.mask, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_gate_enable(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_gate_config *gate = &mix->gate;
|
||||||
|
|
||||||
|
ccu_update(&mix->common, ctrl, gate->mask, gate->mask);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_gate_is_enabled(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_gate_config *gate = &mix->gate;
|
||||||
|
|
||||||
|
return (ccu_read(&mix->common, ctrl) & gate->mask) == gate->mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ccu_factor_recalc_rate(struct clk_hw *hw,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
|
||||||
|
return parent_rate * mix->factor.mul / mix->factor.div;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_div_config *div = &mix->div;
|
||||||
|
unsigned long val;
|
||||||
|
|
||||||
|
val = ccu_read(&mix->common, ctrl) >> div->shift;
|
||||||
|
val &= (1 << div->width) - 1;
|
||||||
|
|
||||||
|
return divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some clocks require a "FC" (frequency change) bit to be set after changing
|
||||||
|
* their rates or reparenting. This bit will be automatically cleared by
|
||||||
|
* hardware in MIX_FC_TIMEOUT_US, which indicates the operation is completed.
|
||||||
|
*/
|
||||||
|
static int ccu_mix_trigger_fc(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
unsigned int val;
|
||||||
|
|
||||||
|
if (common->reg_fc)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ccu_update(common, fc, common->mask_fc, common->mask_fc);
|
||||||
|
|
||||||
|
return regmap_read_poll_timeout_atomic(common->regmap, common->reg_fc,
|
||||||
|
val, !(val & common->mask_fc),
|
||||||
|
MIX_FC_DELAY_US,
|
||||||
|
MIX_FC_TIMEOUT_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
static long ccu_factor_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long *prate)
|
||||||
|
{
|
||||||
|
return ccu_factor_recalc_rate(hw, *prate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_factor_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long
|
||||||
|
ccu_mix_calc_best_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
struct clk_hw **best_parent,
|
||||||
|
unsigned long *best_parent_rate,
|
||||||
|
u32 *div_val)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
unsigned int parent_num = clk_hw_get_num_parents(hw);
|
||||||
|
struct ccu_div_config *div = &mix->div;
|
||||||
|
u32 div_max = 1 << div->width;
|
||||||
|
unsigned long best_rate = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < parent_num; i++) {
|
||||||
|
struct clk_hw *parent = clk_hw_get_parent_by_index(hw, i);
|
||||||
|
unsigned long parent_rate;
|
||||||
|
|
||||||
|
if (!parent)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
parent_rate = clk_hw_get_rate(parent);
|
||||||
|
|
||||||
|
for (int j = 1; j <= div_max; j++) {
|
||||||
|
unsigned long tmp = DIV_ROUND_CLOSEST_ULL(parent_rate, j);
|
||||||
|
|
||||||
|
if (abs(tmp - rate) < abs(best_rate - rate)) {
|
||||||
|
best_rate = tmp;
|
||||||
|
|
||||||
|
if (div_val)
|
||||||
|
*div_val = j - 1;
|
||||||
|
|
||||||
|
if (best_parent) {
|
||||||
|
*best_parent = parent;
|
||||||
|
*best_parent_rate = parent_rate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_mix_determine_rate(struct clk_hw *hw,
|
||||||
|
struct clk_rate_request *req)
|
||||||
|
{
|
||||||
|
req->rate = ccu_mix_calc_best_rate(hw, req->rate,
|
||||||
|
&req->best_parent_hw,
|
||||||
|
&req->best_parent_rate,
|
||||||
|
NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_mix_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_common *common = &mix->common;
|
||||||
|
struct ccu_div_config *div = &mix->div;
|
||||||
|
u32 current_div, target_div, mask;
|
||||||
|
|
||||||
|
ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div);
|
||||||
|
|
||||||
|
current_div = ccu_read(common, ctrl) >> div->shift;
|
||||||
|
current_div &= (1 << div->width) - 1;
|
||||||
|
|
||||||
|
if (current_div == target_div)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mask = GENMASK(div->width + div->shift - 1, div->shift);
|
||||||
|
|
||||||
|
ccu_update(common, ctrl, mask, target_div << div->shift);
|
||||||
|
|
||||||
|
return ccu_mix_trigger_fc(hw);
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 ccu_mux_get_parent(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_mux_config *mux = &mix->mux;
|
||||||
|
u8 parent;
|
||||||
|
|
||||||
|
parent = ccu_read(&mix->common, ctrl) >> mux->shift;
|
||||||
|
parent &= (1 << mux->width) - 1;
|
||||||
|
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
|
||||||
|
{
|
||||||
|
struct ccu_mix *mix = hw_to_ccu_mix(hw);
|
||||||
|
struct ccu_mux_config *mux = &mix->mux;
|
||||||
|
u32 mask;
|
||||||
|
|
||||||
|
mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
|
||||||
|
|
||||||
|
ccu_update(&mix->common, ctrl, mask, index << mux->shift);
|
||||||
|
|
||||||
|
return ccu_mix_trigger_fc(hw);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_gate_ops = {
|
||||||
|
.disable = ccu_gate_disable,
|
||||||
|
.enable = ccu_gate_enable,
|
||||||
|
.is_enabled = ccu_gate_is_enabled,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_factor_ops = {
|
||||||
|
.round_rate = ccu_factor_round_rate,
|
||||||
|
.recalc_rate = ccu_factor_recalc_rate,
|
||||||
|
.set_rate = ccu_factor_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_mux_ops = {
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.get_parent = ccu_mux_get_parent,
|
||||||
|
.set_parent = ccu_mux_set_parent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_div_ops = {
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.recalc_rate = ccu_div_recalc_rate,
|
||||||
|
.set_rate = ccu_mix_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_factor_gate_ops = {
|
||||||
|
.disable = ccu_gate_disable,
|
||||||
|
.enable = ccu_gate_enable,
|
||||||
|
.is_enabled = ccu_gate_is_enabled,
|
||||||
|
|
||||||
|
.round_rate = ccu_factor_round_rate,
|
||||||
|
.recalc_rate = ccu_factor_recalc_rate,
|
||||||
|
.set_rate = ccu_factor_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_mux_gate_ops = {
|
||||||
|
.disable = ccu_gate_disable,
|
||||||
|
.enable = ccu_gate_enable,
|
||||||
|
.is_enabled = ccu_gate_is_enabled,
|
||||||
|
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.get_parent = ccu_mux_get_parent,
|
||||||
|
.set_parent = ccu_mux_set_parent,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_div_gate_ops = {
|
||||||
|
.disable = ccu_gate_disable,
|
||||||
|
.enable = ccu_gate_enable,
|
||||||
|
.is_enabled = ccu_gate_is_enabled,
|
||||||
|
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.recalc_rate = ccu_div_recalc_rate,
|
||||||
|
.set_rate = ccu_mix_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_mux_div_gate_ops = {
|
||||||
|
.disable = ccu_gate_disable,
|
||||||
|
.enable = ccu_gate_enable,
|
||||||
|
.is_enabled = ccu_gate_is_enabled,
|
||||||
|
|
||||||
|
.get_parent = ccu_mux_get_parent,
|
||||||
|
.set_parent = ccu_mux_set_parent,
|
||||||
|
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.recalc_rate = ccu_div_recalc_rate,
|
||||||
|
.set_rate = ccu_mix_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_mux_div_ops = {
|
||||||
|
.get_parent = ccu_mux_get_parent,
|
||||||
|
.set_parent = ccu_mux_set_parent,
|
||||||
|
|
||||||
|
.determine_rate = ccu_mix_determine_rate,
|
||||||
|
.recalc_rate = ccu_div_recalc_rate,
|
||||||
|
.set_rate = ccu_mix_set_rate,
|
||||||
|
};
|
218
drivers/clk/spacemit/ccu_mix.h
Normal file
218
drivers/clk/spacemit/ccu_mix.h
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CCU_MIX_H_
|
||||||
|
#define _CCU_MIX_H_
|
||||||
|
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
|
||||||
|
#include "ccu_common.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct ccu_gate_config - Gate configuration
|
||||||
|
*
|
||||||
|
* @mask: Mask to enable the gate. Some clocks may have more than one bit
|
||||||
|
* set in this field.
|
||||||
|
*/
|
||||||
|
struct ccu_gate_config {
|
||||||
|
u32 mask;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ccu_factor_config {
|
||||||
|
u32 div;
|
||||||
|
u32 mul;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ccu_mux_config {
|
||||||
|
u8 shift;
|
||||||
|
u8 width;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ccu_div_config {
|
||||||
|
u8 shift;
|
||||||
|
u8 width;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ccu_mix {
|
||||||
|
struct ccu_factor_config factor;
|
||||||
|
struct ccu_gate_config gate;
|
||||||
|
struct ccu_div_config div;
|
||||||
|
struct ccu_mux_config mux;
|
||||||
|
struct ccu_common common;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CCU_GATE_INIT(_mask) { .mask = _mask }
|
||||||
|
#define CCU_FACTOR_INIT(_div, _mul) { .div = _div, .mul = _mul }
|
||||||
|
#define CCU_MUX_INIT(_shift, _width) { .shift = _shift, .width = _width }
|
||||||
|
#define CCU_DIV_INIT(_shift, _width) { .shift = _shift, .width = _width }
|
||||||
|
|
||||||
|
#define CCU_PARENT_HW(_parent) { .hw = &_parent.common.hw }
|
||||||
|
#define CCU_PARENT_NAME(_name) { .fw_name = #_name }
|
||||||
|
|
||||||
|
#define CCU_MIX_INITHW(_name, _parent, _ops, _flags) \
|
||||||
|
.hw.init = &(struct clk_init_data) { \
|
||||||
|
.flags = _flags, \
|
||||||
|
.name = #_name, \
|
||||||
|
.parent_data = (const struct clk_parent_data[]) \
|
||||||
|
{ _parent }, \
|
||||||
|
.num_parents = 1, \
|
||||||
|
.ops = &_ops, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MIX_INITHW_PARENTS(_name, _parents, _ops, _flags) \
|
||||||
|
.hw.init = CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags)
|
||||||
|
|
||||||
|
#define CCU_GATE_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_gate_ops, _flags), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.factor = CCU_FACTOR_INIT(_div, _mul), \
|
||||||
|
.common = { \
|
||||||
|
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_factor_ops, 0), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_DEFINE(_name, _parents, _reg_ctrl, _shift, _width, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.mux = CCU_MUX_INIT(_shift, _width), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, spacemit_ccu_mux_ops, \
|
||||||
|
_flags), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_DIV_DEFINE(_name, _parent, _reg_ctrl, _shift, _width, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.div = CCU_DIV_INIT(_shift, _width), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_div_ops, _flags) \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_FACTOR_GATE_DEFINE(_name, _parent, _reg_ctrl, _mask_gate, _div, \
|
||||||
|
_mul) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.factor = CCU_FACTOR_INIT(_div, _mul), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_factor_gate_ops, 0) \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _shift, _width, \
|
||||||
|
_mask_gate, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.mux = CCU_MUX_INIT(_shift, _width), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, \
|
||||||
|
spacemit_ccu_mux_gate_ops, _flags), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_DIV_GATE_DEFINE(_name, _parent, _reg_ctrl, _shift, _width, \
|
||||||
|
_mask_gate, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.div = CCU_DIV_INIT(_shift, _width), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW(_name, _parent, spacemit_ccu_div_gate_ops, \
|
||||||
|
_flags), \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_DIV_GATE_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth, \
|
||||||
|
_muxshift, _muxwidth, _mask_gate, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.div = CCU_DIV_INIT(_mshift, _mwidth), \
|
||||||
|
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, \
|
||||||
|
spacemit_ccu_mux_div_gate_ops, _flags), \
|
||||||
|
}, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_DIV_GATE_SPLIT_FC_DEFINE(_name, _parents, _reg_ctrl, _reg_fc, \
|
||||||
|
_mshift, _mwidth, _mask_fc, _muxshift, \
|
||||||
|
_muxwidth, _mask_gate, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||||
|
.div = CCU_DIV_INIT(_mshift, _mwidth), \
|
||||||
|
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
.reg_fc = _reg_fc, \
|
||||||
|
.mask_fc = _mask_fc, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, \
|
||||||
|
spacemit_ccu_mux_div_gate_ops, _flags), \
|
||||||
|
}, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_DIV_GATE_FC_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth,\
|
||||||
|
_mask_fc, _muxshift, _muxwidth, _mask_gate, \
|
||||||
|
_flags) \
|
||||||
|
CCU_MUX_DIV_GATE_SPLIT_FC_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl, _mshift,\
|
||||||
|
_mwidth, _mask_fc, _muxshift, _muxwidth, \
|
||||||
|
_mask_gate, _flags)
|
||||||
|
|
||||||
|
#define CCU_MUX_DIV_FC_DEFINE(_name, _parents, _reg_ctrl, _mshift, _mwidth, \
|
||||||
|
_mask_fc, _muxshift, _muxwidth, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.div = CCU_DIV_INIT(_mshift, _mwidth), \
|
||||||
|
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
.reg_fc = _reg_ctrl, \
|
||||||
|
.mask_fc = _mask_fc, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, \
|
||||||
|
spacemit_ccu_mux_div_ops, _flags), \
|
||||||
|
}, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_MUX_FC_DEFINE(_name, _parents, _reg_ctrl, _mask_fc, _muxshift, \
|
||||||
|
_muxwidth, _flags) \
|
||||||
|
static struct ccu_mix _name = { \
|
||||||
|
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||||
|
.common = { \
|
||||||
|
.reg_ctrl = _reg_ctrl, \
|
||||||
|
.reg_fc = _reg_ctrl, \
|
||||||
|
.mask_fc = _mask_fc, \
|
||||||
|
CCU_MIX_INITHW_PARENTS(_name, _parents, spacemit_ccu_mux_ops, \
|
||||||
|
_flags) \
|
||||||
|
}, \
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct ccu_mix *hw_to_ccu_mix(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
|
||||||
|
return container_of(common, struct ccu_mix, common);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const struct clk_ops spacemit_ccu_gate_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_factor_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_mux_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_div_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_factor_gate_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_div_gate_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_mux_gate_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_mux_div_ops;
|
||||||
|
extern const struct clk_ops spacemit_ccu_mux_div_gate_ops;
|
||||||
|
#endif /* _CCU_DIV_H_ */
|
157
drivers/clk/spacemit/ccu_pll.c
Normal file
157
drivers/clk/spacemit/ccu_pll.c
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
#include <linux/math.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
#include "ccu_common.h"
|
||||||
|
#include "ccu_pll.h"
|
||||||
|
|
||||||
|
#define PLL_TIMEOUT_US 3000
|
||||||
|
#define PLL_DELAY_US 5
|
||||||
|
|
||||||
|
#define PLL_SWCR3_EN ((u32)BIT(31))
|
||||||
|
#define PLL_SWCR3_MASK GENMASK(30, 0)
|
||||||
|
|
||||||
|
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_best_rate(struct ccu_pll *pll,
|
||||||
|
unsigned long rate)
|
||||||
|
{
|
||||||
|
struct ccu_pll_config *config = &pll->config;
|
||||||
|
const struct ccu_pll_rate_tbl *best_entry;
|
||||||
|
unsigned long best_delta = ULONG_MAX;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < config->tbl_num; i++) {
|
||||||
|
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
|
||||||
|
unsigned long delta = abs_diff(entry->rate, rate);
|
||||||
|
|
||||||
|
if (delta < best_delta) {
|
||||||
|
best_delta = delta;
|
||||||
|
best_entry = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct ccu_pll_rate_tbl *ccu_pll_lookup_matched_entry(struct ccu_pll *pll)
|
||||||
|
{
|
||||||
|
struct ccu_pll_config *config = &pll->config;
|
||||||
|
u32 swcr1, swcr3;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
swcr1 = ccu_read(&pll->common, swcr1);
|
||||||
|
swcr3 = ccu_read(&pll->common, swcr3);
|
||||||
|
swcr3 &= PLL_SWCR3_MASK;
|
||||||
|
|
||||||
|
for (i = 0; i < config->tbl_num; i++) {
|
||||||
|
const struct ccu_pll_rate_tbl *entry = &config->rate_tbl[i];
|
||||||
|
|
||||||
|
if (swcr1 == entry->swcr1 && swcr3 == entry->swcr3)
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ccu_pll_update_param(struct ccu_pll *pll, const struct ccu_pll_rate_tbl *entry)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = &pll->common;
|
||||||
|
|
||||||
|
regmap_write(common->regmap, common->reg_swcr1, entry->swcr1);
|
||||||
|
ccu_update(common, swcr3, PLL_SWCR3_MASK, entry->swcr3);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_pll_is_enabled(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
|
||||||
|
return ccu_read(common, swcr3) & PLL_SWCR3_EN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_pll_enable(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
||||||
|
struct ccu_common *common = &pll->common;
|
||||||
|
unsigned int tmp;
|
||||||
|
|
||||||
|
ccu_update(common, swcr3, PLL_SWCR3_EN, PLL_SWCR3_EN);
|
||||||
|
|
||||||
|
/* check lock status */
|
||||||
|
return regmap_read_poll_timeout_atomic(common->lock_regmap,
|
||||||
|
pll->config.reg_lock,
|
||||||
|
tmp,
|
||||||
|
tmp & pll->config.mask_lock,
|
||||||
|
PLL_DELAY_US, PLL_TIMEOUT_US);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ccu_pll_disable(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
|
||||||
|
ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* PLLs must be gated before changing rate, which is ensured by
|
||||||
|
* flag CLK_SET_RATE_GATE.
|
||||||
|
*/
|
||||||
|
static int ccu_pll_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
||||||
|
const struct ccu_pll_rate_tbl *entry;
|
||||||
|
|
||||||
|
entry = ccu_pll_lookup_best_rate(pll, rate);
|
||||||
|
ccu_pll_update_param(pll, entry);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
||||||
|
const struct ccu_pll_rate_tbl *entry;
|
||||||
|
|
||||||
|
entry = ccu_pll_lookup_matched_entry(pll);
|
||||||
|
|
||||||
|
WARN_ON_ONCE(!entry);
|
||||||
|
|
||||||
|
return entry ? entry->rate : -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long *prate)
|
||||||
|
{
|
||||||
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
||||||
|
|
||||||
|
return ccu_pll_lookup_best_rate(pll, rate)->rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int ccu_pll_init(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_pll *pll = hw_to_ccu_pll(hw);
|
||||||
|
|
||||||
|
if (ccu_pll_lookup_matched_entry(pll))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
ccu_pll_disable(hw);
|
||||||
|
ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct clk_ops spacemit_ccu_pll_ops = {
|
||||||
|
.init = ccu_pll_init,
|
||||||
|
.enable = ccu_pll_enable,
|
||||||
|
.disable = ccu_pll_disable,
|
||||||
|
.set_rate = ccu_pll_set_rate,
|
||||||
|
.recalc_rate = ccu_pll_recalc_rate,
|
||||||
|
.round_rate = ccu_pll_round_rate,
|
||||||
|
.is_enabled = ccu_pll_is_enabled,
|
||||||
|
};
|
86
drivers/clk/spacemit/ccu_pll.h
Normal file
86
drivers/clk/spacemit/ccu_pll.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||||
|
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CCU_PLL_H_
|
||||||
|
#define _CCU_PLL_H_
|
||||||
|
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
|
||||||
|
#include "ccu_common.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct ccu_pll_rate_tbl - Structure mapping between PLL rate and register
|
||||||
|
* configuration.
|
||||||
|
*
|
||||||
|
* @rate: PLL rate
|
||||||
|
* @swcr1: Register value of PLLX_SW1_CTRL (PLLx_SWCR1).
|
||||||
|
* @swcr3: Register value of the PLLx_SW3_CTRL's lowest 31 bits of
|
||||||
|
* PLLx_SW3_CTRL (PLLx_SWCR3). This highest bit is for enabling
|
||||||
|
* the PLL and not contained in this field.
|
||||||
|
*/
|
||||||
|
struct ccu_pll_rate_tbl {
|
||||||
|
unsigned long rate;
|
||||||
|
u32 swcr1;
|
||||||
|
u32 swcr3;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ccu_pll_config {
|
||||||
|
const struct ccu_pll_rate_tbl *rate_tbl;
|
||||||
|
u32 tbl_num;
|
||||||
|
u32 reg_lock;
|
||||||
|
u32 mask_lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
|
||||||
|
{ \
|
||||||
|
.rate = _rate, \
|
||||||
|
.swcr1 = _swcr1, \
|
||||||
|
.swcr3 = _swcr3, \
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ccu_pll {
|
||||||
|
struct ccu_common common;
|
||||||
|
struct ccu_pll_config config;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock) \
|
||||||
|
{ \
|
||||||
|
.rate_tbl = _table, \
|
||||||
|
.tbl_num = ARRAY_SIZE(_table), \
|
||||||
|
.reg_lock = (_reg_lock), \
|
||||||
|
.mask_lock = (_mask_lock), \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CCU_PLL_HWINIT(_name, _flags) \
|
||||||
|
(&(struct clk_init_data) { \
|
||||||
|
.name = #_name, \
|
||||||
|
.ops = &spacemit_ccu_pll_ops, \
|
||||||
|
.parent_data = &(struct clk_parent_data) { .index = 0 }, \
|
||||||
|
.num_parents = 1, \
|
||||||
|
.flags = _flags, \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define CCU_PLL_DEFINE(_name, _table, _reg_swcr1, _reg_swcr3, _reg_lock, \
|
||||||
|
_mask_lock, _flags) \
|
||||||
|
static struct ccu_pll _name = { \
|
||||||
|
.config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \
|
||||||
|
.common = { \
|
||||||
|
.reg_swcr1 = _reg_swcr1, \
|
||||||
|
.reg_swcr3 = _reg_swcr3, \
|
||||||
|
.hw.init = CCU_PLL_HWINIT(_name, _flags) \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct ccu_pll *hw_to_ccu_pll(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct ccu_common *common = hw_to_ccu_common(hw);
|
||||||
|
|
||||||
|
return container_of(common, struct ccu_pll, common);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const struct clk_ops spacemit_ccu_pll_ops;
|
||||||
|
|
||||||
|
#endif
|
Loading…
Add table
Reference in a new issue