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

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.comAcked-by: default avatarPalmer Dabbelt <palmer@rivosinc.com>
Signed-off-by: default avatarStephen Boyd <sboyd@kernel.org>
parent 8e8fbab4
......@@ -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) { \
.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), \
#define CLK_CFG(_id, _name, _parent, _shift, _width, _table, _flags, _offset) { \
.cfg.id = _id, \
.cfg.shift = _shift, \
.cfg.width = _width, \
.cfg.table = _table, \
.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;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment