clk: microchip: mpfs: re-parent the configurable clocks
authorConor Dooley <conor.dooley@microchip.com>
Wed, 13 Apr 2022 07:58:34 +0000 (08:58 +0100)
committerStephen Boyd <sboyd@kernel.org>
Sat, 23 Apr 2022 01:40:22 +0000 (18:40 -0700)
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: 635e5e73370e ("clk: microchip: Add driver for Microchip PolarFire SoC")
Reviewed-by: Daire McNamara <daire.mcnamara@microchip.com>
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
Link: https://lore.kernel.org/r/20220413075835.3354193-8-conor.dooley@microchip.com
Acked-by: Palmer Dabbelt <palmer@rivosinc.com>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/microchip/clk-mpfs.c

index 7056d6d5b92b52eb8221bc2480b1f385ec247c61..8338a4c154046fbfbd0af585d842786777d26a2d 100644 (file)
 #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;