Commit 445c2da8 authored by Conor Dooley's avatar Conor Dooley Committed by Stephen Boyd
Browse files

clk: microchip: mpfs: re-parent the configurable clocks



Currently the mpfs clock driver uses a reference clock called the
"msspll", set in the device tree, as the parent for the cpu/axi/ahb
(config) clocks. The frequency of the msspll is determined by the FPGA
bitstream & the bootloader configures the clock to match the bitstream.
The real reference is provided by a 100 or 125 MHz off chip oscillator.

However, the msspll clock is not actually the parent of all clocks on
the system - the reference clock for the rtc/mtimer actually has the
off chip oscillator as its parent.

In order to fix this, add support for reading the configuration of the
msspll & reparent the "config" clocks so that they are derived from
this clock rather than the reference in the device tree.

Fixes: 635e5e73 ("clk: microchip: Add driver for Microchip PolarFire SoC")
Reviewed-by: default avatarDaire McNamara <daire.mcnamara@microchip.com>
Signed-off-by: default avatarConor Dooley <conor.dooley@microchip.com>
Link: https://lore.kernel.org/r/20220413075835.3354193-8-conor.dooley@microchip.com


Acked-by: default avatarPalmer Dabbelt <palmer@rivosinc.com>
Signed-off-by: default avatarStephen Boyd <sboyd@kernel.org>
parent 8e8fbab4
Loading
Loading
Loading
Loading
+132 −19
Original line number Diff line number Diff line
@@ -11,20 +11,47 @@
#include <dt-bindings/clock/microchip,mpfs-clock.h>

/* address offset of control registers */
#define REG_MSSPLL_REF_CR	0x08u
#define REG_MSSPLL_POSTDIV_CR	0x10u
#define REG_MSSPLL_SSCG_2_CR	0x2Cu
#define REG_CLOCK_CONFIG_CR	0x08u
#define REG_SUBBLK_CLOCK_CR	0x84u
#define REG_SUBBLK_RESET_CR	0x88u

#define MSSPLL_FBDIV_SHIFT	0x00u
#define MSSPLL_FBDIV_WIDTH	0x0Cu
#define MSSPLL_REFDIV_SHIFT	0x08u
#define MSSPLL_REFDIV_WIDTH	0x06u
#define MSSPLL_POSTDIV_SHIFT	0x08u
#define MSSPLL_POSTDIV_WIDTH	0x07u
#define MSSPLL_FIXED_DIV	4u

struct mpfs_clock_data {
	void __iomem *base;
	void __iomem *msspll_base;
	struct clk_hw_onecell_data hw_data;
};

struct mpfs_msspll_hw_clock {
	void __iomem *base;
	unsigned int id;
	u32 reg_offset;
	u32 shift;
	u32 width;
	u32 flags;
	struct clk_hw hw;
	struct clk_init_data init;
};

#define to_mpfs_msspll_clk(_hw) container_of(_hw, struct mpfs_msspll_hw_clock, hw)

struct mpfs_cfg_clock {
	const struct clk_div_table *table;
	unsigned int id;
	u32 reg_offset;
	u8 shift;
	u8 width;
	u8 flags;
};

struct mpfs_cfg_hw_clock {
@@ -55,7 +82,7 @@ struct mpfs_periph_hw_clock {
 */
static DEFINE_SPINLOCK(mpfs_clk_lock);

static const struct clk_parent_data mpfs_cfg_parent[] = {
static const struct clk_parent_data mpfs_ext_ref[] = {
	{ .index = 0 },
};

@@ -69,6 +96,75 @@ static const struct clk_div_table mpfs_div_ahb_table[] = {
	{ 0, 0 }
};

static unsigned long mpfs_clk_msspll_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
	struct mpfs_msspll_hw_clock *msspll_hw = to_mpfs_msspll_clk(hw);
	void __iomem *mult_addr = msspll_hw->base + msspll_hw->reg_offset;
	void __iomem *ref_div_addr = msspll_hw->base + REG_MSSPLL_REF_CR;
	void __iomem *postdiv_addr = msspll_hw->base + REG_MSSPLL_POSTDIV_CR;
	u32 mult, ref_div, postdiv;

	mult = readl_relaxed(mult_addr) >> MSSPLL_FBDIV_SHIFT;
	mult &= clk_div_mask(MSSPLL_FBDIV_WIDTH);
	ref_div = readl_relaxed(ref_div_addr) >> MSSPLL_REFDIV_SHIFT;
	ref_div &= clk_div_mask(MSSPLL_REFDIV_WIDTH);
	postdiv = readl_relaxed(postdiv_addr) >> MSSPLL_POSTDIV_SHIFT;
	postdiv &= clk_div_mask(MSSPLL_POSTDIV_WIDTH);

	return prate * mult / (ref_div * MSSPLL_FIXED_DIV * postdiv);
}

static const struct clk_ops mpfs_clk_msspll_ops = {
	.recalc_rate = mpfs_clk_msspll_recalc_rate,
};

#define CLK_PLL(_id, _name, _parent, _shift, _width, _flags, _offset) {			\
	.id = _id,									\
	.shift = _shift,								\
	.width = _width,								\
	.reg_offset = _offset,								\
	.flags = _flags,								\
	.hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parent, &mpfs_clk_msspll_ops, 0),	\
}

static struct mpfs_msspll_hw_clock mpfs_msspll_clks[] = {
	CLK_PLL(CLK_MSSPLL, "clk_msspll", mpfs_ext_ref, MSSPLL_FBDIV_SHIFT,
		MSSPLL_FBDIV_WIDTH, 0, REG_MSSPLL_SSCG_2_CR),
};

static int mpfs_clk_register_msspll(struct device *dev, struct mpfs_msspll_hw_clock *msspll_hw,
				    void __iomem *base)
{
	msspll_hw->base = base;

	return devm_clk_hw_register(dev, &msspll_hw->hw);
}

static int mpfs_clk_register_mssplls(struct device *dev, struct mpfs_msspll_hw_clock *msspll_hws,
				     unsigned int num_clks, struct mpfs_clock_data *data)
{
	void __iomem *base = data->msspll_base;
	unsigned int i;
	int ret;

	for (i = 0; i < num_clks; i++) {
		struct mpfs_msspll_hw_clock *msspll_hw = &msspll_hws[i];

		ret = mpfs_clk_register_msspll(dev, msspll_hw, base);
		if (ret)
			return dev_err_probe(dev, ret, "failed to register msspll id: %d\n",
					     CLK_MSSPLL);

		data->hw_data.hws[msspll_hw->id] = &msspll_hw->hw;
	}

	return 0;
}

/*
 * "CFG" clocks
 */

static unsigned long mpfs_cfg_clk_recalc_rate(struct clk_hw *hw, unsigned long prate)
{
	struct mpfs_cfg_hw_clock *cfg_hw = to_mpfs_cfg_clk(hw);
@@ -76,10 +172,10 @@ static unsigned long mpfs_cfg_clk_recalc_rate(struct clk_hw *hw, unsigned long p
	void __iomem *base_addr = cfg_hw->sys_base;
	u32 val;

	val = readl_relaxed(base_addr + REG_CLOCK_CONFIG_CR) >> cfg->shift;
	val = readl_relaxed(base_addr + cfg->reg_offset) >> cfg->shift;
	val &= clk_div_mask(cfg->width);

	return prate / (1u << val);
	return divider_recalc_rate(hw, prate, val, cfg->table, cfg->flags, cfg->width);
}

static long mpfs_cfg_clk_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate)
@@ -105,11 +201,10 @@ static int mpfs_cfg_clk_set_rate(struct clk_hw *hw, unsigned long rate, unsigned
		return divider_setting;

	spin_lock_irqsave(&mpfs_clk_lock, flags);

	val = readl_relaxed(base_addr + REG_CLOCK_CONFIG_CR);
	val = readl_relaxed(base_addr + cfg->reg_offset);
	val &= ~(clk_div_mask(cfg->width) << cfg_hw->cfg.shift);
	val |= divider_setting << cfg->shift;
	writel_relaxed(val, base_addr + REG_CLOCK_CONFIG_CR);
	writel_relaxed(val, base_addr + cfg->reg_offset);

	spin_unlock_irqrestore(&mpfs_clk_lock, flags);

@@ -122,19 +217,23 @@ static const struct clk_ops mpfs_clk_cfg_ops = {
	.set_rate = mpfs_cfg_clk_set_rate,
};

#define CLK_CFG(_id, _name, _parent, _shift, _width, _table, _flags) {		\
#define CLK_CFG(_id, _name, _parent, _shift, _width, _table, _flags, _offset) {		\
	.cfg.id = _id,									\
	.cfg.shift = _shift,								\
	.cfg.width = _width,								\
	.cfg.table = _table,								\
	.hw.init = CLK_HW_INIT_PARENTS_DATA(_name, _parent, &mpfs_clk_cfg_ops,	\
					    _flags),				\
	.cfg.reg_offset = _offset,							\
	.cfg.flags = _flags,								\
	.hw.init = CLK_HW_INIT(_name, _parent, &mpfs_clk_cfg_ops, 0),			\
}

static struct mpfs_cfg_hw_clock mpfs_cfg_clks[] = {
	CLK_CFG(CLK_CPU, "clk_cpu", mpfs_cfg_parent, 0, 2, mpfs_div_cpu_axi_table, 0),
	CLK_CFG(CLK_AXI, "clk_axi", mpfs_cfg_parent, 2, 2, mpfs_div_cpu_axi_table, 0),
	CLK_CFG(CLK_AHB, "clk_ahb", mpfs_cfg_parent, 4, 2, mpfs_div_ahb_table, 0),
	CLK_CFG(CLK_CPU, "clk_cpu", "clk_msspll", 0, 2, mpfs_div_cpu_axi_table, 0,
		REG_CLOCK_CONFIG_CR),
	CLK_CFG(CLK_AXI, "clk_axi", "clk_msspll", 2, 2, mpfs_div_cpu_axi_table, 0,
		REG_CLOCK_CONFIG_CR),
	CLK_CFG(CLK_AHB, "clk_ahb", "clk_msspll", 4, 2, mpfs_div_ahb_table, 0,
		REG_CLOCK_CONFIG_CR),
};

static int mpfs_clk_register_cfg(struct device *dev, struct mpfs_cfg_hw_clock *cfg_hw,
@@ -160,13 +259,17 @@ static int mpfs_clk_register_cfgs(struct device *dev, struct mpfs_cfg_hw_clock *
			return dev_err_probe(dev, ret, "failed to register clock id: %d\n",
					     cfg_hw->cfg.id);

		id = cfg_hws[i].cfg.id;
		id = cfg_hw->cfg.id;
		data->hw_data.hws[id] = &cfg_hw->hw;
	}

	return 0;
}

/*
 * peripheral clocks - devices connected to axi or ahb buses.
 */

static int mpfs_periph_clk_enable(struct clk_hw *hw)
{
	struct mpfs_periph_hw_clock *periph_hw = to_mpfs_periph_clk(hw);
@@ -320,8 +423,9 @@ static int mpfs_clk_probe(struct platform_device *pdev)
	unsigned int num_clks;
	int ret;

	/* CLK_RESERVED is not part of cfg_clks nor periph_clks, so add 1 */
	num_clks = ARRAY_SIZE(mpfs_cfg_clks) + ARRAY_SIZE(mpfs_periph_clks) + 1;
	/* CLK_RESERVED is not part of clock arrays, so add 1 */
	num_clks = ARRAY_SIZE(mpfs_msspll_clks) + ARRAY_SIZE(mpfs_cfg_clks)
		   + ARRAY_SIZE(mpfs_periph_clks) + 1;

	clk_data = devm_kzalloc(dev, struct_size(clk_data, hw_data.hws, num_clks), GFP_KERNEL);
	if (!clk_data)
@@ -331,8 +435,17 @@ static int mpfs_clk_probe(struct platform_device *pdev)
	if (IS_ERR(clk_data->base))
		return PTR_ERR(clk_data->base);

	clk_data->msspll_base = devm_platform_ioremap_resource(pdev, 1);
	if (IS_ERR(clk_data->msspll_base))
		return PTR_ERR(clk_data->msspll_base);

	clk_data->hw_data.num = num_clks;

	ret = mpfs_clk_register_mssplls(dev, mpfs_msspll_clks, ARRAY_SIZE(mpfs_msspll_clks),
					clk_data);
	if (ret)
		return ret;

	ret = mpfs_clk_register_cfgs(dev, mpfs_cfg_clks, ARRAY_SIZE(mpfs_cfg_clks), clk_data);
	if (ret)
		return ret;