]> git.sur5r.net Git - u-boot/commitdiff
driver: clk: Add support for clocks on Armada 37xx
authorMarek BehĂșn <marek.behun@nic.cz>
Tue, 24 Apr 2018 15:21:25 +0000 (17:21 +0200)
committerStefan Roese <sr@denx.de>
Mon, 14 May 2018 08:00:15 +0000 (10:00 +0200)
The drivers are based on Linux driver by Gregory Clement.

The TBG clocks support only the .get_rate method.
  - since setting rate is not supported, the driver computes the rates
    when probing and so subsequent calls to the .get_rate method do not
    read the corresponding registers again

The peripheral clocks support methods .get_rate, .enable and .disable.

  - the .set_parent method theoretically could be supported on some clocks
    (the parent would have to be one of the TBG clocks)

  - the .set_rate method would have to try all the divider values to find
    the best approximation of a given rate, and it doesn't seem like
    this should be needed in U-Boot, therefore not implemented

Signed-off-by: Marek Behun <marek.behun@nic.cz>
Reviewed-by: Stefan Roese <sr@denx.de>
Signed-off-by: Stefan Roese <sr@denx.de>
arch/arm/dts/armada-37xx.dtsi
drivers/clk/Kconfig
drivers/clk/Makefile
drivers/clk/mvebu/Kconfig [new file with mode: 0644]
drivers/clk/mvebu/Makefile [new file with mode: 0644]
drivers/clk/mvebu/armada-37xx-periph.c [new file with mode: 0644]
drivers/clk/mvebu/armada-37xx-tbg.c [new file with mode: 0644]

index 040e8568e69c6a8946208a5f8204b688d16b5ca5..c72fd25abcd2002c58f7871fea2ae07c7ab289cc 100644 (file)
                                status = "disabled";
                        };
 
+                       nb_periph_clk: nb-periph-clk@13000 {
+                               compatible = "marvell,armada-3700-periph-clock-nb";
+                               reg = <0x13000 0x100>;
+                               clocks = <&tbg 0>, <&tbg 1>, <&tbg 2>, <&tbg 3>;
+                               #clock-cells = <1>;
+                       };
+
+                       sb_periph_clk: sb-periph-clk@18000 {
+                               compatible = "marvell,armada-3700-periph-clock-sb";
+                               reg = <0x18000 0x100>;
+                               clocks = <&tbg 0>, <&tbg 1>, <&tbg 2>, <&tbg 3>;
+                               #clock-cells = <1>;
+                       };
+
+                       tbg: tbg@13200 {
+                               compatible = "marvell,armada-3700-tbg-clock";
+                               reg = <0x13200 0x100>;
+                               #clock-cells = <1>;
+                       };
+
                        pinctrl_nb: pinctrl-nb@13800 {
                                compatible = "marvell,armada3710-nb-pinctrl",
                                "syscon", "simple-mfd";
index 60a6859c247eac3def04ac28f7f5ed1f84b903dd..edb4ca58ea575878dec69f71647366ca686bf177 100644 (file)
@@ -88,6 +88,7 @@ source "drivers/clk/uniphier/Kconfig"
 source "drivers/clk/exynos/Kconfig"
 source "drivers/clk/at91/Kconfig"
 source "drivers/clk/renesas/Kconfig"
+source "drivers/clk/mvebu/Kconfig"
 
 config ICS8N3QV01
        bool "Enable ICS8N3QV01 VCXO driver"
index 0b4f38527ac7c1dc3739eba9fe37d4a8a3e86718..58139b13a89b3daf9ecbafc2128d89ee4afa5ee3 100644 (file)
@@ -11,6 +11,7 @@ obj-y += tegra/
 obj-$(CONFIG_ARCH_ASPEED) += aspeed/
 obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
 obj-$(CONFIG_CLK_AT91) += at91/
+obj-$(CONFIG_CLK_MVEBU) += mvebu/
 obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o
 obj-$(CONFIG_CLK_BOSTON) += clk_boston.o
 obj-$(CONFIG_CLK_EXYNOS) += exynos/
diff --git a/drivers/clk/mvebu/Kconfig b/drivers/clk/mvebu/Kconfig
new file mode 100644 (file)
index 0000000..e776a15
--- /dev/null
@@ -0,0 +1,11 @@
+config CLK_MVEBU
+       bool "MVEBU clock drivers"
+       depends on CLK && ARCH_MVEBU
+       help
+         Enable support for clock present on Marvell MVEBU SoCs.
+
+config CLK_ARMADA_3720
+       bool "Marvell Armada 3720 clock driver"
+       depends on CLK_MVEBU && ARM64
+       help
+         Enable this to support the clocks on Marvell Armada 3720 SoC.
diff --git a/drivers/clk/mvebu/Makefile b/drivers/clk/mvebu/Makefile
new file mode 100644 (file)
index 0000000..7f80313
--- /dev/null
@@ -0,0 +1 @@
+obj-$(CONFIG_CLK_ARMADA_3720) += armada-37xx-periph.o armada-37xx-tbg.o
diff --git a/drivers/clk/mvebu/armada-37xx-periph.c b/drivers/clk/mvebu/armada-37xx-periph.c
new file mode 100644 (file)
index 0000000..af08e3d
--- /dev/null
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Marvell Armada 37xx SoC Peripheral clocks
+ *
+ * Marek Behun <marek.behun@nic.cz>
+ *
+ * Based on Linux driver by:
+ *   Gregory CLEMENT <gregory.clement@free-electrons.com>
+ */
+
+#include <common.h>
+#include <malloc.h>
+#include <clk-uclass.h>
+#include <clk.h>
+#include <dm.h>
+#include <asm/io.h>
+#include <asm/arch/cpu.h>
+
+#define TBG_SEL                0x0
+#define DIV_SEL0       0x4
+#define DIV_SEL1       0x8
+#define DIV_SEL2       0xC
+#define CLK_SEL                0x10
+#define CLK_DIS                0x14
+
+enum a37xx_periph_parent {
+       TBG_A_P         = 0,
+       TBG_B_P         = 1,
+       TBG_A_S         = 2,
+       TBG_B_S         = 3,
+       MAX_TBG_PARENTS = 4,
+       XTAL            = 4,
+       MAX_PARENTS     = 5,
+};
+
+static const struct {
+       const char *name;
+       enum a37xx_periph_parent parent;
+} a37xx_periph_parent_names[] = {
+       { "TBG-A-P", TBG_A_P },
+       { "TBG-B-P", TBG_B_P },
+       { "TBG-A-S", TBG_A_S },
+       { "TBG-B-S", TBG_B_S },
+       { "xtal",    XTAL    },
+};
+
+struct clk_periph;
+
+struct a37xx_periphclk {
+       void __iomem *reg;
+
+       ulong parents[MAX_PARENTS];
+
+       const struct clk_periph *clks;
+       bool clk_has_periph_parent[16];
+       int clk_parent[16];
+
+       int count;
+};
+
+struct clk_div_table {
+       u32 div;
+       u32 val;
+};
+
+struct clk_periph {
+       const char *name;
+
+       const char *parent_name;
+
+       u32 disable_bit;
+       int mux_shift;
+
+       const struct clk_div_table *div_table[2];
+       s32 div_reg_off[2];
+       u32 div_mask[2];
+       int div_shift[2];
+
+       unsigned can_gate : 1;
+       unsigned can_mux : 1;
+       unsigned dividers : 2;
+};
+
+static const struct clk_div_table div_table1[] = {
+       { 1, 1 },
+       { 2, 2 },
+       { 0, 0 },
+};
+
+static const struct clk_div_table div_table2[] = {
+       { 2, 1 },
+       { 4, 2 },
+       { 0, 0 },
+};
+
+static const struct clk_div_table div_table6[] = {
+       { 1, 1 },
+       { 2, 2 },
+       { 3, 3 },
+       { 4, 4 },
+       { 5, 5 },
+       { 6, 6 },
+       { 0, 0 },
+};
+
+#define CLK_FULL_DD(_n, _d, _mux, _r0, _r1, _s0, _s1)  \
+       {                                               \
+               .name = #_n,                            \
+               .disable_bit = BIT(_d),                 \
+               .mux_shift = _mux,                      \
+               .div_table[0] = div_table6,             \
+               .div_table[1] = div_table6,             \
+               .div_reg_off[0] = _r0,                  \
+               .div_reg_off[1] = _r1,                  \
+               .div_shift[0] = _s0,                    \
+               .div_shift[1] = _s1,                    \
+               .div_mask[0] = 7,                       \
+               .div_mask[1] = 7,                       \
+               .can_gate = 1,                          \
+               .can_mux = 1,                           \
+               .dividers = 2,                          \
+       }
+
+#define CLK_FULL(_n, _d, _mux, _r, _s, _m, _t) \
+       {                                       \
+               .name = #_n,                    \
+               .disable_bit = BIT(_d),         \
+               .mux_shift = _mux,              \
+               .div_table[0] = _t,             \
+               .div_reg_off[0] = _r,           \
+               .div_shift[0] = _s,             \
+               .div_mask[0] = _m,              \
+               .can_gate = 1,                  \
+               .can_mux = 1,                   \
+               .dividers = 1,                  \
+       }
+
+#define CLK_GATE_DIV(_n, _d, _r, _s, _m, _t, _p)       \
+       {                                               \
+               .name = #_n,                            \
+               .parent_name = _p,                      \
+               .disable_bit = BIT(_d),                 \
+               .div_table[0] = _t,                     \
+               .div_reg_off[0] = _r,                   \
+               .div_shift[0] = _s,                     \
+               .div_mask[0] = _m,                      \
+               .can_gate = 1,                          \
+               .dividers = 1,                          \
+       }
+
+#define CLK_GATE(_n, _d, _p)           \
+       {                               \
+               .name = #_n,            \
+               .parent_name = _p,      \
+               .disable_bit = BIT(_d), \
+               .can_gate = 1,          \
+       }
+
+#define CLK_MUX_DIV(_n, _mux, _r, _s, _m, _t)  \
+       {                                       \
+               .name = #_n,                    \
+               .mux_shift = _mux,              \
+               .div_table[0] = _t,             \
+               .div_reg_off[0] = _r,           \
+               .div_shift[0] = _s,             \
+               .div_mask[0] = _m,              \
+               .can_mux = 1,                   \
+               .dividers = 1,                  \
+       }
+
+#define CLK_MUX_DD(_n, _mux, _r0, _r1, _s0, _s1)       \
+       {                                               \
+               .name = #_n,                            \
+               .mux_shift = _mux,                      \
+               .div_table[0] = div_table6,             \
+               .div_table[1] = div_table6,             \
+               .div_reg_off[0] = _r0,                  \
+               .div_reg_off[1] = _r1,                  \
+               .div_shift[0] = _s0,                    \
+               .div_shift[1] = _s1,                    \
+               .div_mask[0] = 7,                       \
+               .div_mask[1] = 7,                       \
+               .can_mux = 1,                           \
+               .dividers = 2,                          \
+       }
+
+/* NB periph clocks */
+static const struct clk_periph clks_nb[] = {
+       CLK_FULL_DD(mmc, 2, 0, DIV_SEL2, DIV_SEL2, 16, 13),
+       CLK_FULL_DD(sata_host, 3, 2, DIV_SEL2, DIV_SEL2, 10, 7),
+       CLK_FULL_DD(sec_at, 6, 4, DIV_SEL1, DIV_SEL1, 3, 0),
+       CLK_FULL_DD(sec_dap, 7, 6, DIV_SEL1, DIV_SEL1, 9, 6),
+       CLK_FULL_DD(tscem, 8, 8, DIV_SEL1, DIV_SEL1, 15, 12),
+       CLK_FULL(tscem_tmx, 10, 10, DIV_SEL1, 18, 7, div_table6),
+       CLK_GATE(avs, 11, "xtal"),
+       CLK_FULL_DD(sqf, 12, 12, DIV_SEL1, DIV_SEL1, 27, 24),
+       CLK_FULL_DD(pwm, 13, 14, DIV_SEL0, DIV_SEL0, 3, 0),
+       CLK_GATE(i2c_2, 16, "xtal"),
+       CLK_GATE(i2c_1, 17, "xtal"),
+       CLK_GATE_DIV(ddr_phy, 19, DIV_SEL0, 18, 1, div_table2, "TBG-A-S"),
+       CLK_FULL_DD(ddr_fclk, 21, 16, DIV_SEL0, DIV_SEL0, 15, 12),
+       CLK_FULL(trace, 22, 18, DIV_SEL0, 20, 7, div_table6),
+       CLK_FULL(counter, 23, 20, DIV_SEL0, 23, 7, div_table6),
+       CLK_FULL_DD(eip97, 24, 24, DIV_SEL2, DIV_SEL2, 22, 19),
+       CLK_MUX_DIV(cpu, 22, DIV_SEL0, 28, 7, div_table6),
+       { },
+};
+
+/* SB periph clocks */
+static const struct clk_periph clks_sb[] = {
+       CLK_MUX_DD(gbe_50, 6, DIV_SEL2, DIV_SEL2, 6, 9),
+       CLK_MUX_DD(gbe_core, 8, DIV_SEL1, DIV_SEL1, 18, 21),
+       CLK_MUX_DD(gbe_125, 10, DIV_SEL1, DIV_SEL1, 6, 9),
+       CLK_GATE(gbe1_50, 0, "gbe_50"),
+       CLK_GATE(gbe0_50, 1, "gbe_50"),
+       CLK_GATE(gbe1_125, 2, "gbe_125"),
+       CLK_GATE(gbe0_125, 3, "gbe_125"),
+       CLK_GATE_DIV(gbe1_core, 4, DIV_SEL1, 13, 1, div_table1, "gbe_core"),
+       CLK_GATE_DIV(gbe0_core, 5, DIV_SEL1, 14, 1, div_table1, "gbe_core"),
+       CLK_GATE_DIV(gbe_bm, 12, DIV_SEL1, 0, 1, div_table1, "gbe_core"),
+       CLK_FULL_DD(sdio, 11, 14, DIV_SEL0, DIV_SEL0, 3, 6),
+       CLK_FULL_DD(usb32_usb2_sys, 16, 16, DIV_SEL0, DIV_SEL0, 9, 12),
+       CLK_FULL_DD(usb32_ss_sys, 17, 18, DIV_SEL0, DIV_SEL0, 15, 18),
+       { },
+};
+
+static inline int get_mux(struct a37xx_periphclk *priv, int shift)
+{
+       return (readl(priv->reg + TBG_SEL) >> shift) & 3;
+}
+
+static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id);
+
+static ulong get_parent_rate(struct a37xx_periphclk *priv, int id)
+{
+       const struct clk_periph *clk = &priv->clks[id];
+       ulong res;
+
+       if (clk->can_mux) {
+               /* parent is one of TBG clocks */
+               int tbg = get_mux(priv, clk->mux_shift);
+
+               res = priv->parents[tbg];
+       } else if (priv->clk_has_periph_parent[id]) {
+               /* parent is one of other periph clocks */
+
+               if (priv->clk_parent[id] >= priv->count)
+                       return -EINVAL;
+
+               res = periph_clk_get_rate(priv, priv->clk_parent[id]);
+       } else {
+               /* otherwise parent is one of TBGs or XTAL */
+
+               if (priv->clk_parent[id] >= MAX_PARENTS)
+                       return -EINVAL;
+
+               res = priv->parents[priv->clk_parent[id]];
+       }
+
+       return res;
+}
+
+static ulong get_div(struct a37xx_periphclk *priv,
+                    const struct clk_periph *clk, int idx)
+{
+       const struct clk_div_table *i;
+       u32 reg;
+
+       reg = readl(priv->reg + clk->div_reg_off[idx]);
+       reg = (reg >> clk->div_shift[idx]) & clk->div_mask[idx];
+
+       /* find divisor for register value val */
+       for (i = clk->div_table[idx]; i && i->div != 0; ++i)
+               if (i->val == reg)
+                       return i->div;
+
+       return 0;
+}
+
+static ulong periph_clk_get_rate(struct a37xx_periphclk *priv, int id)
+{
+       const struct clk_periph *clk = &priv->clks[id];
+       ulong rate, div;
+       int i;
+
+       rate = get_parent_rate(priv, id);
+       if (rate == -EINVAL)
+               return -EINVAL;
+
+       /* divide the parent rate by dividers */
+       div = 1;
+       for (i = 0; i < clk->dividers; ++i)
+               div *= get_div(priv, clk, i);
+
+       if (!div)
+               return 0;
+
+       return DIV_ROUND_UP(rate, div);
+}
+
+static ulong armada_37xx_periph_clk_get_rate(struct clk *clk)
+{
+       struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
+
+       if (clk->id >= priv->count)
+               return -EINVAL;
+
+       return periph_clk_get_rate(priv, clk->id);
+}
+
+static int periph_clk_enable(struct clk *clk, int enable)
+{
+       struct a37xx_periphclk *priv = dev_get_priv(clk->dev);
+       const struct clk_periph *periph_clk = &priv->clks[clk->id];
+
+       if (clk->id >= priv->count)
+               return -EINVAL;
+
+       if (!periph_clk->can_gate)
+               return -ENOTSUPP;
+
+       if (enable)
+               clrbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
+       else
+               setbits_le32(priv->reg + CLK_DIS, periph_clk->disable_bit);
+
+       return 0;
+}
+
+static int armada_37xx_periph_clk_enable(struct clk *clk)
+{
+       return periph_clk_enable(clk, 1);
+}
+
+static int armada_37xx_periph_clk_disable(struct clk *clk)
+{
+       return periph_clk_enable(clk, 0);
+}
+
+int armada_37xx_periph_clk_dump(struct udevice *dev)
+{
+       struct a37xx_periphclk *priv = dev_get_priv(dev);
+       const struct clk_periph *clks;
+       int i;
+
+       if (!priv)
+               return -ENODEV;
+
+       clks = priv->clks;
+
+       for (i = 0; i < priv->count; ++i)
+               printf("  %s at %lu Hz\n", clks[i].name,
+                      periph_clk_get_rate(priv, i));
+       printf("\n");
+
+       return 0;
+}
+
+static int armada_37xx_periph_clk_probe(struct udevice *dev)
+{
+       struct a37xx_periphclk *priv = dev_get_priv(dev);
+       const struct clk_periph *clks;
+       int ret, i;
+
+       clks = (const struct clk_periph *)dev_get_driver_data(dev);
+       if (!clks)
+               return -ENODEV;
+
+       priv->reg = dev_read_addr_ptr(dev);
+       if (!priv->reg) {
+               dev_err(dev, "no io address\n");
+               return -ENODEV;
+       }
+
+       /* count clk_periph nodes */
+       priv->count = 0;
+       while (clks[priv->count].name)
+               priv->count++;
+
+       priv->clks = clks;
+
+       /* assign parent IDs to nodes which have non-NULL parent_name */
+       for (i = 0; i < priv->count; ++i) {
+               int j;
+
+               if (!clks[i].parent_name)
+                       continue;
+
+               /* first try if parent_name is one of TBGs or XTAL */
+               for (j = 0; j < MAX_PARENTS; ++j)
+                       if (!strcmp(clks[i].parent_name,
+                                   a37xx_periph_parent_names[j].name))
+                               break;
+
+               if (j < MAX_PARENTS) {
+                       priv->clk_has_periph_parent[i] = false;
+                       priv->clk_parent[i] =
+                               a37xx_periph_parent_names[j].parent;
+                       continue;
+               }
+
+               /* else parent_name should be one of other periph clocks */
+               for (j = 0; j < priv->count; ++j) {
+                       if (!strcmp(clks[i].parent_name, clks[j].name))
+                               break;
+               }
+
+               if (j < priv->count) {
+                       priv->clk_has_periph_parent[i] = true;
+                       priv->clk_parent[i] = j;
+                       continue;
+               }
+
+               dev_err(dev, "undefined parent %s\n", clks[i].parent_name);
+               return -EINVAL;
+       }
+
+       for (i = 0; i < MAX_PARENTS; ++i) {
+               struct clk clk;
+
+               if (i == XTAL) {
+                       priv->parents[i] = get_ref_clk() * 1000000;
+                       continue;
+               }
+
+               ret = clk_get_by_index(dev, i, &clk);
+               if (ret) {
+                       dev_err(dev, "one of parent clocks (%i) missing: %i\n",
+                               i, ret);
+                       return -ENODEV;
+               }
+
+               priv->parents[i] = clk_get_rate(&clk);
+               clk_free(&clk);
+       }
+
+       return 0;
+}
+
+static const struct clk_ops armada_37xx_periph_clk_ops = {
+       .get_rate = armada_37xx_periph_clk_get_rate,
+       .enable = armada_37xx_periph_clk_enable,
+       .disable = armada_37xx_periph_clk_disable,
+};
+
+static const struct udevice_id armada_37xx_periph_clk_ids[] = {
+       {
+               .compatible = "marvell,armada-3700-periph-clock-nb",
+               .data = (ulong)clks_nb,
+       },
+       {
+               .compatible = "marvell,armada-3700-periph-clock-sb",
+               .data = (ulong)clks_sb,
+       },
+       {}
+};
+
+U_BOOT_DRIVER(armada_37xx_periph_clk) = {
+       .name           = "armada_37xx_periph_clk",
+       .id             = UCLASS_CLK,
+       .of_match       = armada_37xx_periph_clk_ids,
+       .ops            = &armada_37xx_periph_clk_ops,
+       .priv_auto_alloc_size = sizeof(struct a37xx_periphclk),
+       .probe          = armada_37xx_periph_clk_probe,
+};
diff --git a/drivers/clk/mvebu/armada-37xx-tbg.c b/drivers/clk/mvebu/armada-37xx-tbg.c
new file mode 100644 (file)
index 0000000..c035b8f
--- /dev/null
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Marvell Armada 37xx SoC Time Base Generator clocks
+ *
+ * Marek Behun <marek.behun@nic.cz>
+ *
+ * Based on Linux driver by:
+ *   Gregory CLEMENT <gregory.clement@free-electrons.com>
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <clk.h>
+#include <dm.h>
+#include <asm/io.h>
+#include <asm/arch/cpu.h>
+
+#define NUM_TBG            4
+
+#define TBG_CTRL0              0x4
+#define TBG_CTRL1              0x8
+#define TBG_CTRL7              0x20
+#define TBG_CTRL8              0x30
+
+#define TBG_DIV_MASK           0x1FF
+
+#define TBG_A_REFDIV           0
+#define TBG_B_REFDIV           16
+
+#define TBG_A_FBDIV            2
+#define TBG_B_FBDIV            18
+
+#define TBG_A_VCODIV_SE                0
+#define TBG_B_VCODIV_SE                16
+
+#define TBG_A_VCODIV_DIFF      1
+#define TBG_B_VCODIV_DIFF      17
+
+struct tbg_def {
+       const char *name;
+       u32 refdiv_offset;
+       u32 fbdiv_offset;
+       u32 vcodiv_reg;
+       u32 vcodiv_offset;
+};
+
+static const struct tbg_def tbg[NUM_TBG] = {
+       {"TBG-A-P", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL8, TBG_A_VCODIV_DIFF},
+       {"TBG-B-P", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL8, TBG_B_VCODIV_DIFF},
+       {"TBG-A-S", TBG_A_REFDIV, TBG_A_FBDIV, TBG_CTRL1, TBG_A_VCODIV_SE},
+       {"TBG-B-S", TBG_B_REFDIV, TBG_B_FBDIV, TBG_CTRL1, TBG_B_VCODIV_SE},
+};
+
+struct a37xx_tbgclk {
+       ulong rates[NUM_TBG];
+       unsigned int mult[NUM_TBG];
+       unsigned int div[NUM_TBG];
+};
+
+static unsigned int tbg_get_mult(void __iomem *reg, const struct tbg_def *ptbg)
+{
+       u32 val;
+
+       val = readl(reg + TBG_CTRL0);
+
+       return ((val >> ptbg->fbdiv_offset) & TBG_DIV_MASK) << 2;
+}
+
+static unsigned int tbg_get_div(void __iomem *reg, const struct tbg_def *ptbg)
+{
+       u32 val;
+       unsigned int div;
+
+       val = readl(reg + TBG_CTRL7);
+
+       div = (val >> ptbg->refdiv_offset) & TBG_DIV_MASK;
+       if (div == 0)
+               div = 1;
+       val = readl(reg + ptbg->vcodiv_reg);
+
+       div *= 1 << ((val >>  ptbg->vcodiv_offset) & TBG_DIV_MASK);
+
+       return div;
+}
+
+static ulong armada_37xx_tbg_clk_get_rate(struct clk *clk)
+{
+       struct a37xx_tbgclk *priv = dev_get_priv(clk->dev);
+
+       if (clk->id >= NUM_TBG)
+               return -ENODEV;
+
+       return priv->rates[clk->id];
+}
+
+int armada_37xx_tbg_clk_dump(struct udevice *dev)
+{
+       struct a37xx_tbgclk *priv = dev_get_priv(dev);
+       int i;
+
+       for (i = 0; i < NUM_TBG; ++i)
+               printf("  %s at %lu Hz\n", tbg[i].name,
+                      priv->rates[i]);
+       printf("\n");
+
+       return 0;
+}
+
+static int armada_37xx_tbg_clk_probe(struct udevice *dev)
+{
+       struct a37xx_tbgclk *priv = dev_get_priv(dev);
+       void __iomem *reg;
+       ulong xtal;
+       int i;
+
+       reg = dev_read_addr_ptr(dev);
+       if (!reg) {
+               dev_err(dev, "no io address\n");
+               return -ENODEV;
+       }
+
+       xtal = (ulong)get_ref_clk() * 1000000;
+
+       for (i = 0; i < NUM_TBG; ++i) {
+               unsigned int mult, div;
+
+               mult = tbg_get_mult(reg, &tbg[i]);
+               div = tbg_get_div(reg, &tbg[i]);
+
+               priv->rates[i] = (xtal * mult) / div;
+       }
+
+       return 0;
+}
+
+static const struct clk_ops armada_37xx_tbg_clk_ops = {
+       .get_rate = armada_37xx_tbg_clk_get_rate,
+};
+
+static const struct udevice_id armada_37xx_tbg_clk_ids[] = {
+       { .compatible = "marvell,armada-3700-tbg-clock" },
+       {}
+};
+
+U_BOOT_DRIVER(armada_37xx_tbg_clk) = {
+       .name           = "armada_37xx_tbg_clk",
+       .id             = UCLASS_CLK,
+       .of_match       = armada_37xx_tbg_clk_ids,
+       .ops            = &armada_37xx_tbg_clk_ops,
+       .priv_auto_alloc_size = sizeof(struct a37xx_tbgclk),
+       .probe          = armada_37xx_tbg_clk_probe,
+};