clk: clk-loongson2: Refactor driver for adding new platforms
authorBinbin Zhou <zhoubinbin@loongson.cn>
Thu, 11 Apr 2024 02:58:07 +0000 (10:58 +0800)
committerStephen Boyd <sboyd@kernel.org>
Thu, 11 Apr 2024 07:30:00 +0000 (00:30 -0700)
The driver only supported loongson-2K1000 at first, but the clock
structure of loongson-2K0500 and loongson-2K2000 are actually similar,
and I tried to refactor the whole driver to adjust to the addition of
the new platform.

Briefly, I have divided all clocks into three categories according to
their properties and their parent clocks: Independent PLLs, clocks based
on frequency scales, and clock dividers.

Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn>
Link: https://lore.kernel.org/r/fb020d1ca19e6f4cdcc95c87b2748869ca76b8ec.1712731524.git.zhoubinbin@loongson.cn
Acked-by: Huacai Chen <chenhuacai@loongson.cn>
[sboyd@kernel.org: Drop clk.h include]
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/clk-loongson2.c

index bacdcbb287ac618b57c236b1103ebbe2c31e7107..749cf66aef2d434125bb601b61f448407040e290 100644 (file)
 #include <linux/io-64-nonatomic-lo-hi.h>
 #include <dt-bindings/clock/loongson,ls2k-clk.h>
 
-#define LOONGSON2_PLL_MULT_SHIFT               32
-#define LOONGSON2_PLL_MULT_WIDTH               10
-#define LOONGSON2_PLL_DIV_SHIFT                        26
-#define LOONGSON2_PLL_DIV_WIDTH                        6
-#define LOONGSON2_APB_FREQSCALE_SHIFT          20
-#define LOONGSON2_APB_FREQSCALE_WIDTH          3
-#define LOONGSON2_USB_FREQSCALE_SHIFT          16
-#define LOONGSON2_USB_FREQSCALE_WIDTH          3
-#define LOONGSON2_SATA_FREQSCALE_SHIFT         12
-#define LOONGSON2_SATA_FREQSCALE_WIDTH         3
-#define LOONGSON2_BOOT_FREQSCALE_SHIFT         8
-#define LOONGSON2_BOOT_FREQSCALE_WIDTH         3
-
-static void __iomem *loongson2_pll_base;
-
 static const struct clk_parent_data pdata[] = {
-       { .fw_name = "ref_100m",},
+       { .fw_name = "ref_100m", },
 };
 
-static struct clk_hw *loongson2_clk_register(struct device *dev,
-                                         const char *name,
-                                         const char *parent_name,
-                                         const struct clk_ops *ops,
-                                         unsigned long flags)
-{
-       int ret;
-       struct clk_hw *hw;
-       struct clk_init_data init = { };
-
-       hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL);
-       if (!hw)
-               return ERR_PTR(-ENOMEM);
-
-       init.name = name;
-       init.ops = ops;
-       init.flags = flags;
-       init.num_parents = 1;
-
-       if (!parent_name)
-               init.parent_data = pdata;
-       else
-               init.parent_names = &parent_name;
-
-       hw->init = &init;
-
-       ret = devm_clk_hw_register(dev, hw);
-       if (ret)
-               hw = ERR_PTR(ret);
-
-       return hw;
-}
-
-static unsigned long loongson2_calc_pll_rate(int offset, unsigned long rate)
-{
-       u64 val;
-       u32 mult, div;
-
-       val = readq(loongson2_pll_base + offset);
-
-       mult = (val >> LOONGSON2_PLL_MULT_SHIFT) &
-                       clk_div_mask(LOONGSON2_PLL_MULT_WIDTH);
-       div = (val >> LOONGSON2_PLL_DIV_SHIFT) &
-                       clk_div_mask(LOONGSON2_PLL_DIV_WIDTH);
-
-       return div_u64((u64)rate * mult, div);
-}
-
-static unsigned long loongson2_node_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
-{
-       return loongson2_calc_pll_rate(0x0, parent_rate);
-}
-
-static const struct clk_ops loongson2_node_clk_ops = {
-       .recalc_rate = loongson2_node_recalc_rate,
+enum loongson2_clk_type {
+       CLK_TYPE_PLL,
+       CLK_TYPE_SCALE,
+       CLK_TYPE_DIVIDER,
+       CLK_TYPE_NONE,
 };
 
-static unsigned long loongson2_ddr_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
-{
-       return loongson2_calc_pll_rate(0x10, parent_rate);
-}
+struct loongson2_clk_provider {
+       void __iomem *base;
+       struct device *dev;
+       struct clk_hw_onecell_data clk_data;
+       spinlock_t clk_lock;    /* protect access to DIV registers */
+};
 
-static const struct clk_ops loongson2_ddr_clk_ops = {
-       .recalc_rate = loongson2_ddr_recalc_rate,
+struct loongson2_clk_data {
+       struct clk_hw hw;
+       void __iomem *reg;
+       u8 div_shift;
+       u8 div_width;
+       u8 mult_shift;
+       u8 mult_width;
 };
 
-static unsigned long loongson2_dc_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
-{
-       return loongson2_calc_pll_rate(0x20, parent_rate);
-}
+struct loongson2_clk_board_info {
+       u8 id;
+       enum loongson2_clk_type type;
+       const char *name;
+       const char *parent_name;
+       u8 reg_offset;
+       u8 div_shift;
+       u8 div_width;
+       u8 mult_shift;
+       u8 mult_width;
+};
 
-static const struct clk_ops loongson2_dc_clk_ops = {
-       .recalc_rate = loongson2_dc_recalc_rate,
+#define CLK_DIV(_id, _name, _pname, _offset, _dshift, _dwidth) \
+       {                                                       \
+               .id             = _id,                          \
+               .type           = CLK_TYPE_DIVIDER,             \
+               .name           = _name,                        \
+               .parent_name    = _pname,                       \
+               .reg_offset     = _offset,                      \
+               .div_shift      = _dshift,                      \
+               .div_width      = _dwidth,                      \
+       }
+
+#define CLK_PLL(_id, _name, _offset, _mshift, _mwidth,         \
+               _dshift, _dwidth)                               \
+       {                                                       \
+               .id             = _id,                          \
+               .type           = CLK_TYPE_PLL,                 \
+               .name           = _name,                        \
+               .parent_name    = NULL,                         \
+               .reg_offset     = _offset,                      \
+               .mult_shift     = _mshift,                      \
+               .mult_width     = _mwidth,                      \
+               .div_shift      = _dshift,                      \
+               .div_width      = _dwidth,                      \
+       }
+
+#define CLK_SCALE(_id, _name, _pname, _offset,                 \
+                 _dshift, _dwidth)                             \
+       {                                                       \
+               .id             = _id,                          \
+               .type           = CLK_TYPE_SCALE,               \
+               .name           = _name,                        \
+               .parent_name    = _pname,                       \
+               .reg_offset     = _offset,                      \
+               .div_shift      = _dshift,                      \
+               .div_width      = _dwidth,                      \
+       }
+
+static const struct loongson2_clk_board_info ls2k1000_clks[] = {
+       CLK_PLL(LOONGSON2_NODE_PLL,   "pll_node", 0,    32, 10, 26, 6),
+       CLK_PLL(LOONGSON2_DDR_PLL,    "pll_ddr",  0x10, 32, 10, 26, 6),
+       CLK_PLL(LOONGSON2_DC_PLL,     "pll_dc",   0x20, 32, 10, 26, 6),
+       CLK_PLL(LOONGSON2_PIX0_PLL,   "pll_pix0", 0x30, 32, 10, 26, 6),
+       CLK_PLL(LOONGSON2_PIX1_PLL,   "pll_pix1", 0x40, 32, 10, 26, 6),
+       CLK_DIV(LOONGSON2_NODE_CLK,   "clk_node", "pll_node", 0x8,  0,  6),
+       CLK_DIV(LOONGSON2_DDR_CLK,    "clk_ddr",  "pll_ddr",  0x18, 0,  6),
+       CLK_DIV(LOONGSON2_GPU_CLK,    "clk_gpu",  "pll_ddr",  0x18, 22, 6),
+       /*
+        * The hda clk divisor in the upper 32bits and the clk-prodiver
+        * layer code doesn't support 64bit io operation thus a conversion
+        * is required that subtract shift by 32 and add 4byte to the hda
+        * address
+        */
+       CLK_DIV(LOONGSON2_HDA_CLK,    "clk_hda",  "pll_ddr",  0x22, 12, 7),
+       CLK_DIV(LOONGSON2_DC_CLK,     "clk_dc",   "pll_dc",   0x28, 0,  6),
+       CLK_DIV(LOONGSON2_GMAC_CLK,   "clk_gmac", "pll_dc",   0x28, 22, 6),
+       CLK_DIV(LOONGSON2_PIX0_CLK,   "clk_pix0", "pll_pix0", 0x38, 0,  6),
+       CLK_DIV(LOONGSON2_PIX1_CLK,   "clk_pix1", "pll_pix1", 0x38, 0,  6),
+       CLK_SCALE(LOONGSON2_BOOT_CLK, "clk_boot", NULL,       0x50, 8,  3),
+       CLK_SCALE(LOONGSON2_SATA_CLK, "clk_sata", "clk_gmac", 0x50, 12, 3),
+       CLK_SCALE(LOONGSON2_USB_CLK,  "clk_usb",  "clk_gmac", 0x50, 16, 3),
+       CLK_SCALE(LOONGSON2_APB_CLK,  "clk_apb",  "clk_gmac", 0x50, 20, 3),
+       { /* Sentinel */ },
 };
 
-static unsigned long loongson2_pix0_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
+static inline struct loongson2_clk_data *to_loongson2_clk(struct clk_hw *hw)
 {
-       return loongson2_calc_pll_rate(0x30, parent_rate);
+       return container_of(hw, struct loongson2_clk_data, hw);
 }
 
-static const struct clk_ops loongson2_pix0_clk_ops = {
-       .recalc_rate = loongson2_pix0_recalc_rate,
-};
-
-static unsigned long loongson2_pix1_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
+static inline unsigned long loongson2_rate_part(u64 val, u8 shift, u8 width)
 {
-       return loongson2_calc_pll_rate(0x40, parent_rate);
+       return (val & GENMASK(shift + width - 1, shift)) >> shift;
 }
 
-static const struct clk_ops loongson2_pix1_clk_ops = {
-       .recalc_rate = loongson2_pix1_recalc_rate,
-};
-
-static unsigned long loongson2_calc_rate(unsigned long rate,
-                                        int shift, int width)
+static unsigned long loongson2_pll_recalc_rate(struct clk_hw *hw,
+                                              unsigned long parent_rate)
 {
-       u64 val;
-       u32 mult;
-
-       val = readq(loongson2_pll_base + 0x50);
+       u64 val, mult, div;
+       struct loongson2_clk_data *clk = to_loongson2_clk(hw);
 
-       mult = (val >> shift) & clk_div_mask(width);
+       val  = readq(clk->reg);
+       mult = loongson2_rate_part(val, clk->mult_shift, clk->mult_width);
+       div  = loongson2_rate_part(val, clk->div_shift,  clk->div_width);
 
-       return div_u64((u64)rate * (mult + 1), 8);
-}
-
-static unsigned long loongson2_boot_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
-{
-       return loongson2_calc_rate(parent_rate,
-                                  LOONGSON2_BOOT_FREQSCALE_SHIFT,
-                                  LOONGSON2_BOOT_FREQSCALE_WIDTH);
+       return div_u64((u64)parent_rate * mult, div);
 }
 
-static const struct clk_ops loongson2_boot_clk_ops = {
-       .recalc_rate = loongson2_boot_recalc_rate,
+static const struct clk_ops loongson2_pll_recalc_ops = {
+       .recalc_rate = loongson2_pll_recalc_rate,
 };
 
-static unsigned long loongson2_apb_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
+static unsigned long loongson2_freqscale_recalc_rate(struct clk_hw *hw,
+                                                    unsigned long parent_rate)
 {
-       return loongson2_calc_rate(parent_rate,
-                                  LOONGSON2_APB_FREQSCALE_SHIFT,
-                                  LOONGSON2_APB_FREQSCALE_WIDTH);
-}
+       u64 val, mult;
+       struct loongson2_clk_data *clk = to_loongson2_clk(hw);
 
-static const struct clk_ops loongson2_apb_clk_ops = {
-       .recalc_rate = loongson2_apb_recalc_rate,
-};
+       val  = readq(clk->reg);
+       mult = loongson2_rate_part(val, clk->div_shift, clk->div_width) + 1;
 
-static unsigned long loongson2_usb_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
-{
-       return loongson2_calc_rate(parent_rate,
-                                  LOONGSON2_USB_FREQSCALE_SHIFT,
-                                  LOONGSON2_USB_FREQSCALE_WIDTH);
+       return div_u64((u64)parent_rate * mult, 8);
 }
 
-static const struct clk_ops loongson2_usb_clk_ops = {
-       .recalc_rate = loongson2_usb_recalc_rate,
+static const struct clk_ops loongson2_freqscale_recalc_ops = {
+       .recalc_rate = loongson2_freqscale_recalc_rate,
 };
 
-static unsigned long loongson2_sata_recalc_rate(struct clk_hw *hw,
-                                         unsigned long parent_rate)
+static struct clk_hw *loongson2_clk_register(struct loongson2_clk_provider *clp,
+                                            const struct loongson2_clk_board_info *cld,
+                                            const struct clk_ops *ops)
 {
-       return loongson2_calc_rate(parent_rate,
-                                  LOONGSON2_SATA_FREQSCALE_SHIFT,
-                                  LOONGSON2_SATA_FREQSCALE_WIDTH);
-}
+       int ret;
+       struct clk_hw *hw;
+       struct loongson2_clk_data *clk;
+       struct clk_init_data init = { };
 
-static const struct clk_ops loongson2_sata_clk_ops = {
-       .recalc_rate = loongson2_sata_recalc_rate,
-};
+       clk = devm_kzalloc(clp->dev, sizeof(*clk), GFP_KERNEL);
+       if (!clk)
+               return ERR_PTR(-ENOMEM);
 
-static inline int loongson2_check_clk_hws(struct clk_hw *clks[], unsigned int count)
-{
-       unsigned int i;
+       init.name  = cld->name;
+       init.ops   = ops;
+       init.flags = 0;
+       init.num_parents = 1;
 
-       for (i = 0; i < count; i++)
-               if (IS_ERR(clks[i])) {
-                       pr_err("Loongson2 clk %u: register failed with %ld\n",
-                               i, PTR_ERR(clks[i]));
-                       return PTR_ERR(clks[i]);
-               }
+       if (!cld->parent_name)
+               init.parent_data = pdata;
+       else
+               init.parent_names = &cld->parent_name;
+
+       clk->reg        = clp->base + cld->reg_offset;
+       clk->div_shift  = cld->div_shift;
+       clk->div_width  = cld->div_width;
+       clk->mult_shift = cld->mult_shift;
+       clk->mult_width = cld->mult_width;
+       clk->hw.init    = &init;
 
-       return 0;
+       hw = &clk->hw;
+       ret = devm_clk_hw_register(clp->dev, hw);
+       if (ret)
+               clk = ERR_PTR(ret);
+
+       return hw;
 }
 
 static int loongson2_clk_probe(struct platform_device *pdev)
 {
-       int ret;
-       struct clk_hw **hws;
-       struct clk_hw_onecell_data *clk_hw_data;
-       spinlock_t loongson2_clk_lock;
+       int i, clks_num = 0;
+       struct clk_hw *hw;
        struct device *dev = &pdev->dev;
+       struct loongson2_clk_provider *clp;
+       const struct loongson2_clk_board_info *p, *data;
 
-       loongson2_pll_base = devm_platform_ioremap_resource(pdev, 0);
-       if (IS_ERR(loongson2_pll_base))
-               return PTR_ERR(loongson2_pll_base);
-
-       clk_hw_data = devm_kzalloc(dev, struct_size(clk_hw_data, hws, LOONGSON2_CLK_END),
-                                       GFP_KERNEL);
-       if (WARN_ON(!clk_hw_data))
-               return -ENOMEM;
-
-       clk_hw_data->num = LOONGSON2_CLK_END;
-       hws = clk_hw_data->hws;
-
-       hws[LOONGSON2_NODE_PLL] = loongson2_clk_register(dev, "node_pll",
-                                               NULL,
-                                               &loongson2_node_clk_ops, 0);
-
-       hws[LOONGSON2_DDR_PLL] = loongson2_clk_register(dev, "ddr_pll",
-                                               NULL,
-                                               &loongson2_ddr_clk_ops, 0);
+       data = device_get_match_data(dev);
+       if (!data)
+               return -EINVAL;
 
-       hws[LOONGSON2_DC_PLL] = loongson2_clk_register(dev, "dc_pll",
-                                               NULL,
-                                               &loongson2_dc_clk_ops, 0);
+       for (p = data; p->name; p++)
+               clks_num++;
 
-       hws[LOONGSON2_PIX0_PLL] = loongson2_clk_register(dev, "pix0_pll",
-                                               NULL,
-                                               &loongson2_pix0_clk_ops, 0);
-
-       hws[LOONGSON2_PIX1_PLL] = loongson2_clk_register(dev, "pix1_pll",
-                                               NULL,
-                                               &loongson2_pix1_clk_ops, 0);
+       clp = devm_kzalloc(dev, struct_size(clp, clk_data.hws, clks_num),
+                          GFP_KERNEL);
+       if (!clp)
+               return -ENOMEM;
 
-       hws[LOONGSON2_BOOT_CLK] = loongson2_clk_register(dev, "boot",
-                                               NULL,
-                                               &loongson2_boot_clk_ops, 0);
+       clp->base = devm_platform_ioremap_resource(pdev, 0);
+       if (IS_ERR(clp->base))
+               return PTR_ERR(clp->base);
+
+       spin_lock_init(&clp->clk_lock);
+       clp->clk_data.num = clks_num + 1;
+       clp->dev = dev;
+
+       for (i = 0; i < clks_num; i++) {
+               p = &data[i];
+               switch (p->type) {
+               case CLK_TYPE_PLL:
+                       hw = loongson2_clk_register(clp, p,
+                                                   &loongson2_pll_recalc_ops);
+                       break;
+               case CLK_TYPE_SCALE:
+                       hw = loongson2_clk_register(clp, p,
+                                                   &loongson2_freqscale_recalc_ops);
+                       break;
+               case CLK_TYPE_DIVIDER:
+                       hw = devm_clk_hw_register_divider(dev, p->name,
+                                                         p->parent_name, 0,
+                                                         clp->base + p->reg_offset,
+                                                         p->div_shift, p->div_width,
+                                                         CLK_DIVIDER_ONE_BASED,
+                                                         &clp->clk_lock);
+                       break;
+               default:
+                       return dev_err_probe(dev, -EINVAL, "Invalid clk type\n");
+               }
 
-       hws[LOONGSON2_NODE_CLK] = devm_clk_hw_register_divider(dev, "node",
-                                               "node_pll", 0,
-                                               loongson2_pll_base + 0x8, 0,
-                                               6, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
+               if (IS_ERR(hw))
+                       return dev_err_probe(dev, PTR_ERR(hw),
+                                            "Register clk: %s, type: %u failed!\n",
+                                            p->name, p->type);
 
-       /*
-        * The hda clk divisor in the upper 32bits and the clk-prodiver
-        * layer code doesn't support 64bit io operation thus a conversion
-        * is required that subtract shift by 32 and add 4byte to the hda
-        * address
-        */
-       hws[LOONGSON2_HDA_CLK] = devm_clk_hw_register_divider(dev, "hda",
-                                               "ddr_pll", 0,
-                                               loongson2_pll_base + 0x22, 12,
-                                               7, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_GPU_CLK] = devm_clk_hw_register_divider(dev, "gpu",
-                                               "ddr_pll", 0,
-                                               loongson2_pll_base + 0x18, 22,
-                                               6, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_DDR_CLK] = devm_clk_hw_register_divider(dev, "ddr",
-                                               "ddr_pll", 0,
-                                               loongson2_pll_base + 0x18, 0,
-                                               6, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_GMAC_CLK] = devm_clk_hw_register_divider(dev, "gmac",
-                                               "dc_pll", 0,
-                                               loongson2_pll_base + 0x28, 22,
-                                               6, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_DC_CLK] = devm_clk_hw_register_divider(dev, "dc",
-                                               "dc_pll", 0,
-                                               loongson2_pll_base + 0x28, 0,
-                                               6, CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_APB_CLK] = loongson2_clk_register(dev, "apb",
-                                               "gmac",
-                                               &loongson2_apb_clk_ops, 0);
-
-       hws[LOONGSON2_USB_CLK] = loongson2_clk_register(dev, "usb",
-                                               "gmac",
-                                               &loongson2_usb_clk_ops, 0);
-
-       hws[LOONGSON2_SATA_CLK] = loongson2_clk_register(dev, "sata",
-                                               "gmac",
-                                               &loongson2_sata_clk_ops, 0);
-
-       hws[LOONGSON2_PIX0_CLK] = clk_hw_register_divider(NULL, "pix0",
-                                               "pix0_pll", 0,
-                                               loongson2_pll_base + 0x38, 0, 6,
-                                               CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       hws[LOONGSON2_PIX1_CLK] = clk_hw_register_divider(NULL, "pix1",
-                                               "pix1_pll", 0,
-                                               loongson2_pll_base + 0x48, 0, 6,
-                                               CLK_DIVIDER_ONE_BASED,
-                                               &loongson2_clk_lock);
-
-       ret = loongson2_check_clk_hws(hws, LOONGSON2_CLK_END);
-       if (ret)
-               return ret;
+               clp->clk_data.hws[p->id] = hw;
+       }
 
-       return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, clk_hw_data);
+       return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, &clp->clk_data);
 }
 
 static const struct of_device_id loongson2_clk_match_table[] = {
-       { .compatible = "loongson,ls2k-clk" },
+       { .compatible = "loongson,ls2k-clk", .data = &ls2k1000_clks },
        { }
 };
 MODULE_DEVICE_TABLE(of, loongson2_clk_match_table);
@@ -338,4 +275,5 @@ static struct platform_driver loongson2_clk_driver = {
 module_platform_driver(loongson2_clk_driver);
 
 MODULE_DESCRIPTION("Loongson2 clock driver");
+MODULE_AUTHOR("Loongson Technology Corporation Limited");
 MODULE_LICENSE("GPL");