On 17:57 Thu 06 Mar , Haylen Chu wrote: > 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 atomiciy > between concurrent operations of clock driver and reset, > power-domain driver that will be introduced in the future. > > Signed-off-by: Haylen Chu <heylenay@xxxxxxx> > --- > drivers/clk/Kconfig | 1 + > drivers/clk/Makefile | 1 + > drivers/clk/spacemit/Kconfig | 20 + > drivers/clk/spacemit/Makefile | 5 + > drivers/clk/spacemit/ccu-k1.c | 1714 +++++++++++++++++++++++++++++ > drivers/clk/spacemit/ccu_common.h | 47 + > drivers/clk/spacemit/ccu_ddn.c | 80 ++ > drivers/clk/spacemit/ccu_ddn.h | 48 + > drivers/clk/spacemit/ccu_mix.c | 284 +++++ > drivers/clk/spacemit/ccu_mix.h | 246 +++++ > drivers/clk/spacemit/ccu_pll.c | 146 +++ > drivers/clk/spacemit/ccu_pll.h | 76 ++ > 12 files changed, 2668 insertions(+) > create mode 100644 drivers/clk/spacemit/Kconfig > create mode 100644 drivers/clk/spacemit/Makefile > create mode 100644 drivers/clk/spacemit/ccu-k1.c > create mode 100644 drivers/clk/spacemit/ccu_common.h > create mode 100644 drivers/clk/spacemit/ccu_ddn.c > create mode 100644 drivers/clk/spacemit/ccu_ddn.h > create mode 100644 drivers/clk/spacemit/ccu_mix.c > create mode 100644 drivers/clk/spacemit/ccu_mix.h > create mode 100644 drivers/clk/spacemit/ccu_pll.c > create mode 100644 drivers/clk/spacemit/ccu_pll.h > .. > +static int k1_ccu_probe(struct platform_device *pdev) > +{ > + struct regmap *base_regmap, *lock_regmap = NULL; > + struct device *dev = &pdev->dev; > + int ret; > + > + base_regmap = device_node_to_regmap(dev->of_node); > + if (IS_ERR(base_regmap)) > + return dev_err_probe(dev, PTR_ERR(base_regmap), > + "failed to get regmap\n"); > + > + if (of_device_is_compatible(dev->of_node, "spacemit,k1-pll")) { .. > + struct device_node *mpmu = of_parse_phandle(dev->of_node, > + "spacemit,mpmu", 0); > + if (!mpmu) > + return dev_err_probe(dev, -ENODEV, > + "Cannot parse MPMU region\n"); > + > + lock_regmap = device_node_to_regmap(mpmu); > + of_node_put(mpmu); > + you can simplify above with syscon_regmap_lookup_by_phandle(), which would save a few lines or further, just call syscon_regmap_lookup_by_compatible()? then won't be necessary to introduce the "spacemit,mpmu" property.. > + if (IS_ERR(lock_regmap)) > + return dev_err_probe(dev, PTR_ERR(lock_regmap), > + "failed to get lock regmap\n"); > + } > + > + ret = spacemit_ccu_register(dev, base_regmap, lock_regmap, > + of_device_get_match_data(dev)); > + if (ret) > + return dev_err_probe(dev, ret, "failed to register clocks\n"); > + > + return 0; > +} > + > +static const struct of_device_id of_k1_ccu_match[] = { > + { > + .compatible = "spacemit,k1-pll", > + .data = k1_ccu_apbs_clks, > + }, > + { > + .compatible = "spacemit,k1-syscon-mpmu", > + .data = k1_ccu_mpmu_clks, > + }, > + { > + .compatible = "spacemit,k1-syscon-apbc", > + .data = k1_ccu_apbc_clks, > + }, > + { > + .compatible = "spacemit,k1-syscon-apmu", > + .data = k1_ccu_apmu_clks, > + }, > + { } > +}; > +MODULE_DEVICE_TABLE(of, of_k1_ccu_match); > + > +static struct platform_driver k1_ccu_driver = { > + .driver = { > + .name = "spacemit,k1-ccu", > + .of_match_table = of_k1_ccu_match, > + }, > + .probe = k1_ccu_probe, > +}; > +module_platform_driver(k1_ccu_driver); > + > +MODULE_DESCRIPTION("Spacemit K1 CCU driver"); > +MODULE_AUTHOR("Haylen Chu <heylenay@xxxxxxx>"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/clk/spacemit/ccu_common.h b/drivers/clk/spacemit/ccu_common.h > new file mode 100644 > index 000000000000..494cde96fe3c > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_common.h > @@ -0,0 +1,47 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#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 fc; > + }; > + > + /* For PLL */ > + struct { > + u32 reg_swcr1; > + u32 reg_swcr2; > + 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(reg, c, val) regmap_read((c)->regmap, (c)->reg_##reg, val) > +#define ccu_update(reg, c, mask, val) \ > + regmap_update_bits((c)->regmap, (c)->reg_##reg, mask, val) > +#define ccu_poll(reg, c, tmp, cond, sleep, timeout) \ > + regmap_read_poll_timeout_atomic((c)->regmap, (c)->reg_##reg, \ > + tmp, cond, sleep, timeout) > + > +#endif /* _CCU_COMMON_H_ */ > diff --git a/drivers/clk/spacemit/ccu_ddn.c b/drivers/clk/spacemit/ccu_ddn.c > new file mode 100644 > index 000000000000..ee187687d0c4 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_ddn.c > @@ -0,0 +1,80 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Spacemit clock type ddn > + * > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#include <linux/clk-provider.h> > +#include <linux/rational.h> > + > +#include "ccu_ddn.h" > + > +/* > + * 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 > + */ > + > +static unsigned long clk_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->num_mask, > + den, num); > + return prate / 2 * *den / *num; > +} > + > +static long clk_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 = 0, den = 0; > + > + return clk_ddn_calc_best_rate(ddn, rate, *prate, &num, &den); > +} > + > +static unsigned long clk_ddn_recalc_rate(struct clk_hw *hw, unsigned long prate) > +{ > + struct ccu_ddn *ddn = hw_to_ccu_ddn(hw); > + unsigned int val, num, den; > + > + ccu_read(ctrl, &ddn->common, &val); > + > + num = (val & ddn->num_mask) >> ddn->num_shift; > + den = (val & ddn->den_mask) >> ddn->den_shift; > + > + return prate / 2 * den / num; > +} > + > +static int clk_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; > + > + clk_ddn_calc_best_rate(ddn, rate, prate, &num, &den); > + > + ccu_update(ctrl, &ddn->common, > + 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 = clk_ddn_recalc_rate, > + .round_rate = clk_ddn_round_rate, > + .set_rate = clk_ddn_set_rate, > +}; > diff --git a/drivers/clk/spacemit/ccu_ddn.h b/drivers/clk/spacemit/ccu_ddn.h > new file mode 100644 > index 000000000000..3746d084e1e7 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_ddn.h > @@ -0,0 +1,48 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#ifndef _CCU_DDN_H_ > +#define _CCU_DDN_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_mask, _num_shift, _den_mask, _den_shift, \ > + _flags) \ > + struct ccu_ddn _name = { \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .hw.init = CCU_DDN_INIT(_name, _parent, _flags), \ > + }, \ > + .num_mask = _num_mask, \ > + .num_shift = _num_shift, \ > + .den_mask = _den_mask, \ > + .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 > diff --git a/drivers/clk/spacemit/ccu_mix.c b/drivers/clk/spacemit/ccu_mix.c > new file mode 100644 > index 000000000000..a5c13000e062 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_mix.c > @@ -0,0 +1,284 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Spacemit clock type mix(div/mux/gate/factor) > + * > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#include <linux/clk-provider.h> > + > +#include "ccu_mix.h" > + > +#define MIX_TIMEOUT 10000 > + > +static void ccu_gate_disable(struct clk_hw *hw) > +{ > + struct ccu_mix *mix = hw_to_ccu_mix(hw); > + struct ccu_common *common = &mix->common; > + > + ccu_update(ctrl, common, mix->gate.mask, 0); > +} > + > +static int ccu_gate_enable(struct clk_hw *hw) > +{ > + struct ccu_mix *mix = hw_to_ccu_mix(hw); > + struct ccu_common *common = &mix->common; > + struct ccu_gate_config *gate = &mix->gate; > + > + ccu_update(ctrl, common, 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_common *common = &mix->common; > + u32 tmp; > + > + ccu_read(ctrl, common, &tmp); > + > + return !!(tmp & mix->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_common *common = &mix->common; > + struct ccu_div_config *div = &mix->div; > + unsigned long val; > + u32 reg; > + > + ccu_read(ctrl, common, ®); > + > + val = reg >> div->shift; > + val &= (1 << div->width) - 1; > + > + val = divider_recalc_rate(hw, parent_rate, val, NULL, 0, div->width); > + > + return val; > +} > + > +static int ccu_mix_trigger_fc(struct clk_hw *hw) > +{ > + struct ccu_mix *mix = hw_to_ccu_mix(hw); > + struct ccu_common *common = &mix->common; > + unsigned int val = 0; > + > + ccu_update(fc, common, common->fc, common->fc); > + > + return ccu_poll(fc, common, val, !(val & common->fc), > + 5, MIX_TIMEOUT); > +} > + > +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_UP_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; > + int ret = 0, tmp = 0; > + u32 current_div, target_div; > + > + ccu_mix_calc_best_rate(hw, rate, NULL, NULL, &target_div); > + > + ccu_read(ctrl, common, &tmp); > + > + current_div = tmp >> div->shift; > + current_div &= (1 << div->width) - 1; > + > + if (current_div == target_div) > + return 0; > + > + tmp = GENMASK(div->width + div->shift - 1, div->shift); > + > + ccu_update(ctrl, common, tmp, target_div << div->shift); > + > + if (common->reg_fc) > + ret = ccu_mix_trigger_fc(hw); > + > + return ret; > +} > + > +static u8 ccu_mux_get_parent(struct clk_hw *hw) > +{ > + struct ccu_mix *mix = hw_to_ccu_mix(hw); > + struct ccu_common *common = &mix->common; > + struct ccu_mux_config *mux = &mix->mux; > + u32 reg; > + u8 parent; > + > + ccu_read(ctrl, common, ®); > + > + parent = reg >> 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_common *common = &mix->common; > + struct ccu_mux_config *mux = &mix->mux; > + int ret = 0; > + u32 mask; > + > + mask = GENMASK(mux->width + mux->shift - 1, mux->shift); > + > + ccu_update(ctrl, common, mask, index << mux->shift); > + > + if (common->reg_fc) > + ret = ccu_mix_trigger_fc(hw); > + > + return ret; > +} > + > +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_gate_factor_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_div_mux_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_div_mux_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, > +}; > diff --git a/drivers/clk/spacemit/ccu_mix.h b/drivers/clk/spacemit/ccu_mix.h > new file mode 100644 > index 000000000000..a3aa292d073d > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_mix.h > @@ -0,0 +1,246 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#ifndef _CCU_MIX_H_ > +#define _CCU_MIX_H_ > + > +#include <linux/clk-provider.h> > + > +#include "ccu_common.h" > + > +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) \ > + (&(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) \ > + CLK_HW_INIT_PARENTS_DATA(#_name, _parents, &_ops, _flags) > + > +#define CCU_GATE_DEFINE(_name, _parent, _reg, _gate_mask, _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_gate_ops, _flags), \ > + } \ > +} > + > +#define CCU_FACTOR_DEFINE(_name, _parent, _div, _mul) \ > +struct ccu_mix _name = { \ > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > + .common = { \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_factor_ops, 0), \ > + } \ > +} > + > +#define CCU_MUX_DEFINE(_name, _parents, _reg, _shift, _width, _flags) \ > +struct ccu_mix _name = { \ > + .mux = CCU_MUX_INIT(_shift, _width), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_mux_ops, _flags),\ > + } \ > +} > + > +#define CCU_DIV_DEFINE(_name, _parent, _reg, _shift, _width, _flags) \ > +struct ccu_mix _name = { \ > + .div = CCU_DIV_INIT(_shift, _width), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_div_ops, _flags) \ > + } \ > +} > + > +#define CCU_GATE_FACTOR_DEFINE(_name, _parent, \ > + _reg, \ > + _gate_mask, \ > + _div, _mul, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .factor = CCU_FACTOR_INIT(_div, _mul), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_gate_factor_ops, _flags) \ > + } \ > +} > + > +#define CCU_MUX_GATE_DEFINE(_name, _parents, \ > + _reg, \ > + _shift, _width, \ > + _gate_mask, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .mux = CCU_MUX_INIT(_shift, _width), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_mux_gate_ops, \ > + _flags), \ > + } \ > +} > + > +#define CCU_DIV_GATE_DEFINE(_name, _parent, \ > + _reg, \ > + _shift, _width, \ > + _gate_mask, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .div = CCU_DIV_INIT(_shift, _width), \ > + .common = { \ > + .reg_ctrl = _reg, \ > + .hw.init = CCU_MIX_INITHW(_name, _parent, \ > + spacemit_ccu_div_gate_ops, _flags), \ > + } \ > +} > + > +#define CCU_DIV_MUX_GATE_DEFINE(_name, _parents, \ > + _reg_ctrl, \ > + _mshift, _mwidth, _muxshift, _muxwidth, \ > + _gate_mask, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .div = CCU_DIV_INIT(_mshift, _mwidth), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_gate_ops,\ > + _flags), \ > + }, \ > +} > + > +#define CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents, \ > + _reg_ctrl, _reg_fc, \ > + _mshift, _mwidth, \ > + _fc, \ > + _muxshift, _muxwidth, \ > + _gate_mask, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .gate = CCU_GATE_INIT(_gate_mask), \ > + .div = CCU_DIV_INIT(_mshift, _mwidth), \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_fc, \ > + .fc = _fc, \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_gate_ops,\ > + _flags), \ > + }, \ > +} > + > +#define CCU_DIV_FC_MUX_GATE_DEFINE(_name, _parents, \ > + _reg_ctrl, \ > + _mshift, _mwidth, \ > + _fc, \ > + _muxshift, _muxwidth, \ > + _gate_mask, _flags) \ > +CCU_DIV_SPLIT_FC_MUX_GATE_DEFINE(_name, _parents, _reg_ctrl, _reg_ctrl, \ > + _mshift, _mwidth, _fc, _muxshift, _muxwidth, \ > + _gate_mask, _flags) > + > +#define CCU_DIV_FC_MUX_DEFINE(_name, _parents, \ > + _reg_ctrl, \ > + _mshift, _mwidth, \ > + _fc, \ > + _muxshift, _muxwidth, \ > + _flags) \ > +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, \ > + .fc = _fc, \ > + .hw.init = CCU_MIX_INITHW_PARENTS(_name, _parents, \ > + spacemit_ccu_div_mux_ops, \ > + _flags), \ > + }, \ > +} > + > +#define CCU_MUX_FC_DEFINE(_name, _parents, \ > + _reg_ctrl, \ > + _fc, \ > + _muxshift, _muxwidth, \ > + _flags) \ > +struct ccu_mix _name = { \ > + .mux = CCU_MUX_INIT(_muxshift, _muxwidth), \ > + .common = { \ > + .reg_ctrl = _reg_ctrl, \ > + .reg_fc = _reg_ctrl, \ > + .fc = _fc, \ > + .hw.init = 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, spacemit_ccu_factor_ops; > +extern const struct clk_ops spacemit_ccu_mux_ops, spacemit_ccu_div_ops; > + > +extern const struct clk_ops spacemit_ccu_gate_factor_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_div_mux_ops; > + > +extern const struct clk_ops spacemit_ccu_div_mux_gate_ops; > +#endif /* _CCU_DIV_H_ */ > diff --git a/drivers/clk/spacemit/ccu_pll.c b/drivers/clk/spacemit/ccu_pll.c > new file mode 100644 > index 000000000000..9df2149f6c98 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_pll.c > @@ -0,0 +1,146 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Spacemit clock type pll > + * > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#include <linux/clk-provider.h> > +#include <linux/regmap.h> > + > +#include "ccu_common.h" > +#include "ccu_pll.h" > + > +#define PLL_DELAY_TIME 3000 > + > +#define PLL_SWCR3_EN BIT(31) > + > +static int ccu_pll_is_enabled(struct clk_hw *hw) > +{ > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + u32 tmp; > + > + ccu_read(swcr3, &p->common, &tmp); > + > + return tmp & PLL_SWCR3_EN; > +} > + > +/* frequency unit Mhz, return pll vco freq */ > +static unsigned long ccu_pll_get_vco_freq(struct clk_hw *hw) > +{ > + const struct ccu_pll_rate_tbl *pll_rate_table; > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + struct ccu_common *common = &p->common; > + u32 swcr1, swcr3, size; > + int i; > + > + ccu_read(swcr1, common, &swcr1); > + ccu_read(swcr3, common, &swcr3); > + swcr3 &= ~PLL_SWCR3_EN; > + > + pll_rate_table = p->pll.rate_tbl; > + size = p->pll.tbl_size; > + > + for (i = 0; i < size; i++) { > + if (pll_rate_table[i].swcr1 == swcr1 && > + pll_rate_table[i].swcr3 == swcr3) > + return pll_rate_table[i].rate; > + } > + > + WARN_ON_ONCE(1); > + > + return 0; > +} > + > +static int ccu_pll_enable(struct clk_hw *hw) > +{ > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + struct ccu_common *common = &p->common; > + unsigned int tmp; > + int ret; > + > + if (ccu_pll_is_enabled(hw)) > + return 0; > + > + ccu_update(swcr3, common, PLL_SWCR3_EN, PLL_SWCR3_EN); > + > + /* check lock status */ > + ret = regmap_read_poll_timeout_atomic(common->lock_regmap, > + p->pll.reg_lock, > + tmp, > + tmp & p->pll.lock_enable_bit, > + 5, PLL_DELAY_TIME); > + > + return ret; > +} > + > +static void ccu_pll_disable(struct clk_hw *hw) > +{ > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + struct ccu_common *common = &p->common; > + > + ccu_update(swcr3, common, 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 *p = hw_to_ccu_pll(hw); > + struct ccu_common *common = &p->common; > + struct ccu_pll_config *params = &p->pll; > + const struct ccu_pll_rate_tbl *entry = NULL; > + int i; > + > + for (i = 0; i < params->tbl_size; i++) { > + if (rate == params->rate_tbl[i].rate) { > + entry = ¶ms->rate_tbl[i]; > + break; > + } > + } > + > + if (WARN_ON_ONCE(!entry)) > + return -EINVAL; > + > + ccu_update(swcr1, common, entry->swcr1, entry->swcr1); > + ccu_update(swcr3, common, (u32)~PLL_SWCR3_EN, entry->swcr3); > + > + return 0; > +} > + > +static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + return ccu_pll_get_vco_freq(hw); > +} > + > +static long ccu_pll_round_rate(struct clk_hw *hw, unsigned long rate, > + unsigned long *prate) > +{ > + struct ccu_pll *p = hw_to_ccu_pll(hw); > + struct ccu_pll_config *params = &p->pll; > + unsigned int i; > + > + for (i = 0; i < params->tbl_size; i++) { > + if (params->rate_tbl[i].rate > rate) { > + i--; > + break; > + } > + } > + > + return rate; > +} > + > +const struct clk_ops spacemit_ccu_pll_ops = { > + .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, > +}; > + > diff --git a/drivers/clk/spacemit/ccu_pll.h b/drivers/clk/spacemit/ccu_pll.h > new file mode 100644 > index 000000000000..c6a3a5cce995 > --- /dev/null > +++ b/drivers/clk/spacemit/ccu_pll.h > @@ -0,0 +1,76 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2024 SpacemiT Technology Co. Ltd > + * Copyright (c) 2024 Haylen Chu <heylenay@xxxxxxx> > + */ > + > +#ifndef _CCU_PLL_H_ > +#define _CCU_PLL_H_ > + > +#include <linux/clk-provider.h> > + > +#include "ccu_common.h" > + > +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_size; > + u32 reg_lock; > + u32 lock_enable_bit; > +}; > + > +#define CCU_PLL_RATE(_rate, _swcr1, _swcr3) \ > + { \ > + .rate = _rate, \ > + .swcr1 = _swcr1, \ > + .swcr3 = _swcr3, \ > + } > + > +struct ccu_pll { > + struct ccu_pll_config pll; > + struct ccu_common common; > +}; > + > +#define CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit) \ > + { \ > + .rate_tbl = _table, \ > + .tbl_size = ARRAY_SIZE(_table), \ > + .reg_lock = (_reg_lock), \ > + .lock_enable_bit = (_lock_enable_bit), \ > + } > + > +#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, _lock_enable_bit, _flags) \ > + struct ccu_pll _name = { \ > + .pll = CCU_PLL_CONFIG(_table, _reg_lock, _lock_enable_bit), \ > + .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 > -- > 2.48.1 > > -- Yixun Lan (dlan) Gentoo Linux Developer GPG Key ID AABEFD55