summaryrefslogtreecommitdiffstats
path: root/drivers/clk/clk-fractional-divider.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/clk-fractional-divider.c')
-rw-r--r--drivers/clk/clk-fractional-divider.c136
1 files changed, 89 insertions, 47 deletions
diff --git a/drivers/clk/clk-fractional-divider.c b/drivers/clk/clk-fractional-divider.c
index 1504f7a728..d175921f64 100644
--- a/drivers/clk/clk-fractional-divider.c
+++ b/drivers/clk/clk-fractional-divider.c
@@ -1,89 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 Intel Corporation
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
* 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, clk)
+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 clk;
- void __iomem *reg;
- u8 mshift;
- u32 mmask;
- u8 nshift;
- u32 nmask;
- u8 flags;
-};
+ return readl(fd->reg);
+}
-static unsigned long clk_fd_recalc_rate(struct clk *hw,
+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 long clk_fd_round_rate(struct clk *hw, unsigned long rate,
- unsigned long *prate)
+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 maxn = (fd->nmask >> fd->nshift) + 1;
- unsigned div;
+ 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);
+}
- if (!rate || rate >= *prate)
- return *prate;
+static long clk_fd_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ struct clk *clk = clk_hw_to_clk(hw);
+ struct clk_fractional_divider *fd = to_clk_fd(hw);
+ unsigned long m, n;
+ u64 ret;
- div = gcd(*prate, rate);
+ if (!rate || (!(clk->flags & CLK_SET_RATE_PARENT) && rate >= *parent_rate))
+ return *parent_rate;
- while ((*prate / div) > maxn) {
- div <<= 1;
- rate <<= 1;
- }
+ if (fd->approximation)
+ fd->approximation(hw, rate, parent_rate, &m, &n);
+ else
+ clk_fd_general_approximation(hw, rate, parent_rate, &m, &n);
- return rate;
+ ret = (u64)*parent_rate * m;
+ do_div(ret, n);
+
+ return ret;
}
-static int clk_fd_set_rate(struct clk *hw, unsigned long rate,
+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;
}
@@ -106,22 +146,24 @@ 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->clk.name = name;
- fd->clk.ops = &clk_fractional_divider_ops;
- fd->clk.flags = flags;
- fd->clk.parent_names = parent_name ? &parent_name : NULL;
- fd->clk.num_parents = parent_name ? 1 : 0;
+ fd->hw.clk.name = name;
+ fd->hw.clk.ops = &clk_fractional_divider_ops;
+ fd->hw.clk.flags = flags;
+ fd->hw.clk.parent_names = parent_name ? &parent_name : NULL;
+ fd->hw.clk.num_parents = parent_name ? 1 : 0;
- return &fd->clk;
+ return &fd->hw.clk;
}
void clk_fractional_divider_free(struct clk *clk_fd)
{
- struct clk_fractional_divider *fd = to_clk_fd(clk_fd);
+ struct clk_fractional_divider *fd = to_clk_fd(clk_to_clk_hw(clk_fd));
free(fd);
}
@@ -141,7 +183,7 @@ struct clk *clk_fractional_divider(
if (IS_ERR(fd))
return fd;
- ret = clk_register(fd);
+ ret = bclk_register(fd);
if (ret) {
clk_fractional_divider_free(fd);
return ERR_PTR(ret);