The AT91 UTMI clock is a PLL generating the 480 MHz USB clock.
Originally, this PLL always had a fixed x40 multiplier, thus requiring
a 12 MHz input clock. Recent SOCs (sama5d2 and sama5d3) have a special
function register for configuring this multiplier, allowing to use
different main clock frequencies.

Signed-off-by: Ingo van Lil <ing...@gmx.de>
---
 drivers/clk/at91/clk-utmi.c  | 105 +++++++++++++++++++++++++++++++++++++++++--
 include/soc/at91/atmel-sfr.h |   2 +
 2 files changed, 104 insertions(+), 3 deletions(-)

diff --git a/drivers/clk/at91/clk-utmi.c b/drivers/clk/at91/clk-utmi.c
index aadabd9..d41c38b 100644
--- a/drivers/clk/at91/clk-utmi.c
+++ b/drivers/clk/at91/clk-utmi.c
@@ -14,14 +14,29 @@
 #include <linux/of.h>
 #include <linux/mfd/syscon.h>
 #include <linux/regmap.h>
+#include <soc/at91/atmel-sfr.h>
 
 #include "pmc.h"
 
+/* default multiplier for SOCs that do not allow configuration via SFR */
 #define UTMI_FIXED_MUL         40
 
+/* supported multiplier settings for SOCs that allow configuration via SFR */
+struct utmi_multipliers {
+       const char *sfr_compatible_name;
+       u8 multipliers[4];
+};
+
+static const struct utmi_multipliers utmi_multipliers[] = {
+       { "atmel,sama5d2-sfr", { 40, 30, 20, 40 } },
+       { "atmel,sama5d3-sfr", { 40, 30, 20, 10 } },
+};
+
 struct clk_utmi {
        struct clk_hw hw;
        struct regmap *regmap;
+       struct regmap *sfr_regmap;
+       const u8 *multipliers;
 };
 
 #define to_clk_utmi(hw) container_of(hw, struct clk_utmi, hw)
@@ -66,8 +81,67 @@ static void clk_utmi_unprepare(struct clk_hw *hw)
 static unsigned long clk_utmi_recalc_rate(struct clk_hw *hw,
                                          unsigned long parent_rate)
 {
-       /* UTMI clk is a fixed clk multiplier */
-       return parent_rate * UTMI_FIXED_MUL;
+       struct clk_utmi *utmi = to_clk_utmi(hw);
+       u8 mul = UTMI_FIXED_MUL;
+
+       if (utmi->sfr_regmap && utmi->multipliers) {
+               u32 regval;
+               regmap_read(utmi->sfr_regmap, AT91_SFR_UTMICKTRIM, &regval);
+               mul = utmi->multipliers[regval & AT91_UTMICKTRIM_FREQ_MASK];
+       }
+
+       return parent_rate * mul;
+}
+
+static long clk_utmi_round_rate(struct clk_hw *hw, unsigned long rate,
+                               unsigned long *parent_rate)
+{
+       struct clk_utmi *utmi = to_clk_utmi(hw);
+       unsigned long bestrate = 0;
+       int bestdiff = -1;
+       int i;
+
+       if (!utmi->sfr_regmap || !utmi->multipliers)
+               return *parent_rate * UTMI_FIXED_MUL;
+
+       for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) {
+               unsigned long tmprate = *parent_rate * utmi->multipliers[i];
+               int tmpdiff;
+
+               if (tmprate < rate)
+                       continue;
+
+               tmpdiff = tmprate - rate;
+               if (bestdiff < 0 || bestdiff > tmpdiff) {
+                       bestrate = tmprate;
+                       bestdiff = tmpdiff;
+               }
+
+               if (!bestdiff)
+                       break;
+       }
+
+       return bestrate;
+}
+
+static int clk_utmi_set_rate(struct clk_hw *hw, unsigned long rate,
+                            unsigned long parent_rate)
+{
+       struct clk_utmi *utmi = to_clk_utmi(hw);
+       int i;
+
+       if (!utmi->sfr_regmap || !utmi->multipliers)
+               return rate == parent_rate * UTMI_FIXED_MUL ? 0 : -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) {
+               if (rate == parent_rate * utmi->multipliers[i]) {
+                       regmap_update_bits(utmi->sfr_regmap, 
AT91_SFR_UTMICKTRIM,
+                                          AT91_UTMICKTRIM_FREQ_MASK, i);
+                       return 0;
+               }
+       }
+
+       return -EINVAL;
 }
 
 static const struct clk_ops utmi_ops = {
@@ -75,10 +149,13 @@ static const struct clk_ops utmi_ops = {
        .unprepare = clk_utmi_unprepare,
        .is_prepared = clk_utmi_is_prepared,
        .recalc_rate = clk_utmi_recalc_rate,
+       .round_rate = clk_utmi_round_rate,
+       .set_rate = clk_utmi_set_rate,
 };
 
 static struct clk_hw * __init
 at91_clk_register_utmi(struct regmap *regmap,
+                      struct regmap *sfr_regmap, const u8 *multipliers,
                       const char *name, const char *parent_name)
 {
        struct clk_utmi *utmi;
@@ -98,6 +175,8 @@ at91_clk_register_utmi(struct regmap *regmap,
 
        utmi->hw.init = &init;
        utmi->regmap = regmap;
+       utmi->sfr_regmap = sfr_regmap;
+       utmi->multipliers = multipliers;
 
        hw = &utmi->hw;
        ret = clk_hw_register(NULL, &utmi->hw);
@@ -115,6 +194,9 @@ static void __init of_at91sam9x5_clk_utmi_setup(struct 
device_node *np)
        const char *parent_name;
        const char *name = np->name;
        struct regmap *regmap;
+       struct regmap *sfr_regmap;
+       const u8 *multipliers = NULL;
+       size_t i;
 
        parent_name = of_clk_get_parent_name(np, 0);
 
@@ -124,7 +206,24 @@ static void __init of_at91sam9x5_clk_utmi_setup(struct 
device_node *np)
        if (IS_ERR(regmap))
                return;
 
-       hw = at91_clk_register_utmi(regmap, name, parent_name);
+       for (i = 0; i < ARRAY_SIZE(utmi_multipliers); i++) {
+               sfr_regmap = syscon_regmap_lookup_by_compatible(
+                       utmi_multipliers[i].sfr_compatible_name);
+               if (!IS_ERR(sfr_regmap)) {
+                       pr_debug("clk-utmi: found sfr node: %s\n",
+                                utmi_multipliers[i].sfr_compatible_name);
+                       multipliers = utmi_multipliers[i].multipliers;
+                       break;
+               }
+       }
+
+       if (IS_ERR(sfr_regmap)) {
+               pr_debug("clk-utmi: failed to find sfr node\n");
+               sfr_regmap = NULL;
+       }
+
+       hw = at91_clk_register_utmi(regmap, sfr_regmap, multipliers,
+                                   name, parent_name);
        if (IS_ERR(hw))
                return;
 
diff --git a/include/soc/at91/atmel-sfr.h b/include/soc/at91/atmel-sfr.h
index 506ea8f..763948e 100644
--- a/include/soc/at91/atmel-sfr.h
+++ b/include/soc/at91/atmel-sfr.h
@@ -17,6 +17,7 @@
 /* 0x08 ~ 0x0c: Reserved */
 #define AT91_SFR_OHCIICR       0x10    /* OHCI INT Configuration Register */
 #define AT91_SFR_OHCIISR       0x14    /* OHCI INT Status Register */
+#define AT91_SFR_UTMICKTRIM    0x30    /* UTMI Clock Trimming Register */
 #define AT91_SFR_I2SCLKSEL     0x90    /* I2SC Register */
 
 /* Field definitions */
@@ -28,5 +29,6 @@
                                         AT91_OHCIICR_SUSPEND_B | \
                                         AT91_OHCIICR_SUSPEND_C)
 
+#define AT91_UTMICKTRIM_FREQ_MASK      0x03
 
 #endif /* _LINUX_MFD_SYSCON_ATMEL_SFR_H */
-- 
2.9.4

Reply via email to