clk: spacemit: Add support for K1 SoC
The K1 SoC exposes four clock providers in the kernel mainline DT: one
PLL controller ("spacemit,k1-pll") and three syscon clock nodes
("spacemit,k1-syscon-{mpmu,apbc,apmu}"). Register a separate
U_BOOT_DRIVER for each.
The controllers register clocks into a single CCF namespace, and a clock
in one controller may parent off a clock owned by another, so a
controller must register only after the controllers that own its parents
have probed. Each probe forces its parent controllers up by driver:
MPMU <- PLL
APMU <- PLL, MPMU
APBC <- PLL, MPMU, APMU
Signed-off-by: Junhui Liu <junhui.liu@pigmoral.tech>
Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
Signed-off-by: Guodong Xu <guodong@riscstar.com>
This commit is contained in:
+3
-2
@@ -277,11 +277,12 @@ source "drivers/clk/mvebu/Kconfig"
|
||||
source "drivers/clk/owl/Kconfig"
|
||||
source "drivers/clk/qcom/Kconfig"
|
||||
source "drivers/clk/renesas/Kconfig"
|
||||
source "drivers/clk/sophgo/Kconfig"
|
||||
source "drivers/clk/sunxi/Kconfig"
|
||||
source "drivers/clk/sifive/Kconfig"
|
||||
source "drivers/clk/sophgo/Kconfig"
|
||||
source "drivers/clk/spacemit/Kconfig"
|
||||
source "drivers/clk/starfive/Kconfig"
|
||||
source "drivers/clk/stm32/Kconfig"
|
||||
source "drivers/clk/sunxi/Kconfig"
|
||||
source "drivers/clk/tegra/Kconfig"
|
||||
source "drivers/clk/ti/Kconfig"
|
||||
source "drivers/clk/thead/Kconfig"
|
||||
|
||||
@@ -48,6 +48,7 @@ obj-$(CONFIG_CLK_RENESAS) += renesas/
|
||||
obj-$(CONFIG_$(PHASE_)CLK_SCMI) += clk_scmi.o
|
||||
obj-$(CONFIG_CLK_SIFIVE) += sifive/
|
||||
obj-$(CONFIG_CLK_SOPHGO) += sophgo/
|
||||
obj-$(CONFIG_CLK_SPACEMIT) += spacemit/
|
||||
obj-$(CONFIG_CLK_SUNXI) += sunxi/
|
||||
obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
|
||||
obj-$(CONFIG_CLK_VERSACLOCK) += clk_versaclock.o
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (c) 2025, Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
|
||||
config CLK_SPACEMIT
|
||||
bool "Clock support for SpacemiT SoCs"
|
||||
depends on CLK || COMPILE_TEST
|
||||
select REGMAP
|
||||
select LIB_RATIONAL
|
||||
help
|
||||
This enables support clock driver for Spacemit SoC
|
||||
family.
|
||||
|
||||
if CLK_SPACEMIT
|
||||
|
||||
config CLK_SPACEMIT_K1
|
||||
bool "SpacemiT K1 clock support"
|
||||
select CLK_CCF
|
||||
help
|
||||
This enables support clock driver for Spacemit K1 SoC.
|
||||
It's based on Common Clock Framework.
|
||||
|
||||
endif
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#
|
||||
# Copyright (C) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
|
||||
obj-$(CONFIG_CLK_SPACEMIT) += clk_ddn.o clk_mix.o clk_pll.o
|
||||
|
||||
obj-$(CONFIG_CLK_SPACEMIT_K1) += clk-k1.o
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,79 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Copyright (c) 2025-2026 RISCstar Ltd.
|
||||
*
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*/
|
||||
|
||||
#ifndef _CLK_COMMON_H_
|
||||
#define _CLK_COMMON_H_
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
struct ccu_common;
|
||||
|
||||
typedef int (*ccu_init_fn)(struct ccu_common *common);
|
||||
|
||||
struct ccu_common {
|
||||
struct regmap *regmap;
|
||||
struct regmap *lock_regmap;
|
||||
const char *name;
|
||||
const char * const *parents;
|
||||
size_t num_parents;
|
||||
ccu_init_fn init;
|
||||
|
||||
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 clk;
|
||||
};
|
||||
|
||||
#define CCU_COMMON(_id, _name, _parent, _init, _flags) \
|
||||
.name = #_name, \
|
||||
.parents = (const char *[]) { _parent }, \
|
||||
.num_parents = 1, \
|
||||
.init = _init, \
|
||||
.clk = { .flags = _flags, .id = _id, } \
|
||||
|
||||
#define CCU_COMMON_PARENTS(_id, _name, _parents, _num_p, _init, _flags) \
|
||||
.name = #_name, \
|
||||
.parents = _parents, \
|
||||
.num_parents = _num_p, \
|
||||
.init = _init, \
|
||||
.clk = { .flags = _flags, .id = _id, } \
|
||||
|
||||
static inline struct ccu_common *clk_to_ccu_common(struct clk *clk)
|
||||
{
|
||||
return container_of(clk, struct ccu_common, clk);
|
||||
}
|
||||
|
||||
#define ccu_read(c, reg) \
|
||||
({ \
|
||||
struct ccu_common * const __ccu = (c); \
|
||||
u32 tmp; \
|
||||
regmap_read(__ccu->regmap, __ccu->reg_##reg, &tmp); \
|
||||
tmp; \
|
||||
})
|
||||
#define ccu_update(c, reg, mask, val) \
|
||||
({ \
|
||||
struct ccu_common * const __ccu = (c); \
|
||||
regmap_update_bits(__ccu->regmap, __ccu->reg_##reg, \
|
||||
mask, val); \
|
||||
})
|
||||
|
||||
#endif /* _CLK_COMMON_H_ */
|
||||
@@ -0,0 +1,93 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Authors: 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 <dm/device.h>
|
||||
#include <regmap.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/rational.h>
|
||||
|
||||
#include "clk_ddn.h"
|
||||
|
||||
#define UBOOT_DM_SPACEMIT_CLK_DDN "spacemit_clk_ddn"
|
||||
|
||||
static unsigned long ccu_ddn_calc_rate(unsigned long prate, unsigned long num,
|
||||
unsigned long den, unsigned int pre_div)
|
||||
{
|
||||
return prate * den / pre_div / 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 / ddn->pre_div,
|
||||
ddn->den_mask >> ddn->den_shift,
|
||||
ddn->num_mask >> ddn->num_shift,
|
||||
den, num);
|
||||
return ccu_ddn_calc_rate(prate, *num, *den, ddn->pre_div);
|
||||
}
|
||||
|
||||
static unsigned long ccu_ddn_recalc_rate(struct clk *clk)
|
||||
{
|
||||
struct ccu_ddn *ddn = clk_to_ccu_ddn(clk);
|
||||
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(clk_get_parent_rate(clk), num, den, ddn->pre_div);
|
||||
}
|
||||
|
||||
static unsigned long ccu_ddn_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
struct ccu_ddn *ddn = clk_to_ccu_ddn(clk);
|
||||
unsigned long num, den;
|
||||
|
||||
ccu_ddn_calc_best_rate(ddn, rate, clk_get_parent_rate(clk), &num, &den);
|
||||
|
||||
ccu_update(&ddn->common, ctrl,
|
||||
ddn->num_mask | ddn->den_mask,
|
||||
(num << ddn->num_shift) | (den << ddn->den_shift));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_ddn_ops = {
|
||||
.get_rate = ccu_ddn_recalc_rate,
|
||||
.set_rate = ccu_ddn_set_rate,
|
||||
};
|
||||
|
||||
int spacemit_ddn_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_DDN,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_ddn) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_DDN,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_ddn_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Copyright (c) 2025-2026 RISCstar Ltd.
|
||||
*
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*/
|
||||
|
||||
#ifndef _CLK_DDN_H_
|
||||
#define _CLK_DDN_H_
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "clk_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;
|
||||
unsigned int pre_div;
|
||||
};
|
||||
|
||||
#define CCU_DDN_MASK(_num_shift, _num_width) \
|
||||
GENMASK((_num_shift) + (_num_width) - 1, _num_shift)
|
||||
|
||||
#define CCU_DDN_DEFINE(_id, _var, _name, _parent, _reg_ctrl, _num_mask, \
|
||||
_num_shift, _den_mask, _den_shift, _pre_div, _flags) \
|
||||
static struct ccu_ddn _var = { \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON(_id, _name, _parent, spacemit_ddn_init, _flags) \
|
||||
}, \
|
||||
.num_mask = _num_mask, \
|
||||
.num_shift = _num_shift, \
|
||||
.den_mask = _den_mask, \
|
||||
.den_shift = _den_shift, \
|
||||
.pre_div = _pre_div, \
|
||||
}
|
||||
|
||||
static inline struct ccu_ddn *clk_to_ccu_ddn(struct clk *clk)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
|
||||
return container_of(common, struct ccu_ddn, common);
|
||||
}
|
||||
|
||||
int spacemit_ddn_init(struct ccu_common *common);
|
||||
|
||||
#endif /* _CLK_DDN_H_ */
|
||||
@@ -0,0 +1,403 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*
|
||||
* MIX clock type is the combination of mux, factor or divider, and gate
|
||||
*/
|
||||
|
||||
#include <dm/device.h>
|
||||
#include <dm/uclass.h>
|
||||
#include <div64.h>
|
||||
#include <regmap.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include "clk_mix.h"
|
||||
|
||||
#define UBOOT_DM_SPACEMIT_CLK_GATE "spacemit_clk_gate"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_FACTOR "spacemit_clk_factor"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_MUX "spacemit_clk_mux"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_DIV "spacemit_clk_div"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_FACTOR_GATE "spacemit_clk_factor_gate"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_MUX_GATE "spacemit_clk_mux_gate"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_DIV_GATE "spacemit_clk_div_gate"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_MUX_DIV "spacemit_clk_mux_div"
|
||||
#define UBOOT_DM_SPACEMIT_CLK_MUX_DIV_GATE "spacemit_clk_mux_div_gate"
|
||||
|
||||
#define MIX_FC_TIMEOUT_US 10000
|
||||
#define MIX_FC_DELAY_US 5
|
||||
|
||||
int ccu_gate_disable(struct clk *clk)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
|
||||
ccu_update(&mix->common, ctrl, mix->gate.mask, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ccu_gate_enable(struct clk *clk)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
struct ccu_gate_config *gate = &mix->gate;
|
||||
|
||||
ccu_update(&mix->common, ctrl, gate->mask, gate->mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long ccu_factor_recalc_rate(struct clk *clk)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
|
||||
return clk_get_parent_rate(clk) * mix->factor.mul / mix->factor.div;
|
||||
}
|
||||
|
||||
static unsigned long ccu_div_recalc_rate(struct clk *clk)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
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(clk, clk_get_parent_rate(clk), 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 *clk)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
unsigned int val;
|
||||
|
||||
if (common->reg_fc)
|
||||
return 0;
|
||||
|
||||
ccu_update(common, fc, common->mask_fc, common->mask_fc);
|
||||
|
||||
return regmap_read_poll_timeout(common->regmap, common->reg_fc,
|
||||
val, !(val & common->mask_fc),
|
||||
MIX_FC_DELAY_US,
|
||||
MIX_FC_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
ccu_mix_calc_best_rate(struct clk *clk, unsigned long rate,
|
||||
struct clk **best_parent,
|
||||
unsigned long *best_parent_rate,
|
||||
u32 *div_val)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
unsigned int parent_num = common->num_parents;
|
||||
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 udevice *parent_dev;
|
||||
unsigned long parent_rate;
|
||||
struct clk *parent;
|
||||
|
||||
if (uclass_get_device_by_name(UCLASS_CLK, common->parents[i],
|
||||
&parent_dev))
|
||||
continue;
|
||||
parent = dev_get_clk_ptr(parent_dev);
|
||||
if (!parent)
|
||||
continue;
|
||||
|
||||
parent_rate = clk_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 unsigned long ccu_mix_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
struct ccu_common *common = &mix->common;
|
||||
struct ccu_div_config *div = &mix->div;
|
||||
u32 current_div, target_div, mask;
|
||||
|
||||
ccu_mix_calc_best_rate(clk, 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(clk);
|
||||
}
|
||||
|
||||
static u8 ccu_mux_get_parent(struct clk *clk)
|
||||
{
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
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 *clk, struct clk *parent)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
struct ccu_mix *mix = clk_to_ccu_mix(clk);
|
||||
struct ccu_mux_config *mux = &mix->mux;
|
||||
u32 mask;
|
||||
int i = 0;
|
||||
|
||||
mask = GENMASK(mux->width + mux->shift - 1, mux->shift);
|
||||
|
||||
for (i = 0; i < common->num_parents; i++) {
|
||||
if (!strcmp(parent->dev->name, common->parents[i]))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == common->num_parents)
|
||||
return -EINVAL;
|
||||
|
||||
ccu_update(&mix->common, ctrl, mask, i << mux->shift);
|
||||
|
||||
return ccu_mix_trigger_fc(clk);
|
||||
}
|
||||
|
||||
int spacemit_gate_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_GATE,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_gate_ops = {
|
||||
.disable = ccu_gate_disable,
|
||||
.enable = ccu_gate_enable,
|
||||
.get_rate = clk_generic_get_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_gate) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_GATE,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_gate_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_factor_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_FACTOR,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_factor_ops = {
|
||||
.get_rate = ccu_factor_recalc_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_factor) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_FACTOR,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_factor_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_mux_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
u8 index;
|
||||
|
||||
index = ccu_mux_get_parent(clk);
|
||||
if (index >= common->num_parents)
|
||||
index = 0;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_MUX,
|
||||
common->name, common->parents[index]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_mux_ops = {
|
||||
.set_parent = ccu_mux_set_parent,
|
||||
.get_rate = clk_generic_get_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_mux) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_MUX,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_mux_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_div_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_DIV,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_div_ops = {
|
||||
.get_rate = ccu_div_recalc_rate,
|
||||
.set_rate = ccu_mix_set_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_div) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_DIV,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_div_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_factor_gate_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_FACTOR_GATE,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_factor_gate_ops = {
|
||||
.disable = ccu_gate_disable,
|
||||
.enable = ccu_gate_enable,
|
||||
.get_rate = ccu_factor_recalc_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_factor_gate) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_FACTOR_GATE,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_factor_gate_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_mux_gate_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
u8 index;
|
||||
|
||||
index = ccu_mux_get_parent(clk);
|
||||
if (index >= common->num_parents)
|
||||
index = 0;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_MUX_GATE,
|
||||
common->name, common->parents[index]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_mux_gate_ops = {
|
||||
.disable = ccu_gate_disable,
|
||||
.enable = ccu_gate_enable,
|
||||
.set_parent = ccu_mux_set_parent,
|
||||
.get_rate = clk_generic_get_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_mux_gate) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_MUX_GATE,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_mux_gate_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_div_gate_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_DIV_GATE,
|
||||
common->name, common->parents[0]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_div_gate_ops = {
|
||||
.disable = ccu_gate_disable,
|
||||
.enable = ccu_gate_enable,
|
||||
.get_rate = ccu_div_recalc_rate,
|
||||
.set_rate = ccu_mix_set_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_div_gate) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_DIV_GATE,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_div_gate_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_mux_div_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
u8 index;
|
||||
|
||||
index = ccu_mux_get_parent(clk);
|
||||
if (index >= common->num_parents)
|
||||
index = 0;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_MUX_DIV,
|
||||
common->name, common->parents[index]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_mux_div_ops = {
|
||||
.set_parent = ccu_mux_set_parent,
|
||||
.get_rate = ccu_div_recalc_rate,
|
||||
.set_rate = ccu_mix_set_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_mux_div) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_MUX_DIV,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_mux_div_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
|
||||
int spacemit_mux_div_gate_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
u8 index;
|
||||
|
||||
index = ccu_mux_get_parent(clk);
|
||||
if (index >= common->num_parents)
|
||||
index = 0;
|
||||
|
||||
return clk_register(clk, UBOOT_DM_SPACEMIT_CLK_MUX_DIV_GATE,
|
||||
common->name, common->parents[index]);
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_mux_div_gate_ops = {
|
||||
.disable = ccu_gate_disable,
|
||||
.enable = ccu_gate_enable,
|
||||
.set_parent = ccu_mux_set_parent,
|
||||
.get_rate = ccu_div_recalc_rate,
|
||||
.set_rate = ccu_mix_set_rate,
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_mux_div_gate) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_MUX_DIV_GATE,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_mux_div_gate_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
@@ -0,0 +1,224 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Copyright (c) 2025-2026 RISCstar Ltd.
|
||||
*
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*/
|
||||
|
||||
#ifndef _CLK_MIX_H_
|
||||
#define _CLK_MIX_H_
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "clk_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_GATE_DEFINE(_id, _var, _name, _parent, _reg_ctrl, \
|
||||
_mask_gate, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON(_id, _name, _parent, spacemit_gate_init, \
|
||||
_flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_FACTOR_DEFINE(_id, _var, _name, _parent, _div, _mul) \
|
||||
static struct ccu_mix _var = { \
|
||||
.factor = CCU_FACTOR_INIT(_div, _mul), \
|
||||
.common = { \
|
||||
CCU_COMMON(_id, _name, _parent, spacemit_factor_init, \
|
||||
0) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_MUX_DEFINE(_id, _var, _name, _parents, _num_p, _reg_ctrl, \
|
||||
_shift, _width, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.mux = CCU_MUX_INIT(_shift, _width), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_init, _flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_DIV_DEFINE(_id, _var, _name, _parent, _reg_ctrl, _shift, \
|
||||
_width, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.div = CCU_DIV_INIT(_shift, _width), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON(_id, _name, _parent, spacemit_div_init, \
|
||||
_flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_FACTOR_GATE_FLAGS_DEFINE(_id, _var, _name, _parent, \
|
||||
_reg_ctrl, _mask_gate, _div, _mul, \
|
||||
_flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||
.factor = CCU_FACTOR_INIT(_div, _mul), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON(_id, _name, _parent, \
|
||||
spacemit_factor_gate_init, _flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_FACTOR_GATE_DEFINE(_id, _var, _name, _parent, _reg_ctrl, \
|
||||
_mask_gate, _div, _mul) \
|
||||
CCU_FACTOR_GATE_FLAGS_DEFINE(_id, _var, _name, _parent, \
|
||||
_reg_ctrl, _mask_gate, _div, _mul, \
|
||||
0)
|
||||
|
||||
#define CCU_MUX_GATE_DEFINE(_id, _var, _name, _parents, _num_p, \
|
||||
_reg_ctrl, _shift, _width, _mask_gate, \
|
||||
_flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||
.mux = CCU_MUX_INIT(_shift, _width), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_gate_init, _flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_DIV_GATE_DEFINE(_id, _var, _name, _parent, _reg_ctrl, \
|
||||
_shift, _width, _mask_gate, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||
.div = CCU_DIV_INIT(_shift, _width), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON(_id, _name, _parent, \
|
||||
spacemit_div_gate_init, _flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
#define CCU_MUX_DIV_GATE_DEFINE(_id, _var, _name, _parents, _num_p, \
|
||||
_reg_ctrl, _mshift, _mwidth, _muxshift, \
|
||||
_muxwidth, _mask_gate, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.gate = CCU_GATE_INIT(_mask_gate), \
|
||||
.div = CCU_DIV_INIT(_mshift, _mwidth), \
|
||||
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
CCU_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_div_gate_init, _flags) \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define CCU_MUX_DIV_GATE_SPLIT_FC_DEFINE(_id, _var, _name, _parents, \
|
||||
_num_p, _reg_ctrl, _reg_fc, \
|
||||
_mshift, _mwidth, _mask_fc, \
|
||||
_muxshift, _muxwidth, \
|
||||
_mask_gate, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.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_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_div_gate_init, _flags) \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define CCU_MUX_DIV_FC_DEFINE(_id, _var, _name, _parents, _num_p, \
|
||||
_reg_ctrl, _reg_fc, _mshift, _mwidth, \
|
||||
_mask_fc, _muxshift, _muxwidth, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.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_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_div_init, _flags) \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define CCU_MUX_FC_DEFINE(_id, _var, _name, _parents, _num_p, \
|
||||
_reg_ctrl, _reg_fc, _mask_fc, _muxshift, \
|
||||
_muxwidth, _flags) \
|
||||
static struct ccu_mix _var = { \
|
||||
.mux = CCU_MUX_INIT(_muxshift, _muxwidth), \
|
||||
.common = { \
|
||||
.reg_ctrl = _reg_ctrl, \
|
||||
.reg_fc = _reg_fc, \
|
||||
.mask_fc = _mask_fc, \
|
||||
CCU_COMMON_PARENTS(_id, _name, _parents, _num_p, \
|
||||
spacemit_mux_init, \
|
||||
_flags) \
|
||||
}, \
|
||||
}
|
||||
|
||||
static inline struct ccu_mix *clk_to_ccu_mix(struct clk *clk)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
|
||||
return container_of(common, struct ccu_mix, common);
|
||||
}
|
||||
|
||||
int ccu_gate_enable(struct clk *clk);
|
||||
int ccu_gate_disable(struct clk *clk);
|
||||
|
||||
int spacemit_gate_init(struct ccu_common *common);
|
||||
int spacemit_factor_init(struct ccu_common *common);
|
||||
int spacemit_mux_init(struct ccu_common *common);
|
||||
int spacemit_div_init(struct ccu_common *common);
|
||||
int spacemit_factor_gate_init(struct ccu_common *common);
|
||||
int spacemit_div_gate_init(struct ccu_common *common);
|
||||
int spacemit_mux_gate_init(struct ccu_common *common);
|
||||
int spacemit_mux_div_init(struct ccu_common *common);
|
||||
int spacemit_mux_div_gate_init(struct ccu_common *common);
|
||||
|
||||
#endif /* _CLK_MIX_H_ */
|
||||
@@ -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>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*/
|
||||
|
||||
#include <dm/device.h>
|
||||
#include <regmap.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "clk_pll.h"
|
||||
|
||||
#define UBOOT_DM_SPACEMIT_CLK_PLL "spacemit_clk_pll"
|
||||
|
||||
#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(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_enable(struct clk *clk)
|
||||
{
|
||||
struct ccu_pll *pll = clk_to_ccu_pll(clk);
|
||||
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(common->lock_regmap,
|
||||
pll->config.reg_lock,
|
||||
tmp,
|
||||
tmp & pll->config.mask_lock,
|
||||
PLL_DELAY_US, PLL_TIMEOUT_US);
|
||||
}
|
||||
|
||||
static int ccu_pll_disable(struct clk *clk)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
|
||||
ccu_update(common, swcr3, PLL_SWCR3_EN, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* PLLs must be gated before changing rate, which is ensured by
|
||||
* flag CLK_SET_RATE_GATE.
|
||||
*/
|
||||
static unsigned long ccu_pll_set_rate(struct clk *clk, unsigned long rate)
|
||||
{
|
||||
struct ccu_pll *pll = clk_to_ccu_pll(clk);
|
||||
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 *clk)
|
||||
{
|
||||
struct ccu_pll *pll = clk_to_ccu_pll(clk);
|
||||
const struct ccu_pll_rate_tbl *entry;
|
||||
|
||||
entry = ccu_pll_lookup_matched_entry(pll);
|
||||
|
||||
WARN_ON_ONCE(!entry);
|
||||
|
||||
return entry ? entry->rate : 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops spacemit_clk_pll_ops = {
|
||||
.enable = ccu_pll_enable,
|
||||
.disable = ccu_pll_disable,
|
||||
.set_rate = ccu_pll_set_rate,
|
||||
.get_rate = ccu_pll_recalc_rate,
|
||||
};
|
||||
|
||||
int spacemit_pll_init(struct ccu_common *common)
|
||||
{
|
||||
struct clk *clk = &common->clk;
|
||||
struct ccu_pll *pll = clk_to_ccu_pll(clk);
|
||||
int ret;
|
||||
|
||||
ret = clk_register(clk, UBOOT_DM_SPACEMIT_CLK_PLL,
|
||||
common->name, common->parents[0]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (ccu_pll_lookup_matched_entry(pll))
|
||||
return 0;
|
||||
|
||||
ccu_pll_disable(clk);
|
||||
ccu_pll_update_param(pll, &pll->config.rate_tbl[0]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
U_BOOT_DRIVER(spacemit_clk_pll) = {
|
||||
.name = UBOOT_DM_SPACEMIT_CLK_PLL,
|
||||
.id = UCLASS_CLK,
|
||||
.ops = &spacemit_clk_pll_ops,
|
||||
.flags = DM_FLAG_PRE_RELOC,
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2024 SpacemiT Technology Co. Ltd
|
||||
* Copyright (c) 2024-2025 Haylen Chu <heylenay@4d2.org>
|
||||
* Copyright (c) 2025 Junhui Liu <junhui.liu@pigmoral.tech>
|
||||
* Copyright (c) 2025-2026 RISCstar Ltd.
|
||||
*
|
||||
* Authors: Haylen Chu <heylenay@4d2.org>
|
||||
*/
|
||||
|
||||
#ifndef _CLK_PLL_H_
|
||||
#define _CLK_PLL_H_
|
||||
|
||||
#include <linux/clk-provider.h>
|
||||
|
||||
#include "clk_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;
|
||||
};
|
||||
|
||||
#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \
|
||||
{ \
|
||||
.rate = _rate, \
|
||||
.swcr1 = _swcr1, \
|
||||
.swcr3 = _swcr3, \
|
||||
}
|
||||
|
||||
struct ccu_pll_config {
|
||||
const struct ccu_pll_rate_tbl *rate_tbl;
|
||||
u32 tbl_num;
|
||||
u32 reg_lock;
|
||||
u32 mask_lock;
|
||||
};
|
||||
|
||||
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 = sizeof(_table) / sizeof((_table)[0]), \
|
||||
.reg_lock = (_reg_lock), \
|
||||
.mask_lock = (_mask_lock), \
|
||||
}
|
||||
|
||||
#define CCU_PLL_DEFINE(_id, _var, _name, _parent, _table, _reg_swcr1, \
|
||||
_reg_swcr3, _reg_lock, _mask_lock, _flags) \
|
||||
static struct ccu_pll _var = { \
|
||||
.config = CCU_PLL_CONFIG(_table, _reg_lock, _mask_lock), \
|
||||
.common = { \
|
||||
.reg_swcr1 = _reg_swcr1, \
|
||||
.reg_swcr3 = _reg_swcr3, \
|
||||
CCU_COMMON(_id, _name, _parent, spacemit_pll_init, _flags) \
|
||||
} \
|
||||
}
|
||||
|
||||
static inline struct ccu_pll *clk_to_ccu_pll(struct clk *clk)
|
||||
{
|
||||
struct ccu_common *common = clk_to_ccu_common(clk);
|
||||
|
||||
return container_of(common, struct ccu_pll, common);
|
||||
}
|
||||
|
||||
int spacemit_pll_init(struct ccu_common *common);
|
||||
|
||||
#endif /* _CLK_PLL_H_ */
|
||||
@@ -0,0 +1,149 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
|
||||
/* SpacemiT clock and reset driver definitions for the K1 SoC */
|
||||
|
||||
#ifndef __SOC_K1_SYSCON_H__
|
||||
#define __SOC_K1_SYSCON_H__
|
||||
|
||||
/* APBS register offset */
|
||||
#define APBS_PLL1_SWCR1 0x100
|
||||
#define APBS_PLL1_SWCR2 0x104
|
||||
#define APBS_PLL1_SWCR3 0x108
|
||||
#define APBS_PLL2_SWCR1 0x118
|
||||
#define APBS_PLL2_SWCR2 0x11c
|
||||
#define APBS_PLL2_SWCR3 0x120
|
||||
#define APBS_PLL3_SWCR1 0x124
|
||||
#define APBS_PLL3_SWCR2 0x128
|
||||
#define APBS_PLL3_SWCR3 0x12c
|
||||
|
||||
/* MPMU register offset */
|
||||
#define MPMU_POSR 0x0010
|
||||
#define MPMU_FCCR 0x0008
|
||||
#define POSR_PLL1_LOCK BIT(27)
|
||||
#define POSR_PLL2_LOCK BIT(28)
|
||||
#define POSR_PLL3_LOCK BIT(29)
|
||||
#define MPMU_SUCCR 0x0014
|
||||
#define MPMU_ISCCR 0x0044
|
||||
#define MPMU_WDTPCR 0x0200
|
||||
#define MPMU_RIPCCR 0x0210
|
||||
#define MPMU_ACGR 0x1024
|
||||
#define MPMU_APBCSCR 0x1050
|
||||
#define MPMU_SUCCR_1 0x10b0
|
||||
|
||||
/* APBC register offset */
|
||||
#define APBC_UART1_CLK_RST 0x00
|
||||
#define APBC_UART2_CLK_RST 0x04
|
||||
#define APBC_GPIO_CLK_RST 0x08
|
||||
#define APBC_PWM0_CLK_RST 0x0c
|
||||
#define APBC_PWM1_CLK_RST 0x10
|
||||
#define APBC_PWM2_CLK_RST 0x14
|
||||
#define APBC_PWM3_CLK_RST 0x18
|
||||
#define APBC_TWSI8_CLK_RST 0x20
|
||||
#define APBC_UART3_CLK_RST 0x24
|
||||
#define APBC_RTC_CLK_RST 0x28
|
||||
#define APBC_TWSI0_CLK_RST 0x2c
|
||||
#define APBC_TWSI1_CLK_RST 0x30
|
||||
#define APBC_TIMERS1_CLK_RST 0x34
|
||||
#define APBC_TWSI2_CLK_RST 0x38
|
||||
#define APBC_AIB_CLK_RST 0x3c
|
||||
#define APBC_TWSI4_CLK_RST 0x40
|
||||
#define APBC_TIMERS2_CLK_RST 0x44
|
||||
#define APBC_ONEWIRE_CLK_RST 0x48
|
||||
#define APBC_TWSI5_CLK_RST 0x4c
|
||||
#define APBC_DRO_CLK_RST 0x58
|
||||
#define APBC_IR_CLK_RST 0x5c
|
||||
#define APBC_TWSI6_CLK_RST 0x60
|
||||
#define APBC_COUNTER_CLK_SEL 0x64
|
||||
#define APBC_TWSI7_CLK_RST 0x68
|
||||
#define APBC_TSEN_CLK_RST 0x6c
|
||||
#define APBC_UART4_CLK_RST 0x70
|
||||
#define APBC_UART5_CLK_RST 0x74
|
||||
#define APBC_UART6_CLK_RST 0x78
|
||||
#define APBC_SSP3_CLK_RST 0x7c
|
||||
#define APBC_SSPA0_CLK_RST 0x80
|
||||
#define APBC_SSPA1_CLK_RST 0x84
|
||||
#define APBC_IPC_AP2AUD_CLK_RST 0x90
|
||||
#define APBC_UART7_CLK_RST 0x94
|
||||
#define APBC_UART8_CLK_RST 0x98
|
||||
#define APBC_UART9_CLK_RST 0x9c
|
||||
#define APBC_CAN0_CLK_RST 0xa0
|
||||
#define APBC_PWM4_CLK_RST 0xa8
|
||||
#define APBC_PWM5_CLK_RST 0xac
|
||||
#define APBC_PWM6_CLK_RST 0xb0
|
||||
#define APBC_PWM7_CLK_RST 0xb4
|
||||
#define APBC_PWM8_CLK_RST 0xb8
|
||||
#define APBC_PWM9_CLK_RST 0xbc
|
||||
#define APBC_PWM10_CLK_RST 0xc0
|
||||
#define APBC_PWM11_CLK_RST 0xc4
|
||||
#define APBC_PWM12_CLK_RST 0xc8
|
||||
#define APBC_PWM13_CLK_RST 0xcc
|
||||
#define APBC_PWM14_CLK_RST 0xd0
|
||||
#define APBC_PWM15_CLK_RST 0xd4
|
||||
#define APBC_PWM16_CLK_RST 0xd8
|
||||
#define APBC_PWM17_CLK_RST 0xdc
|
||||
#define APBC_PWM18_CLK_RST 0xe0
|
||||
#define APBC_PWM19_CLK_RST 0xe4
|
||||
|
||||
/* APMU register offset */
|
||||
#define APMU_JPG_CLK_RES_CTRL 0x020
|
||||
#define APMU_CSI_CCIC2_CLK_RES_CTRL 0x024
|
||||
#define APMU_ISP_CLK_RES_CTRL 0x038
|
||||
#define APMU_LCD_CLK_RES_CTRL1 0x044
|
||||
#define APMU_LCD_SPI_CLK_RES_CTRL 0x048
|
||||
#define APMU_LCD_CLK_RES_CTRL2 0x04c
|
||||
#define APMU_CCIC_CLK_RES_CTRL 0x050
|
||||
#define APMU_SDH0_CLK_RES_CTRL 0x054
|
||||
#define APMU_SDH1_CLK_RES_CTRL 0x058
|
||||
#define APMU_USB_CLK_RES_CTRL 0x05c
|
||||
#define APMU_QSPI_CLK_RES_CTRL 0x060
|
||||
#define APMU_DMA_CLK_RES_CTRL 0x064
|
||||
#define APMU_AES_CLK_RES_CTRL 0x068
|
||||
#define APMU_VPU_CLK_RES_CTRL 0x0a4
|
||||
#define APMU_GPU_CLK_RES_CTRL 0x0cc
|
||||
#define APMU_SDH2_CLK_RES_CTRL 0x0e0
|
||||
#define APMU_PMUA_MC_CTRL 0x0e8
|
||||
#define APMU_PMU_CC2_AP 0x100
|
||||
#define APMU_PMUA_EM_CLK_RES_CTRL 0x104
|
||||
#define APMU_AUDIO_CLK_RES_CTRL 0x14c
|
||||
#define APMU_HDMI_CLK_RES_CTRL 0x1b8
|
||||
#define APMU_CCI550_CLK_CTRL 0x300
|
||||
#define APMU_ACLK_CLK_CTRL 0x388
|
||||
#define APMU_CPU_C0_CLK_CTRL 0x38C
|
||||
#define APMU_CPU_C1_CLK_CTRL 0x390
|
||||
#define APMU_PCIE_CLK_RES_CTRL_0 0x3cc
|
||||
#define APMU_PCIE_CLK_RES_CTRL_1 0x3d4
|
||||
#define APMU_PCIE_CLK_RES_CTRL_2 0x3dc
|
||||
#define APMU_EMAC0_CLK_RES_CTRL 0x3e4
|
||||
#define APMU_EMAC1_CLK_RES_CTRL 0x3ec
|
||||
|
||||
/* RCPU register offsets */
|
||||
#define RCPU_SSP0_CLK_RST 0x0028
|
||||
#define RCPU_I2C0_CLK_RST 0x0030
|
||||
#define RCPU_UART1_CLK_RST 0x003c
|
||||
#define RCPU_CAN_CLK_RST 0x0048
|
||||
#define RCPU_IR_CLK_RST 0x004c
|
||||
#define RCPU_UART0_CLK_RST 0x00d8
|
||||
#define AUDIO_HDMI_CLK_CTRL 0x2044
|
||||
|
||||
/* RCPU2 register offsets */
|
||||
#define RCPU2_PWM0_CLK_RST 0x0000
|
||||
#define RCPU2_PWM1_CLK_RST 0x0004
|
||||
#define RCPU2_PWM2_CLK_RST 0x0008
|
||||
#define RCPU2_PWM3_CLK_RST 0x000c
|
||||
#define RCPU2_PWM4_CLK_RST 0x0010
|
||||
#define RCPU2_PWM5_CLK_RST 0x0014
|
||||
#define RCPU2_PWM6_CLK_RST 0x0018
|
||||
#define RCPU2_PWM7_CLK_RST 0x001c
|
||||
#define RCPU2_PWM8_CLK_RST 0x0020
|
||||
#define RCPU2_PWM9_CLK_RST 0x0024
|
||||
|
||||
/* APBC2 register offsets */
|
||||
#define APBC2_UART1_CLK_RST 0x0000
|
||||
#define APBC2_SSP2_CLK_RST 0x0004
|
||||
#define APBC2_TWSI3_CLK_RST 0x0008
|
||||
#define APBC2_RTC_CLK_RST 0x000c
|
||||
#define APBC2_TIMERS0_CLK_RST 0x0010
|
||||
#define APBC2_KPC_CLK_RST 0x0014
|
||||
#define APBC2_GPIO_CLK_RST 0x001c
|
||||
|
||||
#endif /* __SOC_K1_SYSCON_H__ */
|
||||
Reference in New Issue
Block a user