Most of the clocks in the Allwinner's SoCs are configured in the CCU
(Clock Configuration Unit).

The PLL clocks are driven from the main clock. Their rates are controlled
by a set of multiply and divide factors, named from the Allwinner's
documentation:
- multipliers: 'n' and 'k'
- dividers: 'd1', 'd2', 'm' and 'p'

The peripheral clocks may receive their inputs from one or more parents,
thanks to a mux. Their rates are controlled by a set of divide factors
only, named 'm' and 'p'.

This driver also handles:
- fixed clocks,
- the phase delays for the MMCs,
- the clock gates,
- the bus gates,
- and the resets.

Signed-off-by: Jean-Francois Moine <[email protected]>
---
 drivers/clk/sunxi/Makefile |   2 +
 drivers/clk/sunxi/ccu.c    | 980 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/sunxi/ccu.h    | 153 +++++++
 3 files changed, 1135 insertions(+)
 create mode 100644 drivers/clk/sunxi/ccu.c
 create mode 100644 drivers/clk/sunxi/ccu.h

diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index 39d2044..b8ca3e2 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -26,3 +26,5 @@ obj-$(CONFIG_MACH_SUN9I) += clk-sun9i-cpus.o
 obj-$(CONFIG_MFD_SUN6I_PRCM) += \
        clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \
        clk-sun8i-apb0.o
+
+obj-y += ccu.o
diff --git a/drivers/clk/sunxi/ccu.c b/drivers/clk/sunxi/ccu.c
new file mode 100644
index 0000000..5749f9c
--- /dev/null
+++ b/drivers/clk/sunxi/ccu.c
@@ -0,0 +1,980 @@
+/*
+ * Allwinner system CCU
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <[email protected]>
+ * Rewrite from 'sunxi-ng':
+ * Copyright (C) 2016 Maxime Ripard <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/reset-controller.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+#include <linux/rational.h>
+#include <linux/of_address.h>
+
+#include "ccu.h"
+
+#define CCU_DEBUG 0
+
+#define CCU_MASK(shift, width) (((1 << width) - 1) << shift)
+
+/*
+ * factors:
+ *     n: multiplier (PLL)
+ *     d1, d2: boolean dividers by 2 (d2 is p with 1 bit width - PLL)
+ *     k: multiplier (PLL)
+ *     m: divider
+ *     p: divider by power of 2
+ */
+struct values {
+       int n, d1, k, m, p;
+};
+
+static DEFINE_SPINLOCK(ccu_lock);
+
+void ccu_set_clock(struct ccu *ccu, int reg, u32 mask, u32 val)
+{
+
+#if CCU_DEBUG
+       pr_info("** ccu %s set %03x %08x\n",
+               clk_hw_get_name(&ccu->hw), reg,
+               (readl(ccu->base + reg) & ~mask) | val);
+#endif
+       spin_lock(&ccu_lock);
+       writel((readl(ccu->base + reg) & ~mask) | val, ccu->base + reg);
+       spin_unlock(&ccu_lock);
+}
+
+/* --- prepare / enable --- */
+int ccu_prepare(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (!ccu->reset_reg && !ccu->bus_reg)
+               return 0;
+
+#if CCU_DEBUG
+       pr_info("** ccu %s prepare\n", clk_hw_get_name(&ccu->hw));
+#endif
+       spin_lock(&ccu_lock);
+       if (ccu->reset_reg)
+               writel(readl(ccu->base + ccu->reset_reg) |
+                                                       BIT(ccu->reset_bit),
+                               ccu->base + ccu->reset_reg);
+       if (ccu->bus_reg)
+               writel(readl(ccu->base + ccu->bus_reg) | BIT(ccu->bus_bit),
+                               ccu->base + ccu->bus_reg);
+       spin_unlock(&ccu_lock);
+
+       return 0;
+}
+
+void ccu_unprepare(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (!ccu->reset_reg && !ccu->bus_reg)
+               return;
+
+#if CCU_DEBUG
+       pr_info("** ccu %s unprepare\n", clk_hw_get_name(&ccu->hw));
+#endif
+       spin_lock(&ccu_lock);
+       if (ccu->bus_reg)
+               writel(readl(ccu->base + ccu->bus_reg) & ~BIT(ccu->bus_bit),
+                               ccu->base + ccu->bus_reg);
+       if (ccu->reset_reg)
+               writel(readl(ccu->base + ccu->reset_reg) &
+                                                       ~BIT(ccu->reset_bit),
+                               ccu->base + ccu->reset_reg);
+       spin_unlock(&ccu_lock);
+}
+
+int ccu_enable(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (!ccu->has_gate)
+               return 0;
+
+#if CCU_DEBUG
+       pr_info("** ccu %s enable\n", clk_hw_get_name(&ccu->hw));
+#endif
+       spin_lock(&ccu_lock);
+       writel(readl(ccu->base + ccu->reg) | BIT(ccu->gate_bit),
+                               ccu->base + ccu->reg);
+       spin_unlock(&ccu_lock);
+
+       return 0;
+}
+
+void ccu_disable(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (!ccu->has_gate)
+               return;
+
+#if CCU_DEBUG
+       pr_info("** ccu %s disable\n", clk_hw_get_name(&ccu->hw));
+#endif
+       spin_lock(&ccu_lock);
+       writel(readl(ccu->base + ccu->reg) & ~BIT(ccu->gate_bit),
+                               ccu->base + ccu->reg);
+       spin_unlock(&ccu_lock);
+}
+
+/* --- PLL --- */
+static int ccu_pll_find_best(struct ccu *ccu,
+                               unsigned long rate,
+                               unsigned long parent_rate,
+                               struct values *p_v)
+{
+       int max_mul, max_div, mul, div, t;
+       int n = 1, d1 = 1, k = 1, m = 1, p = 0;
+       int max_n = 1 << ccu->n_width;
+       int max_d1 = 1 << ccu->d1_width;
+       int max_k = 1 << ccu->k_width;
+       int max_m = 1 << ccu->m_width;
+       int max_p = 1 << ccu->p_width;
+
+       if (ccu->features & CCU_FEATURE_N0)
+               max_n--;
+
+       /* compute n */
+       if (max_n > 1) {
+               max_mul = max_n * max_k;
+               if (rate > parent_rate * max_mul) {
+                       pr_err("%s: Clock rate too high %ld > %ld * %d * %d\n",
+                               clk_hw_get_name(&ccu->hw),
+                               rate, parent_rate, max_n, max_k);
+                       return -EINVAL;
+               }
+               max_div = max_m * max_d1 << max_p;
+               if (max_div > 1) {
+                       unsigned long lmul, ldiv;
+
+                       rational_best_approximation(rate, parent_rate,
+                                               max_mul - 1,
+                                               max_div - 1,
+                                               &lmul, &ldiv);
+                       mul = lmul;
+                       div = ldiv;
+                       if (ccu->n_min && mul < ccu->n_min) {
+                               t = (ccu->n_min + mul - 1) / mul;
+                               mul *= t;
+                               div *= t;
+                       }
+               } else {
+                       mul = (rate + parent_rate - 1) / parent_rate;
+                       div = 1;
+               }
+
+               /* compute k (present only when 'n' is present) */
+               if (max_k > 1) {
+                       int k_min, k_opt, delta_opt = 100, delta;
+
+                       k = (mul + max_n - 1) / max_n;
+                       k_opt = k_min = k;
+                       for (k = max_k; k > k_min; k--) {
+                               n = (mul + k - 1) / k;
+                               t = n * k;
+                               delta = t - mul;
+                               if (delta == 0) {
+                                       k_opt = k;
+                                       break;
+                               }
+                               if (delta < 0)
+                                       delta = -delta;
+                               if (delta < delta_opt) {
+                                       delta_opt = delta;
+                                       k_opt = k;
+                               }
+                       }
+                       k = k_opt;
+                       n = (mul + k - 1) / k;
+               } else {
+                       n = mul;
+               }
+       } else {
+               div = (parent_rate + rate - 1) / rate;
+       }
+
+       /* compute d1 (value is only 1 or 2) */
+       if (max_d1 > 1) {
+               if (div % 2 == 0) {
+                       d1 = 2;
+                       div /= 2;
+               }
+       }
+
+       /* compute p */
+/*     p = 0; */
+       while (div % 2 == 0 && p <= max_p) {
+               p++;
+               div /= 2;
+       }
+
+       /* compute m */
+       if (max_m > 1) {
+               if (div <= max_m)
+                       m = div;
+               else
+                       m = max_m;
+               div /= m;
+       }
+
+       /* adjust n */
+       n = DIV_ROUND_CLOSEST((rate << p) * m * d1, parent_rate);
+       n = DIV_ROUND_CLOSEST(n, k);
+
+       p_v->n = n;
+       p_v->d1 = d1;
+       p_v->k = k;
+       p_v->m = m;
+       p_v->p = p;
+
+       return 0;
+}
+
+static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
+                                       unsigned long parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       const struct ccu_extra *extra = ccu->extra;
+       unsigned long rate;
+       int i, n, d1, m, k, p;
+       u32 reg;
+
+       reg = readl(ccu->base + ccu->reg);
+
+       if (extra) {
+               for (i = 0; i < extra->num_frac - 1; i++) {
+                       if ((reg & extra->frac[i].mask) == extra->frac[i].val)
+                               return rate = extra->frac[i].rate;
+               }
+       }
+
+       rate = parent_rate;
+
+       if (ccu->d1_width) {
+               d1 = reg >> ccu->d1_shift;
+               d1 &= (1 << ccu->d1_width) - 1;
+               rate /= (d1 + 1);
+       }
+
+       if (ccu->n_width) {
+               n = reg >> ccu->n_shift;
+               n &= (1 << ccu->n_width) - 1;
+               if (!(ccu->features & CCU_FEATURE_N0))
+                       n++;
+               rate *= n;
+       }
+
+       if (ccu->m_width) {
+               m = reg >> ccu->m_shift;
+               m &= (1 << ccu->m_width) - 1;
+               rate /= (m + 1);
+       }
+
+       if (ccu->k_width) {
+               k = reg >> ccu->k_shift;
+               k &= (1 << ccu->k_width) - 1;
+               rate *= (k + 1);
+       }
+
+       if (ccu->p_width) {
+               p = reg >> ccu->p_shift;
+               p &= (1 << ccu->p_width) - 1;
+               rate >>= p;
+       }
+
+       if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+               rate /= extra->fixed_div[0];
+
+       return rate;
+}
+
+static long ccu_pll_round_rate(struct clk_hw *hw,
+                               unsigned long rate,
+                               unsigned long *parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       const struct ccu_extra *extra = ccu->extra;
+       struct values v;
+       int i, ret;
+
+       if (extra) {
+               for (i = 0; i < extra->num_frac - 1; i++) {
+                       if (extra->frac[i].rate == rate)
+                               return rate;
+               }
+
+               if (ccu->features & CCU_FEATURE_FIXED_POSTDIV)
+                       rate *= extra->fixed_div[0];
+       }
+
+       ret = ccu_pll_find_best(ccu, rate, *parent_rate, &v);
+       if (ret)
+               return ret;
+
+       rate = *parent_rate / v.d1 * v.n / v.m * v.k >> v.p;
+
+       if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+               rate /= extra->fixed_div[0];
+
+       return rate;
+}
+
+static void ccu_pll_set_flat_factors(struct ccu *ccu, u32 mask, u32 val)
+{
+       u32 reg, m_val, p_val;
+       u32 m_mask = (1 << ccu->m_width) - 1;
+       u32 p_mask = (1 << ccu->p_width) - 1;
+
+       reg = readl(ccu->base + ccu->reg);
+       m_val = reg & m_mask;
+       p_val = reg & p_mask;
+
+       spin_lock(&ccu_lock);
+
+       /* increase p, then m */
+       if (ccu->p_width && p_val < (val & p_mask)) {
+               reg &= ~p_mask;
+               reg |= val & p_mask;
+               writel(reg, ccu->base + ccu->reg);
+               udelay(10);
+       }
+       if (ccu->m_width && m_val < (val & m_mask)) {
+               reg &= ~m_mask;
+               reg |= val & m_mask;
+               writel(reg, ccu->base + ccu->reg);
+               udelay(10);
+       }
+
+       /* set other factors */
+       reg &= ~(mask & ~(p_mask | m_mask));
+       reg |= val & ~(p_mask | m_mask);
+       writel(reg, ccu->base + ccu->reg);
+
+       /* decrease m */
+       if (ccu->m_width && m_val > (val & m_mask)) {
+               reg &= ~m_mask;
+               reg |= val & m_mask;
+               writel(reg, ccu->base + ccu->reg);
+               udelay(10);
+       }
+
+       /* wait for PLL stable */
+       if (ccu->lock_reg) {
+               u32 lock;
+
+               lock = BIT(ccu->lock_bit);
+               WARN_ON(readl_relaxed_poll_timeout(ccu->base + ccu->lock_reg,
+                                                       reg, !(reg & lock),
+                                                       100, 70000));
+       }
+
+       /* decrease p */
+       if (ccu->p_width && p_val > (val & p_mask)) {
+               reg &= ~p_mask;
+               reg |= val & p_mask;
+               writel(reg, ccu->base + ccu->reg);
+               udelay(10);
+       }
+
+       spin_unlock(&ccu_lock);
+}
+
+static int ccu_pll_set_rate(struct clk_hw *hw,
+                               unsigned long rate,
+                               unsigned long parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       const struct ccu_extra *extra = ccu->extra;
+       struct values v;
+       u32 mask, val;
+       int ret;
+
+       mask =  CCU_MASK(ccu->n_shift, ccu->n_width) |
+               CCU_MASK(ccu->d1_shift, ccu->d1_width) |
+               CCU_MASK(ccu->k_shift, ccu->k_width) |
+               CCU_MASK(ccu->m_shift, ccu->m_width) |
+               CCU_MASK(ccu->p_shift, ccu->p_width);
+       val = 0;
+
+       if (extra && extra->num_frac) {
+               int i;
+
+               for (i = 0; i < extra->num_frac - 1; i++) {
+                       if (extra->frac[i].rate == rate) {
+                               ccu_set_clock(ccu, ccu->reg,
+                                               extra->frac[i].mask,
+                                               extra->frac[i].val);
+                               return 0;
+                       }
+               }
+               mask |= extra->frac[i].mask;
+               val |= extra->frac[i].val;
+       }
+
+       if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+               rate *= extra->fixed_div[0];
+
+
+       ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+       if (ret)
+               return ret;
+
+       if (!(ccu->features & CCU_FEATURE_N0))
+               v.n--;
+
+       val |=  (v.n << ccu->n_shift) |
+               ((v.d1 - 1) << ccu->d1_shift) |
+               ((v.k - 1) << ccu->k_shift) |
+               ((v.m - 1) << ccu->m_shift) |
+               (v.p << ccu->p_shift);
+
+       if (ccu->upd_bit)                               /* cannot be 0 */
+               val |= BIT(ccu->upd_bit);
+
+       if (!(ccu->features & CCU_FEATURE_FLAT_FACTORS))
+               ccu_set_clock(ccu, ccu->reg, mask, val);
+       else
+               ccu_pll_set_flat_factors(ccu, mask, val);
+
+       /* wait for PLL stable */
+       if (ccu->lock_reg) {
+               u32 lock, reg;
+
+               lock = BIT(ccu->lock_bit);
+               WARN_ON(readl_relaxed_poll_timeout(ccu->base + ccu->lock_reg,
+                                                       reg, !(reg & lock),
+                                                       100, 70000));
+       }
+
+       return 0;
+}
+
+const struct clk_ops ccu_pll_ops = {
+       .prepare        = ccu_prepare,
+       .unprepare      = ccu_unprepare,
+       .enable         = ccu_enable,
+       .disable        = ccu_disable,
+/*     .is_enabled     = NULL; (don't disable the clocks at startup time) */
+
+       .recalc_rate    = ccu_pll_recalc_rate,
+       .round_rate     = ccu_pll_round_rate,
+       .set_rate       = ccu_pll_set_rate,
+};
+
+/* --- mux parent --- */
+u8 ccu_get_parent(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (!ccu->mux_width)
+               return 0;
+
+       return (readl(ccu->base + ccu->reg) >> ccu->mux_shift) &
+                               ((1 << ccu->mux_width) - 1);
+}
+
+int ccu_set_parent(struct clk_hw *hw, u8 index)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       u32 mask;
+
+       if (!ccu->mux_width)
+               return 0;
+
+       mask = CCU_MASK(ccu->mux_shift, ccu->mux_width);
+
+       ccu_set_clock(ccu, ccu->reg, mask, index << ccu->mux_shift);
+
+       return 0;
+}
+
+/* --- mux --- */
+static void ccu_mux_adjust_parent_for_prediv(struct ccu *ccu,
+                                            int parent_index,
+                                            unsigned long *parent_rate)
+{
+       const struct ccu_extra *extra = ccu->extra;
+       int prediv = 1;
+       u32 reg;
+
+       if (!(extra &&
+             (ccu->features & (CCU_FEATURE_MUX_FIXED_PREDIV |
+                               CCU_FEATURE_MUX_VARIABLE_PREDIV))))
+               return;
+
+       reg = readl(ccu->base + ccu->reg);
+       if (parent_index < 0)
+               parent_index = (reg >> ccu->mux_shift) &
+                                       ((1 << ccu->mux_width) - 1);
+
+       if (ccu->features & CCU_FEATURE_MUX_FIXED_PREDIV)
+               prediv = extra->fixed_div[parent_index];
+
+       if (ccu->features & CCU_FEATURE_MUX_VARIABLE_PREDIV)
+               if (parent_index == extra->variable_prediv.index) {
+                       u8 div;
+
+                       div = reg >> extra->variable_prediv.shift;
+                       div &= (1 << extra->variable_prediv.width) - 1;
+                       prediv = div + 1;
+               }
+
+       *parent_rate /= prediv;
+}
+
+/* --- periph --- */
+static unsigned long ccu_m_round_rate(struct ccu *ccu,
+                                       unsigned long rate,
+                                       unsigned long parent_rate)
+{
+       int m;
+
+       /*
+        * We can't use divider_round_rate that assumes that there's
+        * several parents, while we might be called to evaluate
+        * several different parents.
+        */
+       m = divider_get_val(rate, parent_rate,
+                       ccu->div_table, ccu->m_width, ccu->div_flags);
+
+       return divider_recalc_rate(&ccu->hw, parent_rate, m,
+                                  ccu->div_table, ccu->div_flags);
+}
+
+static unsigned long ccu_mp_round_rate(struct ccu *ccu,
+                               unsigned long rate,
+                               unsigned long parent_rate)
+{
+       struct values v;
+       int ret;
+
+       ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+       if (ret)
+               return 0;
+
+       return parent_rate / v.m >> v.p;
+}
+
+unsigned long ccu_periph_recalc_rate(struct clk_hw *hw,
+                                       unsigned long parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       int m, p;
+       u32 reg;
+
+       ccu_mux_adjust_parent_for_prediv(ccu, -1, &parent_rate);
+
+       if (!ccu->m_width && !ccu->p_width)
+               return parent_rate;
+
+       reg = readl(ccu->base + ccu->reg);
+       m = (reg >> ccu->m_shift) & ((1 << ccu->m_width) - 1);
+
+       if (ccu->p_width) {
+               reg = readl(ccu->base + ccu->reg);
+               p = (reg >> ccu->p_shift) & ((1 << ccu->p_width) - 1);
+
+               return parent_rate / (m + 1) >> p;
+       }
+
+       return divider_recalc_rate(hw, parent_rate, m,
+                               ccu->div_table, ccu->div_flags);
+}
+
+int ccu_periph_determine_rate(struct clk_hw *hw,
+                               struct clk_rate_request *req)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       unsigned long best_parent_rate = 0, best_rate = 0;
+       struct clk_hw *best_parent;
+       unsigned int i;
+       unsigned long (*round)(struct ccu *,
+                               unsigned long,
+                               unsigned long);
+
+       if (ccu->p_width)
+               round = ccu_mp_round_rate;
+       else if (ccu->m_width)
+               round = ccu_m_round_rate;
+       else
+               return __clk_mux_determine_rate(hw, req);
+
+       for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+               unsigned long new_rate, parent_rate;
+               struct clk_hw *parent;
+
+               parent = clk_hw_get_parent_by_index(hw, i);
+               if (!parent)
+                       continue;
+
+               parent_rate = clk_hw_get_rate(parent);
+               ccu_mux_adjust_parent_for_prediv(ccu, i, &parent_rate);
+               new_rate = round(ccu, req->rate, parent_rate);
+
+               if (new_rate == req->rate) {
+                       best_parent = parent;
+                       best_parent_rate = parent_rate;
+                       best_rate = new_rate;
+                       goto out;
+               }
+
+               if ((req->rate - new_rate) < (req->rate - best_rate)) {
+                       best_rate = new_rate;
+                       best_parent_rate = parent_rate;
+                       best_parent = parent;
+               }
+       }
+
+       if (best_rate == 0)
+               return -EINVAL;
+
+out:
+       req->best_parent_hw = best_parent;
+       req->best_parent_rate = best_parent_rate;
+       req->rate = best_rate;
+
+       return 0;
+}
+
+int ccu_periph_set_rate(struct clk_hw *hw, unsigned long rate,
+                       unsigned long parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       const struct ccu_extra *extra = ccu->extra;
+       struct values v;
+       u32 mask;
+       int ret;
+
+       if (!ccu->m_width && !ccu->p_width)
+               return 0;
+
+       ccu_mux_adjust_parent_for_prediv(ccu, -1, &parent_rate);
+
+       if (extra && (ccu->features & CCU_FEATURE_MODE_SELECT)) {
+               /* fixme: should use new mode */
+               if (rate == extra->mode_select.rate)
+                       rate /= 2;
+       }
+
+       if (ccu->p_width) {                             /* m and p */
+               ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+               if (ret)
+                       return ret;
+       } else {                                        /* m alone */
+               v.m = divider_get_val(rate, parent_rate,
+                               ccu->div_table, ccu->m_width, ccu->div_flags);
+               v.p = 0;
+               return 0;
+       }
+
+       mask = CCU_MASK(ccu->m_shift, ccu->m_width) |
+               CCU_MASK(ccu->p_shift, ccu->p_width);
+
+       if (ccu->features & CCU_FEATURE_SET_RATE_UNGATE)
+               ccu_disable(hw);
+       ccu_set_clock(ccu, ccu->reg, mask, ((v.m - 1) << ccu->m_shift) |
+                                           (v.p << ccu->p_shift));
+       if (ccu->features & CCU_FEATURE_SET_RATE_UNGATE)
+               ccu_enable(hw);
+
+       return 0;
+}
+
+const struct clk_ops ccu_periph_ops = {
+       .prepare        = ccu_prepare,
+       .unprepare      = ccu_unprepare,
+       .enable         = ccu_enable,
+       .disable        = ccu_disable,
+/*     .is_enabled     = NULL; (don't disable the clocks at startup time) */
+
+       .get_parent     = ccu_get_parent,
+       .set_parent     = ccu_set_parent,
+
+       .determine_rate = ccu_periph_determine_rate,
+       .recalc_rate    = ccu_periph_recalc_rate,
+       .set_rate       = ccu_periph_set_rate,
+};
+
+/* --- fixed factor --- */
+/* mul is n_width - div is m_width */
+unsigned long ccu_fixed_factor_recalc_rate(struct clk_hw *hw,
+                                       unsigned long parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       return parent_rate / ccu->m_width * ccu->n_width;
+}
+
+long ccu_fixed_factor_round_rate(struct clk_hw *hw,
+                               unsigned long rate,
+                               unsigned long *parent_rate)
+{
+       struct ccu *ccu = hw2ccu(hw);
+
+       if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+               unsigned long best_parent;
+
+               best_parent = (rate / ccu->n_width) * ccu->m_width;
+               *parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
+                                                best_parent);
+       }
+
+       return *parent_rate / ccu->m_width * ccu->n_width;
+}
+
+int ccu_fixed_factor_set_rate(struct clk_hw *hw, unsigned long rate,
+                                    unsigned long parent_rate)
+{
+       return 0;
+}
+
+const struct clk_ops ccu_fixed_factor_ops = {
+       .disable        = ccu_disable,
+       .enable         = ccu_enable,
+/*     .is_enabled     = NULL, */
+
+       .recalc_rate    = ccu_fixed_factor_recalc_rate,
+       .round_rate     = ccu_fixed_factor_round_rate,
+       .set_rate       = ccu_fixed_factor_set_rate,
+};
+
+/* --- phase --- */
+static int ccu_phase_get_phase(struct clk_hw *hw)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       struct clk_hw *parent, *grandparent;
+       unsigned int parent_rate, grandparent_rate;
+       u16 step, parent_div;
+       u32 reg;
+       u8 delay;
+
+       reg = readl(ccu->base + ccu->reg);
+       delay = (reg >> ccu->p_shift);
+       delay &= (1 << ccu->p_width) - 1;
+
+       if (!delay)
+               return 180;
+
+       /* Get our parent clock, it's the one that can adjust its rate */
+       parent = clk_hw_get_parent(hw);
+       if (!parent)
+               return -EINVAL;
+
+       /* And its rate */
+       parent_rate = clk_hw_get_rate(parent);
+       if (!parent_rate)
+               return -EINVAL;
+
+       /* Now, get our parent's parent (most likely some PLL) */
+       grandparent = clk_hw_get_parent(parent);
+       if (!grandparent)
+               return -EINVAL;
+
+       /* And its rate */
+       grandparent_rate = clk_hw_get_rate(grandparent);
+       if (!grandparent_rate)
+               return -EINVAL;
+
+       /* Get our parent clock divider */
+       parent_div = grandparent_rate / parent_rate;
+
+       step = DIV_ROUND_CLOSEST(360, parent_div);
+       return delay * step;
+}
+
+static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
+{
+       struct ccu *ccu = hw2ccu(hw);
+       struct clk_hw *parent, *grandparent;
+       unsigned int parent_rate, grandparent_rate;
+       u32 mask;
+       u8 delay = 0;
+       u16 step, parent_div;
+
+       if (degrees == 180)
+               goto set_phase;
+
+       /* Get our parent clock, it's the one that can adjust its rate */
+       parent = clk_hw_get_parent(hw);
+       if (!parent)
+               return -EINVAL;
+
+       /* And its rate */
+       parent_rate = clk_hw_get_rate(parent);
+       if (!parent_rate)
+               return -EINVAL;
+
+       /* Now, get our parent's parent (most likely some PLL) */
+       grandparent = clk_hw_get_parent(parent);
+       if (!grandparent)
+               return -EINVAL;
+
+       /* And its rate */
+       grandparent_rate = clk_hw_get_rate(grandparent);
+       if (!grandparent_rate)
+               return -EINVAL;
+
+       /* Get our parent divider */
+       parent_div = grandparent_rate / parent_rate;
+
+       /*
+        * We can only outphase the clocks by multiple of the
+        * PLL's period.
+        *
+        * Since our parent clock is only a divider, and the
+        * formula to get the outphasing in degrees is deg =
+        * 360 * delta / period
+        *
+        * If we simplify this formula, we can see that the
+        * only thing that we're concerned about is the number
+        * of period we want to outphase our clock from, and
+        * the divider set by our parent clock.
+        */
+       step = DIV_ROUND_CLOSEST(360, parent_div);
+       delay = DIV_ROUND_CLOSEST(degrees, step);
+
+set_phase:
+       mask = CCU_MASK(ccu->p_shift, ccu->p_width);
+       ccu_set_clock(ccu, ccu->reg, mask, delay << ccu->p_shift);
+
+       return 0;
+}
+
+const struct clk_ops ccu_phase_ops = {
+       .get_phase      = ccu_phase_get_phase,
+       .set_phase      = ccu_phase_set_phase,
+};
+
+/* --- reset --- */
+static inline
+struct ccu_reset *rcdev_to_ccu_reset(struct reset_controller_dev *rcdev)
+{
+       return container_of(rcdev, struct ccu_reset, rcdev);
+}
+
+static void ccu_set_reset_clock(struct ccu_reset *ccu_reset,
+                               int reg, int bit, int enable)
+{
+       u32 mask;
+
+       if (!reg)                       /* compatibility */
+               return;
+
+#if CCU_DEBUG
+       pr_info("** ccu reset %03x %d %sassert\n",
+               reg, bit, enable ? "de-" : "");
+#endif
+       mask = BIT(bit);
+
+       spin_lock(&ccu_lock);
+       if (enable)
+               writel(readl(ccu_reset->base + reg) | mask,
+                       ccu_reset->base + reg);
+       else
+               writel(readl(ccu_reset->base + reg) & ~mask,
+                       ccu_reset->base + reg);
+       spin_unlock(&ccu_lock);
+}
+
+static int ccu_reset_assert(struct reset_controller_dev *rcdev,
+                           unsigned long id)
+{
+       struct ccu_reset *ccu_reset = rcdev_to_ccu_reset(rcdev);
+       const struct ccu_reset_map *map = &ccu_reset->reset_map[id];
+
+       ccu_set_reset_clock(ccu_reset, map->reg, map->bit, 0);
+
+       return 0;
+}
+
+static int ccu_reset_deassert(struct reset_controller_dev *rcdev,
+                             unsigned long id)
+{
+       struct ccu_reset *ccu_reset = rcdev_to_ccu_reset(rcdev);
+       const struct ccu_reset_map *map = &ccu_reset->reset_map[id];
+
+       ccu_set_reset_clock(ccu_reset, map->reg, map->bit, 1);
+
+       return 0;
+}
+
+const struct reset_control_ops ccu_reset_ops = {
+       .assert         = ccu_reset_assert,
+       .deassert       = ccu_reset_deassert,
+};
+
+/* --- init --- */
+int __init ccu_probe(struct device_node *node,
+                       struct clk_hw_onecell_data *data,
+                       struct ccu_reset *resets)
+{
+       struct clk_hw *hw;
+       struct ccu *ccu;
+       void __iomem *reg;
+       int i, ret;
+
+       reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+       if (IS_ERR(reg)) {
+               pr_err("%s: Clock mapping failed %d\n",
+                       of_node_full_name(node), (int) PTR_ERR(reg));
+               return PTR_ERR(reg);
+       }
+
+       /* register the clocks */
+       for (i = 0; i < data->num; i++) {
+               hw = data->hws[i];
+#if CCU_DEBUG
+               if (!hw) {
+                       pr_err("%s: Bad number of clocks %d != %d\n",
+                               of_node_full_name(node),
+                               i + 1, data->num);
+                       data->num = i;
+                       break;
+               }
+#endif
+               ccu = hw2ccu(hw);
+               ccu->base = reg;
+               ret = clk_hw_register(NULL, hw);
+               if (ret < 0) {
+                       pr_err("%s: Register clock %s failed %d\n",
+                               of_node_full_name(node),
+                               clk_hw_get_name(hw), ret);
+                       data->num = i;
+                       break;
+               }
+       }
+       ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data);
+       if (ret < 0)
+               goto err;
+
+       /* register the resets */
+       resets->rcdev.of_node = node;
+       resets->base = reg;
+
+       ret = reset_controller_register(&resets->rcdev);
+       if (ret) {
+               pr_err("%s: Reset register failed %d\n",
+                       of_node_full_name(node), ret);
+               goto err;
+       }
+
+       return ret;
+
+err:
+       /* don't do anything, otherwise no uart anymore */
+       return ret;
+}
diff --git a/drivers/clk/sunxi/ccu.h b/drivers/clk/sunxi/ccu.h
new file mode 100644
index 0000000..5597681
--- /dev/null
+++ b/drivers/clk/sunxi/ccu.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 Jean-Francois Moine <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ */
+
+#ifndef _CCU_H_
+#define _CCU_H_
+
+struct device_node;
+
+#define CCU_HW(_name, _parent, _ops, _flags)                   \
+       .hw.init = &(struct clk_init_data) {                    \
+               .flags          = _flags,                       \
+               .name           = _name,                        \
+               .parent_names   = (const char *[]) { _parent }, \
+               .num_parents    = 1,                            \
+               .ops            = _ops,                         \
+       }
+
+#define CCU_HW_PARENTS(_name, _parents, _ops, _flags)          \
+       .hw.init = &(struct clk_init_data) {                    \
+               .flags          = _flags,                       \
+               .name           = _name,                        \
+               .parent_names   = _parents,                     \
+               .num_parents    = ARRAY_SIZE(_parents),         \
+               .ops            = _ops,                         \
+       }
+
+#define CCU_REG(_reg) .reg = _reg
+#define CCU_RESET(_reg, _bit) .reset_reg = _reg, .reset_bit = _bit
+#define CCU_BUS(_reg, _bit) .bus_reg = _reg, .bus_bit = _bit
+#define CCU_GATE(_bit) .has_gate = 1, .gate_bit = _bit
+#define CCU_LOCK(_reg, _bit) .lock_reg = _reg, .lock_bit = _bit
+#define CCU_MUX(_shift, _width) .mux_shift = _shift, .mux_width = _width
+#define CCU_N(_shift, _width) .n_shift = _shift, .n_width = _width
+#define CCU_D1(_shift, _width) .d1_shift = _shift, .d1_width = _width
+#define CCU_D2(_shift, _width) .p_shift = _shift, .p_width = _width
+#define CCU_K(_shift, _width) .k_shift = _shift, .k_width = _width
+#define CCU_M(_shift, _width) .m_shift = _shift, .m_width = _width
+#define CCU_P(_shift, _width) .p_shift = _shift, .p_width = _width
+#define CCU_UPD(_bit) .upd_bit = _bit
+/* with ccu_fixed_factor_ops */
+#define CCU_FIXED(_mul, _div) .n_width = _mul, .m_width = _div
+/* with ccu_phase_ops */
+#define CCU_PHASE(_shift, _width) .p_shift = _shift, .p_width = _width
+
+#define CCU_FEATURE_FRACTIONAL         BIT(0)
+#define CCU_FEATURE_MUX_VARIABLE_PREDIV        BIT(1)
+#define CCU_FEATURE_MUX_FIXED_PREDIV   BIT(2)
+#define CCU_FEATURE_FIXED_POSTDIV      BIT(3)
+#define CCU_FEATURE_N0                 BIT(4)
+#define CCU_FEATURE_MODE_SELECT                BIT(5)
+#define CCU_FEATURE_FLAT_FACTORS       BIT(6)
+#define CCU_FEATURE_SET_RATE_UNGATE    BIT(7)
+
+/* extra */
+#define CCU_EXTRA_FRAC(_frac) .frac = _frac, .num_frac = ARRAY_SIZE(_frac)
+#define CCU_EXTRA_POST_DIV(_div) .fixed_div[0] = _div
+
+/* fractional values */
+struct frac {
+       unsigned long rate;
+       u32 mask;
+       u32 val;
+};
+
+/* extra features */
+struct ccu_extra {
+       const struct frac *frac; /* array - last is the fractional mask/value */
+       u8 num_frac;
+
+       u8 fixed_div[4];
+
+       struct {
+               u8 index;
+               u8 shift;
+               u8 width;
+       } variable_prediv;
+
+       struct {
+               unsigned long rate;
+               u8 bit;
+       } mode_select;
+};
+
+struct ccu {
+       struct clk_hw   hw;
+
+       void __iomem *base;
+       u16 reg;
+
+       u16 reset_reg, bus_reg, lock_reg;
+       u8  reset_bit, bus_bit, lock_bit;
+       u8 has_gate, gate_bit;
+
+       u8 mux_shift, mux_width;
+       u8 n_shift, n_width, n_min;
+       u8 d1_shift, d1_width;
+       u8 k_shift, k_width;
+       u8 m_shift, m_width;
+       u8 p_shift, p_width;
+
+       u8 upd_bit;
+
+       u8 features;
+
+       const struct clk_div_table *div_table;
+       u32 div_flags;
+
+       const struct ccu_extra *extra;
+};
+
+struct ccu_reset_map {
+       u16     reg;
+       u16     bit;
+};
+
+struct ccu_reset {
+       void __iomem                    *base;
+       const struct ccu_reset_map      *reset_map;
+       struct reset_controller_dev     rcdev;
+};
+
+extern const struct clk_ops ccu_fixed_factor_ops;
+extern const struct clk_ops ccu_periph_ops;
+extern const struct clk_ops ccu_pll_ops;
+extern const struct clk_ops ccu_phase_ops;
+extern const struct reset_control_ops ccu_reset_ops;
+
+static inline struct ccu *hw2ccu(struct clk_hw *hw)
+{
+       return container_of(hw, struct ccu, hw);
+}
+
+int ccu_probe(struct device_node *node,
+               struct clk_hw_onecell_data *data,
+               struct ccu_reset *resets);
+
+/* functions exported for specific features */
+void ccu_set_clock(struct ccu *ccu, int reg, u32 mask, u32 val);
+unsigned long ccu_fixed_factor_recalc_rate(struct clk_hw *hw,
+                                       unsigned long parent_rate);
+long ccu_fixed_factor_round_rate(struct clk_hw *hw,
+                               unsigned long rate,
+                               unsigned long *parent_rate);
+int ccu_fixed_factor_set_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long parent_rate);
+
+#endif /* _CCU_H_ */
-- 
2.9.0

-- 
You received this message because you are subscribed to the Google Groups 
"linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to