Introduce support for the PLL clocks of the A83T and A80 SoCs
which are set from N, D1, D2, M and P factors.

Signed-off-by: Jean-Francois Moine <[email protected]>
---
 drivers/clk/sunxi-ng/Makefile     |   1 +
 drivers/clk/sunxi-ng/ccu_common.h |   1 +
 drivers/clk/sunxi-ng/ccu_ndmp.c   | 239 ++++++++++++++++++++++++++++++++++++++
 drivers/clk/sunxi-ng/ccu_ndmp.h   |  96 +++++++++++++++
 4 files changed, 337 insertions(+)
 create mode 100644 drivers/clk/sunxi-ng/ccu_ndmp.c
 create mode 100644 drivers/clk/sunxi-ng/ccu_ndmp.h

diff --git a/drivers/clk/sunxi-ng/Makefile b/drivers/clk/sunxi-ng/Makefile
index cafabf0..f0608cb 100644
--- a/drivers/clk/sunxi-ng/Makefile
+++ b/drivers/clk/sunxi-ng/Makefile
@@ -8,6 +8,7 @@ obj-y += ccu_fixed_factor.o
 obj-y += ccu_gate.o
 obj-y += ccu_mp.o
 obj-y += ccu_mux.o
+obj-y += ccu_ndmp.o
 obj-y += ccu_nk.o
 obj-y += ccu_nkm.o
 obj-y += ccu_nkmp.o
diff --git a/drivers/clk/sunxi-ng/ccu_common.h 
b/drivers/clk/sunxi-ng/ccu_common.h
index fda2450..df3ae5e 100644
--- a/drivers/clk/sunxi-ng/ccu_common.h
+++ b/drivers/clk/sunxi-ng/ccu_common.h
@@ -23,6 +23,7 @@
 #define CCU_FEATURE_VARIABLE_PREDIV    BIT(3)
 #define CCU_FEATURE_FIXED_PREDIV       BIT(4)
 #define CCU_FEATURE_FIXED_POSTDIV      BIT(5)
+#define CCU_FEATURE_N1                 BIT(6)
 
 struct device_node;
 
diff --git a/drivers/clk/sunxi-ng/ccu_ndmp.c b/drivers/clk/sunxi-ng/ccu_ndmp.c
new file mode 100644
index 0000000..5481527
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu_ndmp.c
@@ -0,0 +1,239 @@
+/*
+ * PLL clocks of sun8iw6 (A83T) and sun9iw1 (A80)
+ *
+ * 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.
+ *
+ * The clock rates are computed as:
+ *     rate = parent_rate / d1 * n / d2 / m >> p
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/rational.h>
+#include <linux/iopoll.h>
+
+#include "ccu_gate.h"
+#include "ccu_ndmp.h"
+
+struct values {
+       int n, d1, d2, m, p;
+};
+
+static void ccu_ndmp_disable(struct clk_hw *hw)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+
+       return ccu_gate_helper_disable(&ndmp->common, ndmp->enable);
+}
+
+static int ccu_ndmp_enable(struct clk_hw *hw)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+
+       return ccu_gate_helper_enable(&ndmp->common, ndmp->enable);
+}
+
+static int ccu_ndmp_is_enabled(struct clk_hw *hw)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+
+       return ccu_gate_helper_is_enabled(&ndmp->common, ndmp->enable);
+}
+
+static unsigned long ccu_ndmp_recalc_rate(struct clk_hw *hw,
+                                       unsigned long parent_rate)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+       int n, d1, d2, m, p;
+       unsigned long rate;
+       u32 reg;
+
+       reg = readl(ndmp->common.base + ndmp->common.reg);
+
+       rate = parent_rate;
+
+       if (ndmp->d1.width) {
+               d1 = reg >> ndmp->d1.shift;
+               d1 &= (1 << ndmp->d1.width) - 1;
+               rate /= (d1 + 1);
+       }
+
+       n = reg >> ndmp->n.shift;
+       n &= (1 << ndmp->n.width) - 1;
+       if (ndmp->common.features & CCU_FEATURE_N1)
+               n++;
+       rate *= n;
+
+       if (ndmp->d2.width) {
+               d2 = reg >> ndmp->d2.shift;
+               d2 &= (1 << ndmp->d2.width) - 1;
+               rate /= (d2 + 1);
+       }
+
+       if (ndmp->m.width) {
+               m = reg >> ndmp->m.shift;
+               m &= (1 << ndmp->m.width) - 1;
+               rate /= (m + 1);
+       }
+
+       if (ndmp->p.width) {
+               p = reg >> ndmp->p.shift;
+               p &= (1 << ndmp->p.width) - 1;
+               rate >>= p;
+       }
+
+       return rate;
+}
+
+/* all returned values are set here */
+/* d1 and d2 may be only 1 or 2 */
+static int ccu_ndmp_get_fact(struct ccu_ndmp *ndmp,
+                       unsigned long rate, unsigned long prate,
+                       struct values *p_v)
+{
+       int n;
+       int d1 = 0 + 1, d2 = 0 + 1, m = 1, p = 0, d;
+       unsigned long t;
+
+       /* m implies only n, d1, d2 and m (pll-audio) */
+       /*
+        * Setting d1=1 and d2=2 keeps n and m small enough
+        *      with error < 5/10000
+        */
+       if (ndmp->m.width) {
+               unsigned long lun, lum;
+
+               d2 = 1 + 1;
+               t = prate / 2;
+               rational_best_approximation(rate, t,
+                                       1 << ndmp->n.width,
+                                       1 << ndmp->m.width,
+                                       &lun, &lum);
+               if (lum == 0)
+                       return -EINVAL;
+               n = lun;
+               m = lum;
+
+       /* no d1 implies only n and p (pll-cxcpux) */
+       } else if (!ndmp->d1.width) {
+               n = rate / prate;
+               p = 0;          /* p is used only for rates under 288 MHz */
+
+       /* p implies only n, d1 and p (pll-videox) */
+       } else if (ndmp->p.width) {
+               d = 2 + ndmp->p.width;
+               n = rate / (prate / (1 << d));
+               if (n < 12) {
+                       n *= 2;
+                       d++;
+               }
+               while (n >= 12 * 2 && !(n & 1)) {
+                       n /= 2;
+                       if (--d == 0)
+                               break;
+               }
+               if (d <= 1) {
+                       d1 = d + 1;
+               } else {
+                       d1 = 1 + 1;
+                       p = d - 1;
+               }
+
+       /* only n, d1 and d2 (other plls) */
+       } else {
+               t = prate / 4;
+               n = rate / t;
+               if (n < 12) {
+                       n *= 4;
+               } else if (n >= 12 * 2 && !(n & 1)) {
+                       if (n >= 12 * 4 && !(n & 3)) {
+                               n /= 4;
+                       } else {
+                               n /= 2;
+                               d2 = 1 + 1;
+                       }
+               } else {
+                       d1 = d2 = 1 + 1;
+               }
+               if (n > (1 << ndmp->n.width))
+                       return -EINVAL;
+       }
+
+       if (n < 12 || n > (1 << ndmp->n.width))
+               return -EINVAL;
+
+       p_v->n = n;
+       p_v->d1 = d1;
+       p_v->d2 = d2;
+       p_v->m = m;
+       p_v->p = p;
+
+       return 0;
+}
+
+static long ccu_ndmp_round_rate(struct clk_hw *hw, unsigned long rate,
+                             unsigned long *parent_rate)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+       struct values v;
+       int ret;
+
+       ret = ccu_ndmp_get_fact(ndmp, rate, *parent_rate, &v);
+       if (ret)
+               return 0;
+
+       return *parent_rate / v.d1 * v.n / v.d2 / v.m >> v.p;
+}
+
+static int ccu_ndmp_set_rate(struct clk_hw *hw, unsigned long rate,
+                          unsigned long parent_rate)
+{
+       struct ccu_ndmp *ndmp = hw_to_ccu_ndmp(hw);
+       unsigned long flags;
+       struct values v;
+       int ret;
+       u32 reg;
+
+       ret = ccu_ndmp_get_fact(ndmp, rate, parent_rate, &v);
+       if (ret)
+               return ret;
+       if (ndmp->common.features & CCU_FEATURE_N1)
+               v.n--;
+
+       spin_lock_irqsave(ndmp->common.lock, flags);
+
+       reg = readl(ndmp->common.base + ndmp->common.reg) &
+               ~((((1 << ndmp->n.width) - 1) << ndmp->n.shift) |
+                 (((1 << ndmp->d1.width) - 1) << ndmp->d1.shift) |
+                 (((1 << ndmp->d2.width) - 1) << ndmp->d2.shift) |
+                 (((1 << ndmp->m.width) - 1) << ndmp->m.shift) |
+                 (((1 << ndmp->p.width) - 1) << ndmp->p.shift));
+
+       writel(reg | (v.n << ndmp->n.shift) |
+                       ((v.d1 - 1) << ndmp->d1.shift) |
+                       ((v.d2 - 1) << ndmp->d2.shift) |
+                       ((v.m - 1) << ndmp->m.shift) |
+                       (v.p << ndmp->p.shift),
+              ndmp->common.base + ndmp->common.reg);
+
+       spin_unlock_irqrestore(ndmp->common.lock, flags);
+
+       WARN_ON(readl_relaxed_poll_timeout(ndmp->common.base + ndmp->reg_lock,
+                                          reg, !(reg & ndmp->lock), 50, 5000));
+
+       return 0;
+}
+
+const struct clk_ops ccu_ndmp_ops = {
+       .disable        = ccu_ndmp_disable,
+       .enable         = ccu_ndmp_enable,
+       .is_enabled     = ccu_ndmp_is_enabled,
+
+       .recalc_rate    = ccu_ndmp_recalc_rate,
+       .round_rate     = ccu_ndmp_round_rate,
+       .set_rate       = ccu_ndmp_set_rate,
+};
diff --git a/drivers/clk/sunxi-ng/ccu_ndmp.h b/drivers/clk/sunxi-ng/ccu_ndmp.h
new file mode 100644
index 0000000..5341861
--- /dev/null
+++ b/drivers/clk/sunxi-ng/ccu_ndmp.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016 Jean-Francois Moine <[email protected]>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _CCU_NDMP_H_
+#define _CCU_NDMP_H_
+
+#include <linux/clk-provider.h>
+
+#include "ccu_common.h"
+#include "ccu_mult.h"
+
+struct ccu_ndmp {
+       u32                     enable;
+       u32                     lock;
+       u16                     reg_lock;
+
+       struct _ccu_mult        n;
+       struct _ccu_mult        d1;
+       struct _ccu_mult        d2;
+       struct _ccu_mult        m;
+       struct _ccu_mult        p;
+
+       struct ccu_common       common;
+};
+
+#define SUNXI_CCU_NDMP(_struct, _name, _parent, _reg,                  \
+                       _nshift, _nwidth,                               \
+                       _d1shift, _d1width,                             \
+                       _d2shift, _d2width,                             \
+                       _mshift, _mwidth,                               \
+                       _pshift, _pwidth,                               \
+                       _gate, _lock, _reglock, _flags)                 \
+       struct ccu_ndmp _struct = {                                     \
+               .enable         = _gate,                                \
+               .lock           = _lock,                                \
+               .reg_lock       = _reglock,                             \
+               .n              = _SUNXI_CCU_MULT(_nshift, _nwidth),    \
+               .d1             = _SUNXI_CCU_MULT(_d1shift, _d1width),  \
+               .d2             = _SUNXI_CCU_MULT(_d2shift, _d2width),  \
+               .m              = _SUNXI_CCU_MULT(_mshift, _mwidth),    \
+               .p              = _SUNXI_CCU_MULT(_pshift, _pwidth),    \
+               .common         = {                                     \
+                       .reg            = _reg,                         \
+                       .hw.init        = SUNXI_HW_INIT(_name,          \
+                                                       _parent,        \
+                                                       &ccu_ndmp_ops,  \
+                                                       _flags),        \
+               },                                                      \
+       }
+
+#define SUNXI_CCU_NDMP_N1(_struct, _name, _parent, _reg,               \
+                       _nshift, _nwidth,                               \
+                       _d1shift, _d1width,                             \
+                       _d2shift, _d2width,                             \
+                       _mshift, _mwidth,                               \
+                       _pshift, _pwidth,                               \
+                       _gate, _lock, _reglock, _flags)                 \
+       struct ccu_ndmp _struct = {                                     \
+               .enable         = _gate,                                \
+               .lock           = _lock,                                \
+               .reg_lock       = _reglock,                             \
+               .n              = _SUNXI_CCU_MULT(_nshift, _nwidth),    \
+               .d1             = _SUNXI_CCU_MULT(_d1shift, _d1width),  \
+               .d2             = _SUNXI_CCU_MULT(_d2shift, _d2width),  \
+               .m              = _SUNXI_CCU_MULT(_mshift, _mwidth),    \
+               .p              = _SUNXI_CCU_MULT(_pshift, _pwidth),    \
+               .common         = {                                     \
+                       .reg            = _reg,                         \
+                       .features       = CCU_FEATURE_N1,               \
+                       .hw.init        = SUNXI_HW_INIT(_name,          \
+                                                       _parent,        \
+                                                       &ccu_ndmp_ops,  \
+                                                       _flags),        \
+               },                                                      \
+       }
+
+static inline struct ccu_ndmp *hw_to_ccu_ndmp(struct clk_hw *hw)
+{
+       struct ccu_common *common = hw_to_ccu_common(hw);
+
+       return container_of(common, struct ccu_ndmp, common);
+}
+
+extern const struct clk_ops ccu_ndmp_ops;
+
+#endif /* _CCU_NDMP_H_ */
-- 
2.8.4

-- 
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