This updates the fractional divider implementation from Linux-5.12.

Signed-off-by: Sascha Hauer <[email protected]>
---
 drivers/clk/clk-fractional-divider.c | 111 +++++++++++++++++++--------
 include/linux/clk.h                  |  44 +++++++++++
 2 files changed, 122 insertions(+), 33 deletions(-)

diff --git a/drivers/clk/clk-fractional-divider.c 
b/drivers/clk/clk-fractional-divider.c
index 65abf84b40..c844fff374 100644
--- a/drivers/clk/clk-fractional-divider.c
+++ b/drivers/clk/clk-fractional-divider.c
@@ -1,86 +1,129 @@
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0
 /*
  * Copyright (C) 2014 Intel Corporation
  *
  * Adjustable fractional divider clock implementation.
  * Output rate = (m / n) * parent_rate.
+ * Uses rational best approximation algorithm.
  */
 
 #include <common.h>
 #include <io.h>
 #include <malloc.h>
 #include <linux/clk.h>
+#include <linux/spinlock.h>
 #include <linux/err.h>
 #include <linux/gcd.h>
 #include <linux/math64.h>
+#include <linux/rational.h>
 #include <linux/barebox-wrapper.h>
 
-#define to_clk_fd(_hw) container_of(_hw, struct clk_fractional_divider, hw)
+static inline u32 clk_fd_readl(struct clk_fractional_divider *fd)
+{
+       if (fd->flags & CLK_FRAC_DIVIDER_BIG_ENDIAN)
+               return ioread32be(fd->reg);
 
-struct clk_fractional_divider {
-       struct clk_hw   hw;
-       void __iomem    *reg;
-       u8              mshift;
-       u32             mmask;
-       u8              nshift;
-       u32             nmask;
-       u8              flags;
-};
+       return readl(fd->reg);
+}
+
+static inline void clk_fd_writel(struct clk_fractional_divider *fd, u32 val)
+{
+       if (fd->flags & CLK_FRAC_DIVIDER_BIG_ENDIAN)
+               iowrite32be(val, fd->reg);
+       else
+               writel(val, fd->reg);
+}
 
 static unsigned long clk_fd_recalc_rate(struct clk_hw *hw,
                                        unsigned long parent_rate)
 {
        struct clk_fractional_divider *fd = to_clk_fd(hw);
-       u32 val, m, n;
+       unsigned long m, n;
+       u32 val;
        u64 ret;
 
-       val = readl(fd->reg);
+       val = clk_fd_readl(fd);
 
        m = (val & fd->mmask) >> fd->mshift;
        n = (val & fd->nmask) >> fd->nshift;
 
+       if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+               m++;
+               n++;
+       }
+
+       if (!n || !m)
+               return parent_rate;
+
        ret = (u64)parent_rate * m;
        do_div(ret, n);
 
        return ret;
 }
 
+static void clk_fd_general_approximation(struct clk_hw *hw, unsigned long rate,
+                                        unsigned long *parent_rate,
+                                        unsigned long *m, unsigned long *n)
+{
+       struct clk_fractional_divider *fd = to_clk_fd(hw);
+       unsigned long scale;
+
+       /*
+        * Get rate closer to *parent_rate to guarantee there is no overflow
+        * for m and n. In the result it will be the nearest rate left shifted
+        * by (scale - fd->nwidth) bits.
+        */
+       scale = fls_long(*parent_rate / rate - 1);
+       if (scale > fd->nwidth)
+               rate <<= scale - fd->nwidth;
+
+       rational_best_approximation(rate, *parent_rate,
+                       GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
+                       m, n);
+}
+
 static long clk_fd_round_rate(struct clk_hw *hw, unsigned long rate,
-                             unsigned long *prate)
+                             unsigned long *parent_rate)
 {
+       struct clk *clk = clk_hw_to_clk(hw);
        struct clk_fractional_divider *fd = to_clk_fd(hw);
-       unsigned maxn = (fd->nmask >> fd->nshift) + 1;
-       unsigned div;
+       unsigned long m, n;
+       u64 ret;
 
-       if (!rate || rate >= *prate)
-               return *prate;
+       if (!rate || (!(clk->flags & CLK_SET_RATE_PARENT) && rate >= 
*parent_rate))
+               return *parent_rate;
 
-       div = gcd(*prate, rate);
+       if (fd->approximation)
+               fd->approximation(clk, rate, parent_rate, &m, &n);
+       else
+               clk_fd_general_approximation(hw, rate, parent_rate, &m, &n);
 
-       while ((*prate / div) > maxn) {
-               div <<= 1;
-               rate <<= 1;
-       }
+       ret = (u64)*parent_rate * m;
+       do_div(ret, n);
 
-       return rate;
+       return ret;
 }
 
 static int clk_fd_set_rate(struct clk_hw *hw, unsigned long rate,
                           unsigned long parent_rate)
 {
        struct clk_fractional_divider *fd = to_clk_fd(hw);
-       unsigned long div;
-       unsigned n, m;
+       unsigned long m, n;
        u32 val;
 
-       div = gcd(parent_rate, rate);
-       m = rate / div;
-       n = parent_rate / div;
+       rational_best_approximation(rate, parent_rate,
+                       GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
+                       &m, &n);
+
+       if (fd->flags & CLK_FRAC_DIVIDER_ZERO_BASED) {
+               m--;
+               n--;
+       }
 
-       val = readl(fd->reg);
+       val = clk_fd_readl(fd);
        val &= ~(fd->mmask | fd->nmask);
        val |= (m << fd->mshift) | (n << fd->nshift);
-       writel(val, fd->reg);
+       clk_fd_writel(fd, val);
 
        return 0;
 }
@@ -103,9 +146,11 @@ struct clk *clk_fractional_divider_alloc(
 
        fd->reg = reg;
        fd->mshift = mshift;
-       fd->mmask = (BIT(mwidth) - 1) << mshift;
+       fd->mwidth = mwidth;
+       fd->mmask = GENMASK(mwidth - 1, 0) << mshift;
        fd->nshift = nshift;
-       fd->nmask = (BIT(nwidth) - 1) << nshift;
+       fd->nwidth = nwidth;
+       fd->nmask = GENMASK(nwidth - 1, 0) << nshift;
        fd->flags = clk_divider_flags;
        fd->hw.clk.name = name;
        fd->hw.clk.ops = &clk_fractional_divider_ops;
diff --git a/include/linux/clk.h b/include/linux/clk.h
index 1c0fa1f50f..b297dfc4f7 100644
--- a/include/linux/clk.h
+++ b/include/linux/clk.h
@@ -498,6 +498,46 @@ extern struct clk_ops clk_fixed_factor_ops;
 struct clk *clk_fixed_factor(const char *name,
                const char *parent, unsigned int mult, unsigned int div,
                unsigned flags);
+
+/**
+ * struct clk_fractional_divider - adjustable fractional divider clock
+ *
+ * @hw:                handle between common and hardware-specific interfaces
+ * @reg:       register containing the divider
+ * @mshift:    shift to the numerator bit field
+ * @mwidth:    width of the numerator bit field
+ * @nshift:    shift to the denominator bit field
+ * @nwidth:    width of the denominator bit field
+ *
+ * Clock with adjustable fractional divider affecting its output frequency.
+ *
+ * Flags:
+ * CLK_FRAC_DIVIDER_ZERO_BASED - by default the numerator and denominator
+ *      is the value read from the register. If CLK_FRAC_DIVIDER_ZERO_BASED
+ *      is set then the numerator and denominator are both the value read
+ *      plus one.
+ * CLK_FRAC_DIVIDER_BIG_ENDIAN - By default little endian register accesses are
+ *      used for the divider register.  Setting this flag makes the register
+ *      accesses big endian.
+ */
+struct clk_fractional_divider {
+       struct clk_hw   hw;
+       void __iomem    *reg;
+       u8              mshift;
+       u8              mwidth;
+       u32             mmask;
+       u8              nshift;
+       u8              nwidth;
+       u32             nmask;
+       u8              flags;
+       void            (*approximation)(struct clk *clk,
+                               unsigned long rate, unsigned long *parent_rate,
+                               unsigned long *m, unsigned long *n);
+};
+
+#define CLK_FRAC_DIVIDER_ZERO_BASED            BIT(0)
+#define CLK_FRAC_DIVIDER_BIG_ENDIAN            BIT(1)
+
 struct clk *clk_fractional_divider_alloc(
                const char *name, const char *parent_name, unsigned long flags,
                void __iomem *reg, u8 mshift, u8 mwidth, u8 nshift, u8 nwidth,
@@ -508,6 +548,10 @@ struct clk *clk_fractional_divider(
                u8 clk_divider_flags);
 void clk_fractional_divider_free(struct clk *clk_fd);
 
+#define to_clk_fd(_hw) container_of(_hw, struct clk_fractional_divider, hw)
+
+extern const struct clk_ops clk_fractional_divider_ops;
+
 struct clk_mux {
        struct clk_hw hw;
        void __iomem *reg;
-- 
2.29.2


_______________________________________________
barebox mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/barebox

Reply via email to