Re: [PATCH v5 3/5] clk: spacemit: Add clock support for Spacemit K1 SoC

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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, &reg);
> +
> +	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, &reg);
> +
> +	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 = &params->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




[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux