OSDN Git Service

Merge branches 'clk-doc', 'clk-more-critical', 'clk-meson' and 'clk-basic-be' into...
[tomoyo/tomoyo-test1.git] / drivers / clk / meson / clk-pll.c
index afffc15..ddb1e56 100644 (file)
 #include <linux/io.h>
 #include <linux/math64.h>
 #include <linux/module.h>
-#include <linux/of_address.h>
-#include <linux/slab.h>
-#include <linux/string.h>
+#include <linux/rational.h>
 
-#include "clkc.h"
+#include "clk-regmap.h"
+#include "clk-pll.h"
 
 static inline struct meson_clk_pll_data *
 meson_clk_pll_data(struct clk_regmap *clk)
@@ -44,12 +43,21 @@ meson_clk_pll_data(struct clk_regmap *clk)
        return (struct meson_clk_pll_data *)clk->data;
 }
 
+static int __pll_round_closest_mult(struct meson_clk_pll_data *pll)
+{
+       if ((pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) &&
+           !MESON_PARM_APPLICABLE(&pll->frac))
+               return 1;
+
+       return 0;
+}
+
 static unsigned long __pll_params_to_rate(unsigned long parent_rate,
-                                         const struct pll_params_table *pllt,
-                                         u16 frac,
+                                         unsigned int m, unsigned int n,
+                                         unsigned int frac,
                                          struct meson_clk_pll_data *pll)
 {
-       u64 rate = (u64)parent_rate * pllt->m;
+       u64 rate = (u64)parent_rate * m;
 
        if (frac && MESON_PARM_APPLICABLE(&pll->frac)) {
                u64 frac_rate = (u64)parent_rate * frac;
@@ -58,7 +66,7 @@ static unsigned long __pll_params_to_rate(unsigned long parent_rate,
                                         (1 << pll->frac.width));
        }
 
-       return DIV_ROUND_UP_ULL(rate, pllt->n);
+       return DIV_ROUND_UP_ULL(rate, n);
 }
 
 static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
@@ -66,35 +74,39 @@ static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
 {
        struct clk_regmap *clk = to_clk_regmap(hw);
        struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
-       struct pll_params_table pllt;
-       u16 frac;
+       unsigned int m, n, frac;
 
-       pllt.n = meson_parm_read(clk->map, &pll->n);
-       pllt.m = meson_parm_read(clk->map, &pll->m);
+       n = meson_parm_read(clk->map, &pll->n);
+       m = meson_parm_read(clk->map, &pll->m);
 
        frac = MESON_PARM_APPLICABLE(&pll->frac) ?
                meson_parm_read(clk->map, &pll->frac) :
                0;
 
-       return __pll_params_to_rate(parent_rate, &pllt, frac, pll);
+       return __pll_params_to_rate(parent_rate, m, n, frac, pll);
 }
 
-static u16 __pll_params_with_frac(unsigned long rate,
-                                 unsigned long parent_rate,
-                                 const struct pll_params_table *pllt,
-                                 struct meson_clk_pll_data *pll)
+static unsigned int __pll_params_with_frac(unsigned long rate,
+                                          unsigned long parent_rate,
+                                          unsigned int m,
+                                          unsigned int n,
+                                          struct meson_clk_pll_data *pll)
 {
-       u16 frac_max = (1 << pll->frac.width);
-       u64 val = (u64)rate * pllt->n;
+       unsigned int frac_max = (1 << pll->frac.width);
+       u64 val = (u64)rate * n;
+
+       /* Bail out if we are already over the requested rate */
+       if (rate < parent_rate * m / n)
+               return 0;
 
        if (pll->flags & CLK_MESON_PLL_ROUND_CLOSEST)
                val = DIV_ROUND_CLOSEST_ULL(val * frac_max, parent_rate);
        else
                val = div_u64(val * frac_max, parent_rate);
 
-       val -= pllt->m * frac_max;
+       val -= m * frac_max;
 
-       return min((u16)val, (u16)(frac_max - 1));
+       return min((unsigned int)val, (frac_max - 1));
 }
 
 static bool meson_clk_pll_is_better(unsigned long rate,
@@ -102,45 +114,123 @@ static bool meson_clk_pll_is_better(unsigned long rate,
                                    unsigned long now,
                                    struct meson_clk_pll_data *pll)
 {
-       if (!(pll->flags & CLK_MESON_PLL_ROUND_CLOSEST) ||
-           MESON_PARM_APPLICABLE(&pll->frac)) {
-               /* Round down */
-               if (now < rate && best < now)
-                       return true;
-       } else {
+       if (__pll_round_closest_mult(pll)) {
                /* Round Closest */
                if (abs(now - rate) < abs(best - rate))
                        return true;
+       } else {
+               /* Round down */
+               if (now <= rate && best < now)
+                       return true;
        }
 
        return false;
 }
 
-static const struct pll_params_table *
-meson_clk_get_pll_settings(unsigned long rate,
-                          unsigned long parent_rate,
-                          struct meson_clk_pll_data *pll)
+static int meson_clk_get_pll_table_index(unsigned int index,
+                                        unsigned int *m,
+                                        unsigned int *n,
+                                        struct meson_clk_pll_data *pll)
 {
-       const struct pll_params_table *table = pll->table;
-       unsigned long best = 0, now = 0;
-       unsigned int i, best_i = 0;
+       if (!pll->table[index].n)
+               return -EINVAL;
+
+       *m = pll->table[index].m;
+       *n = pll->table[index].n;
 
-       if (!table)
-               return NULL;
+       return 0;
+}
+
+static unsigned int meson_clk_get_pll_range_m(unsigned long rate,
+                                             unsigned long parent_rate,
+                                             unsigned int n,
+                                             struct meson_clk_pll_data *pll)
+{
+       u64 val = (u64)rate * n;
+
+       if (__pll_round_closest_mult(pll))
+               return DIV_ROUND_CLOSEST_ULL(val, parent_rate);
+
+       return div_u64(val,  parent_rate);
+}
+
+static int meson_clk_get_pll_range_index(unsigned long rate,
+                                        unsigned long parent_rate,
+                                        unsigned int index,
+                                        unsigned int *m,
+                                        unsigned int *n,
+                                        struct meson_clk_pll_data *pll)
+{
+       *n = index + 1;
+
+       /* Check the predivider range */
+       if (*n >= (1 << pll->n.width))
+               return -EINVAL;
+
+       if (*n == 1) {
+               /* Get the boundaries out the way */
+               if (rate <= pll->range->min * parent_rate) {
+                       *m = pll->range->min;
+                       return -ENODATA;
+               } else if (rate >= pll->range->max * parent_rate) {
+                       *m = pll->range->max;
+                       return -ENODATA;
+               }
+       }
 
-       for (i = 0; table[i].n; i++) {
-               now = __pll_params_to_rate(parent_rate, &table[i], 0, pll);
+       *m = meson_clk_get_pll_range_m(rate, parent_rate, *n, pll);
+
+       /* the pre-divider gives a multiplier too big - stop */
+       if (*m >= (1 << pll->m.width))
+               return -EINVAL;
 
-               /* If we get an exact match, don't bother any further */
-               if (now == rate) {
-                       return &table[i];
-               } else if (meson_clk_pll_is_better(rate, best, now, pll)) {
+       return 0;
+}
+
+static int meson_clk_get_pll_get_index(unsigned long rate,
+                                      unsigned long parent_rate,
+                                      unsigned int index,
+                                      unsigned int *m,
+                                      unsigned int *n,
+                                      struct meson_clk_pll_data *pll)
+{
+       if (pll->range)
+               return meson_clk_get_pll_range_index(rate, parent_rate,
+                                                    index, m, n, pll);
+       else if (pll->table)
+               return meson_clk_get_pll_table_index(index, m, n, pll);
+
+       return -EINVAL;
+}
+
+static int meson_clk_get_pll_settings(unsigned long rate,
+                                     unsigned long parent_rate,
+                                     unsigned int *best_m,
+                                     unsigned int *best_n,
+                                     struct meson_clk_pll_data *pll)
+{
+       unsigned long best = 0, now = 0;
+       unsigned int i, m, n;
+       int ret;
+
+       for (i = 0, ret = 0; !ret; i++) {
+               ret = meson_clk_get_pll_get_index(rate, parent_rate,
+                                                 i, &m, &n, pll);
+               if (ret == -EINVAL)
+                       break;
+
+               now = __pll_params_to_rate(parent_rate, m, n, 0, pll);
+               if (meson_clk_pll_is_better(rate, best, now, pll)) {
                        best = now;
-                       best_i = i;
+                       *best_m = m;
+                       *best_n = n;
+
+                       if (now == rate)
+                               break;
                }
        }
 
-       return (struct pll_params_table *)&table[best_i];
+       return best ? 0 : -EINVAL;
 }
 
 static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
@@ -148,15 +238,15 @@ static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
 {
        struct clk_regmap *clk = to_clk_regmap(hw);
        struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
-       const struct pll_params_table *pllt =
-               meson_clk_get_pll_settings(rate, *parent_rate, pll);
+       unsigned int m, n, frac;
        unsigned long round;
-       u16 frac;
+       int ret;
 
-       if (!pllt)
+       ret = meson_clk_get_pll_settings(rate, *parent_rate, &m, &n, pll);
+       if (ret)
                return meson_clk_pll_recalc_rate(hw, *parent_rate);
 
-       round = __pll_params_to_rate(*parent_rate, pllt, 0, pll);
+       round = __pll_params_to_rate(*parent_rate, m, n, 0, pll);
 
        if (!MESON_PARM_APPLICABLE(&pll->frac) || rate == round)
                return round;
@@ -165,9 +255,9 @@ static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
         * The rate provided by the setting is not an exact match, let's
         * try to improve the result using the fractional parameter
         */
-       frac = __pll_params_with_frac(rate, *parent_rate, pllt, pll);
+       frac = __pll_params_with_frac(rate, *parent_rate, m, n, pll);
 
-       return __pll_params_to_rate(*parent_rate, pllt, frac, pll);
+       return __pll_params_to_rate(*parent_rate, m, n, frac, pll);
 }
 
 static int meson_clk_pll_wait_lock(struct clk_hw *hw)
@@ -213,6 +303,16 @@ static int meson_clk_pll_is_enabled(struct clk_hw *hw)
        return 1;
 }
 
+static int meson_clk_pcie_pll_enable(struct clk_hw *hw)
+{
+       meson_clk_pll_init(hw);
+
+       if (meson_clk_pll_wait_lock(hw))
+               return -EIO;
+
+       return 0;
+}
+
 static int meson_clk_pll_enable(struct clk_hw *hw)
 {
        struct clk_regmap *clk = to_clk_regmap(hw);
@@ -254,30 +354,27 @@ static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
 {
        struct clk_regmap *clk = to_clk_regmap(hw);
        struct meson_clk_pll_data *pll = meson_clk_pll_data(clk);
-       const struct pll_params_table *pllt;
-       unsigned int enabled;
+       unsigned int enabled, m, n, frac = 0, ret;
        unsigned long old_rate;
-       u16 frac = 0;
 
        if (parent_rate == 0 || rate == 0)
                return -EINVAL;
 
        old_rate = rate;
 
-       pllt = meson_clk_get_pll_settings(rate, parent_rate, pll);
-       if (!pllt)
-               return -EINVAL;
+       ret = meson_clk_get_pll_settings(rate, parent_rate, &m, &n, pll);
+       if (ret)
+               return ret;
 
        enabled = meson_parm_read(clk->map, &pll->en);
        if (enabled)
                meson_clk_pll_disable(hw);
 
-       meson_parm_write(clk->map, &pll->n, pllt->n);
-       meson_parm_write(clk->map, &pll->m, pllt->m);
-
+       meson_parm_write(clk->map, &pll->n, n);
+       meson_parm_write(clk->map, &pll->m, m);
 
        if (MESON_PARM_APPLICABLE(&pll->frac)) {
-               frac = __pll_params_with_frac(rate, parent_rate, pllt, pll);
+               frac = __pll_params_with_frac(rate, parent_rate, m, n, pll);
                meson_parm_write(clk->map, &pll->frac, frac);
        }
 
@@ -300,6 +397,22 @@ static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
        return 0;
 }
 
+/*
+ * The Meson G12A PCIE PLL is fined tuned to deliver a very precise
+ * 100MHz reference clock for the PCIe Analog PHY, and thus requires
+ * a strict register sequence to enable the PLL.
+ * To simplify, re-use the _init() op to enable the PLL and keep
+ * the other ops except set_rate since the rate is fixed.
+ */
+const struct clk_ops meson_clk_pcie_pll_ops = {
+       .recalc_rate    = meson_clk_pll_recalc_rate,
+       .round_rate     = meson_clk_pll_round_rate,
+       .is_enabled     = meson_clk_pll_is_enabled,
+       .enable         = meson_clk_pcie_pll_enable,
+       .disable        = meson_clk_pll_disable
+};
+EXPORT_SYMBOL_GPL(meson_clk_pcie_pll_ops);
+
 const struct clk_ops meson_clk_pll_ops = {
        .init           = meson_clk_pll_init,
        .recalc_rate    = meson_clk_pll_recalc_rate,
@@ -309,8 +422,15 @@ const struct clk_ops meson_clk_pll_ops = {
        .enable         = meson_clk_pll_enable,
        .disable        = meson_clk_pll_disable
 };
+EXPORT_SYMBOL_GPL(meson_clk_pll_ops);
 
 const struct clk_ops meson_clk_pll_ro_ops = {
        .recalc_rate    = meson_clk_pll_recalc_rate,
        .is_enabled     = meson_clk_pll_is_enabled,
 };
+EXPORT_SYMBOL_GPL(meson_clk_pll_ro_ops);
+
+MODULE_DESCRIPTION("Amlogic PLL driver");
+MODULE_AUTHOR("Carlo Caione <carlo@endlessm.com>");
+MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
+MODULE_LICENSE("GPL v2");