clk: at91: add sam9x60 PLL driver
authorAlexandre Belloni <alexandre.belloni@bootlin.com>
Tue, 2 Apr 2019 12:50:54 +0000 (14:50 +0200)
committerStephen Boyd <sboyd@kernel.org>
Thu, 25 Apr 2019 19:34:06 +0000 (12:34 -0700)
The PLLs on the sam9x60 (PLLA and USB PLL) use a different register set and
programming model than the previous SoCs.

Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Signed-off-by: Stephen Boyd <sboyd@kernel.org>
drivers/clk/at91/Makefile
drivers/clk/at91/clk-sam9x60-pll.c [new file with mode: 0644]
drivers/clk/at91/pmc.h

index c75df1cad60e646294a1f5e44871a8c82b2ff6e3..0a30fc8dfcb0dfa1e1b414ef6a98b292c72537f7 100644 (file)
@@ -14,6 +14,7 @@ obj-$(CONFIG_HAVE_AT91_SMD)           += clk-smd.o
 obj-$(CONFIG_HAVE_AT91_H32MX)          += clk-h32mx.o
 obj-$(CONFIG_HAVE_AT91_GENERATED_CLK)  += clk-generated.o
 obj-$(CONFIG_HAVE_AT91_I2S_MUX_CLK)    += clk-i2s-mux.o
+obj-$(CONFIG_HAVE_AT91_SAM9X60_PLL)    += clk-sam9x60-pll.o
 obj-$(CONFIG_SOC_AT91SAM9) += at91sam9260.o at91sam9rl.o at91sam9x5.o
 obj-$(CONFIG_SOC_SAMA5D4) += sama5d4.o
 obj-$(CONFIG_SOC_SAMA5D2) += sama5d2.o
diff --git a/drivers/clk/at91/clk-sam9x60-pll.c b/drivers/clk/at91/clk-sam9x60-pll.c
new file mode 100644 (file)
index 0000000..34b8178
--- /dev/null
@@ -0,0 +1,330 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ *  Copyright (C) 2019 Microchip Technology Inc.
+ *
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/clk/at91_pmc.h>
+#include <linux/of.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+
+#include "pmc.h"
+
+#define PMC_PLL_CTRL0  0xc
+#define                PMC_PLL_CTRL0_DIV_MSK           GENMASK(7, 0)
+#define                PMC_PLL_CTRL0_ENPLL             BIT(28)
+#define                PMC_PLL_CTRL0_ENPLLCK           BIT(29)
+#define                PMC_PLL_CTRL0_ENLOCK            BIT(31)
+
+#define PMC_PLL_CTRL1  0x10
+#define                PMC_PLL_CTRL1_FRACR_MSK         GENMASK(21, 0)
+#define                PMC_PLL_CTRL1_MUL_MSK           GENMASK(30, 24)
+
+#define PMC_PLL_ACR    0x18
+#define                PMC_PLL_ACR_DEFAULT             0x1b040010UL
+#define                PMC_PLL_ACR_UTMIVR              BIT(12)
+#define                PMC_PLL_ACR_UTMIBG              BIT(13)
+#define                PMC_PLL_ACR_LOOP_FILTER_MSK     GENMASK(31, 24)
+
+#define PMC_PLL_UPDT   0x1c
+#define                PMC_PLL_UPDT_UPDATE             BIT(8)
+
+#define PMC_PLL_ISR0   0xec
+
+#define PLL_DIV_MAX            (FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, UINT_MAX) + 1)
+#define UPLL_DIV               2
+#define PLL_MUL_MAX            (FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, UINT_MAX) + 1)
+
+#define PLL_MAX_ID             1
+
+struct sam9x60_pll {
+       struct clk_hw hw;
+       struct regmap *regmap;
+       spinlock_t *lock;
+       const struct clk_pll_characteristics *characteristics;
+       u32 frac;
+       u8 id;
+       u8 div;
+       u16 mul;
+};
+
+#define to_sam9x60_pll(hw) container_of(hw, struct sam9x60_pll, hw)
+
+static inline bool sam9x60_pll_ready(struct regmap *regmap, int id)
+{
+       unsigned int status;
+
+       regmap_read(regmap, PMC_PLL_ISR0, &status);
+
+       return !!(status & BIT(id));
+}
+
+static int sam9x60_pll_prepare(struct clk_hw *hw)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+       struct regmap *regmap = pll->regmap;
+       unsigned long flags;
+       u8 div;
+       u16 mul;
+       u32 val;
+
+       spin_lock_irqsave(pll->lock, flags);
+       regmap_write(regmap, PMC_PLL_UPDT, pll->id);
+
+       regmap_read(regmap, PMC_PLL_CTRL0, &val);
+       div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, val);
+
+       regmap_read(regmap, PMC_PLL_CTRL1, &val);
+       mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, val);
+
+       if (sam9x60_pll_ready(regmap, pll->id) &&
+           (div == pll->div && mul == pll->mul)) {
+               spin_unlock_irqrestore(pll->lock, flags);
+               return 0;
+       }
+
+       /* Recommended value for PMC_PLL_ACR */
+       val = PMC_PLL_ACR_DEFAULT;
+       regmap_write(regmap, PMC_PLL_ACR, val);
+
+       regmap_write(regmap, PMC_PLL_CTRL1,
+                    FIELD_PREP(PMC_PLL_CTRL1_MUL_MSK, pll->mul));
+
+       if (pll->characteristics->upll) {
+               /* Enable the UTMI internal bandgap */
+               val |= PMC_PLL_ACR_UTMIBG;
+               regmap_write(regmap, PMC_PLL_ACR, val);
+
+               udelay(10);
+
+               /* Enable the UTMI internal regulator */
+               val |= PMC_PLL_ACR_UTMIVR;
+               regmap_write(regmap, PMC_PLL_ACR, val);
+
+               udelay(10);
+       }
+
+       regmap_update_bits(regmap, PMC_PLL_UPDT,
+                          PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
+
+       regmap_write(regmap, PMC_PLL_CTRL0,
+                    PMC_PLL_CTRL0_ENLOCK | PMC_PLL_CTRL0_ENPLL |
+                    PMC_PLL_CTRL0_ENPLLCK | pll->div);
+
+       regmap_update_bits(regmap, PMC_PLL_UPDT,
+                          PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
+
+       while (!sam9x60_pll_ready(regmap, pll->id))
+               cpu_relax();
+
+       spin_unlock_irqrestore(pll->lock, flags);
+
+       return 0;
+}
+
+static int sam9x60_pll_is_prepared(struct clk_hw *hw)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+
+       return sam9x60_pll_ready(pll->regmap, pll->id);
+}
+
+static void sam9x60_pll_unprepare(struct clk_hw *hw)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+       unsigned long flags;
+
+       spin_lock_irqsave(pll->lock, flags);
+
+       regmap_write(pll->regmap, PMC_PLL_UPDT, pll->id);
+
+       regmap_update_bits(pll->regmap, PMC_PLL_CTRL0,
+                          PMC_PLL_CTRL0_ENPLLCK, 0);
+
+       regmap_update_bits(pll->regmap, PMC_PLL_UPDT,
+                          PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
+
+       regmap_update_bits(pll->regmap, PMC_PLL_CTRL0, PMC_PLL_CTRL0_ENPLL, 0);
+
+       if (pll->characteristics->upll)
+               regmap_update_bits(pll->regmap, PMC_PLL_ACR,
+                                  PMC_PLL_ACR_UTMIBG | PMC_PLL_ACR_UTMIVR, 0);
+
+       regmap_update_bits(pll->regmap, PMC_PLL_UPDT,
+                          PMC_PLL_UPDT_UPDATE, PMC_PLL_UPDT_UPDATE);
+
+       spin_unlock_irqrestore(pll->lock, flags);
+}
+
+static unsigned long sam9x60_pll_recalc_rate(struct clk_hw *hw,
+                                            unsigned long parent_rate)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+
+       return (parent_rate * (pll->mul + 1)) / (pll->div + 1);
+}
+
+static long sam9x60_pll_get_best_div_mul(struct sam9x60_pll *pll,
+                                        unsigned long rate,
+                                        unsigned long parent_rate,
+                                        bool update)
+{
+       const struct clk_pll_characteristics *characteristics =
+                                                       pll->characteristics;
+       unsigned long bestremainder = ULONG_MAX;
+       unsigned long maxdiv, mindiv, tmpdiv;
+       long bestrate = -ERANGE;
+       unsigned long bestdiv = 0;
+       unsigned long bestmul = 0;
+       unsigned long bestfrac = 0;
+
+       if (rate < characteristics->output[0].min ||
+           rate > characteristics->output[0].max)
+               return -ERANGE;
+
+       if (!pll->characteristics->upll) {
+               mindiv = parent_rate / rate;
+               if (mindiv < 2)
+                       mindiv = 2;
+
+               maxdiv = DIV_ROUND_UP(parent_rate * PLL_MUL_MAX, rate);
+               if (maxdiv > PLL_DIV_MAX)
+                       maxdiv = PLL_DIV_MAX;
+       } else {
+               mindiv = maxdiv = UPLL_DIV;
+       }
+
+       for (tmpdiv = mindiv; tmpdiv <= maxdiv; tmpdiv++) {
+               unsigned long remainder;
+               unsigned long tmprate;
+               unsigned long tmpmul;
+               unsigned long tmpfrac = 0;
+
+               /*
+                * Calculate the multiplier associated with the current
+                * divider that provide the closest rate to the requested one.
+                */
+               tmpmul = mult_frac(rate, tmpdiv, parent_rate);
+               tmprate = mult_frac(parent_rate, tmpmul, tmpdiv);
+               remainder = rate - tmprate;
+
+               if (remainder) {
+                       tmpfrac = DIV_ROUND_CLOSEST_ULL((u64)remainder * tmpdiv * (1 << 22),
+                                                       parent_rate);
+
+                       tmprate += DIV_ROUND_CLOSEST_ULL((u64)tmpfrac * parent_rate,
+                                                        tmpdiv * (1 << 22));
+
+                       if (tmprate > rate)
+                               remainder = tmprate - rate;
+                       else
+                               remainder = rate - tmprate;
+               }
+
+               /*
+                * Compare the remainder with the best remainder found until
+                * now and elect a new best multiplier/divider pair if the
+                * current remainder is smaller than the best one.
+                */
+               if (remainder < bestremainder) {
+                       bestremainder = remainder;
+                       bestdiv = tmpdiv;
+                       bestmul = tmpmul;
+                       bestrate = tmprate;
+                       bestfrac = tmpfrac;
+               }
+
+               /* We've found a perfect match!  */
+               if (!remainder)
+                       break;
+       }
+
+       /* Check if bestrate is a valid output rate  */
+       if (bestrate < characteristics->output[0].min &&
+           bestrate > characteristics->output[0].max)
+               return -ERANGE;
+
+       if (update) {
+               pll->div = bestdiv - 1;
+               pll->mul = bestmul - 1;
+               pll->frac = bestfrac;
+       }
+
+       return bestrate;
+}
+
+static long sam9x60_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+                                  unsigned long *parent_rate)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+
+       return sam9x60_pll_get_best_div_mul(pll, rate, *parent_rate, false);
+}
+
+static int sam9x60_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long parent_rate)
+{
+       struct sam9x60_pll *pll = to_sam9x60_pll(hw);
+
+       return sam9x60_pll_get_best_div_mul(pll, rate, parent_rate, true);
+}
+
+static const struct clk_ops pll_ops = {
+       .prepare = sam9x60_pll_prepare,
+       .unprepare = sam9x60_pll_unprepare,
+       .is_prepared = sam9x60_pll_is_prepared,
+       .recalc_rate = sam9x60_pll_recalc_rate,
+       .round_rate = sam9x60_pll_round_rate,
+       .set_rate = sam9x60_pll_set_rate,
+};
+
+struct clk_hw * __init
+sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock,
+                        const char *name, const char *parent_name, u8 id,
+                        const struct clk_pll_characteristics *characteristics)
+{
+       struct sam9x60_pll *pll;
+       struct clk_hw *hw;
+       struct clk_init_data init;
+       unsigned int pllr;
+       int ret;
+
+       if (id > PLL_MAX_ID)
+               return ERR_PTR(-EINVAL);
+
+       pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+       if (!pll)
+               return ERR_PTR(-ENOMEM);
+
+       init.name = name;
+       init.ops = &pll_ops;
+       init.parent_names = &parent_name;
+       init.num_parents = 1;
+       init.flags = CLK_SET_RATE_GATE;
+
+       pll->id = id;
+       pll->hw.init = &init;
+       pll->characteristics = characteristics;
+       pll->regmap = regmap;
+       pll->lock = lock;
+
+       regmap_write(regmap, PMC_PLL_UPDT, id);
+       regmap_read(regmap, PMC_PLL_CTRL0, &pllr);
+       pll->div = FIELD_GET(PMC_PLL_CTRL0_DIV_MSK, pllr);
+       regmap_read(regmap, PMC_PLL_CTRL1, &pllr);
+       pll->mul = FIELD_GET(PMC_PLL_CTRL1_MUL_MSK, pllr);
+
+       hw = &pll->hw;
+       ret = clk_hw_register(NULL, hw);
+       if (ret) {
+               kfree(pll);
+               hw = ERR_PTR(ret);
+       }
+
+       return hw;
+}
+
index 4a30c20f17f1b5dfdfc5aaba48b0b3b36244d5ea..fb9e9c4cdc8dc9124c6a128cbf8b295d861396e1 100644 (file)
@@ -69,6 +69,7 @@ struct clk_pll_characteristics {
        struct clk_range *output;
        u16 *icpll;
        u8 *out;
+       u8 upll : 1;
 };
 
 struct clk_programmable_layout {
@@ -169,6 +170,11 @@ struct clk_hw * __init
 at91_clk_register_plldiv(struct regmap *regmap, const char *name,
                         const char *parent_name);
 
+struct clk_hw * __init
+sam9x60_clk_register_pll(struct regmap *regmap, spinlock_t *lock,
+                        const char *name, const char *parent_name, u8 id,
+                        const struct clk_pll_characteristics *characteristics);
+
 struct clk_hw * __init
 at91_clk_register_programmable(struct regmap *regmap, const char *name,
                               const char **parent_names, u8 num_parents, u8 id,