Message ID | 20180307215216.10418-15-marek.behun@nic.cz |
---|---|
State | Superseded |
Delegated to: | Stefan Roese |
Headers | show |
Series | More support for Armada 37xx boards | expand |
On 07.03.2018 22:52, Marek Behún wrote: > 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> Looks good, so: Reviewed-by: Stefan Roese <sr@denx.de> Thanks, Stefan > --- > arch/arm/dts/armada-37xx.dtsi | 20 ++ > drivers/clk/Kconfig | 1 + > drivers/clk/Makefile | 1 + > drivers/clk/mvebu/Kconfig | 11 + > drivers/clk/mvebu/Makefile | 1 + > drivers/clk/mvebu/armada-37xx-periph.c | 464 +++++++++++++++++++++++++++++++++ > drivers/clk/mvebu/armada-37xx-tbg.c | 153 +++++++++++ > 7 files changed, 651 insertions(+) > > diff --git a/arch/arm/dts/armada-37xx.dtsi b/arch/arm/dts/armada-37xx.dtsi > index d0529637f4..e848812fca 100644 > --- a/arch/arm/dts/armada-37xx.dtsi > +++ b/arch/arm/dts/armada-37xx.dtsi > @@ -106,6 +106,26 @@ > 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"; > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig > index cdfa052c16..a40c8e5c8f 100644 > --- a/drivers/clk/Kconfig > +++ b/drivers/clk/Kconfig > @@ -80,5 +80,6 @@ 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" > > endmenu > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile > index dab106ab7f..094bcf5847 100644 > --- a/drivers/clk/Makefile > +++ b/drivers/clk/Makefile > @@ -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 > index 0000000000..e776a15e7b > --- /dev/null > +++ b/drivers/clk/mvebu/Kconfig > @@ -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 > index 0000000000..7f80313203 > --- /dev/null > +++ b/drivers/clk/mvebu/Makefile > @@ -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 > index 0000000000..94eabe5f19 > --- /dev/null > +++ b/drivers/clk/mvebu/armada-37xx-periph.c > @@ -0,0 +1,464 @@ > +/* > + * Marvell Armada 37xx SoC Peripheral clocks > + * > + * Marek Behun <marek.behun@nic.cz> > + * > + * Based on Linux driver by: > + * Gregory CLEMENT <gregory.clement@free-electrons.com> > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#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]; > + > + if (clk->can_mux) { > + /* parent is one of TBG clocks */ > + int tbg = get_mux(priv, clk->mux_shift); > + > + return 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; > + > + return 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; > + > + return priv->parents[priv->clk_parent[id]]; > + } > +} > + > +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 > index 0000000000..e8c654f1a6 > --- /dev/null > +++ b/drivers/clk/mvebu/armada-37xx-tbg.c > @@ -0,0 +1,153 @@ > +/* > + * 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> > + * > + * SPDX-License-Identifier: GPL-2.0+ > + */ > + > +#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, > +}; > Viele Grüße, Stefan
diff --git a/arch/arm/dts/armada-37xx.dtsi b/arch/arm/dts/armada-37xx.dtsi index d0529637f4..e848812fca 100644 --- a/arch/arm/dts/armada-37xx.dtsi +++ b/arch/arm/dts/armada-37xx.dtsi @@ -106,6 +106,26 @@ 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"; diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index cdfa052c16..a40c8e5c8f 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -80,5 +80,6 @@ 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" endmenu diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index dab106ab7f..094bcf5847 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -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 index 0000000000..e776a15e7b --- /dev/null +++ b/drivers/clk/mvebu/Kconfig @@ -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 index 0000000000..7f80313203 --- /dev/null +++ b/drivers/clk/mvebu/Makefile @@ -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 index 0000000000..94eabe5f19 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-periph.c @@ -0,0 +1,464 @@ +/* + * Marvell Armada 37xx SoC Peripheral clocks + * + * Marek Behun <marek.behun@nic.cz> + * + * Based on Linux driver by: + * Gregory CLEMENT <gregory.clement@free-electrons.com> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#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]; + + if (clk->can_mux) { + /* parent is one of TBG clocks */ + int tbg = get_mux(priv, clk->mux_shift); + + return 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; + + return 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; + + return priv->parents[priv->clk_parent[id]]; + } +} + +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 index 0000000000..e8c654f1a6 --- /dev/null +++ b/drivers/clk/mvebu/armada-37xx-tbg.c @@ -0,0 +1,153 @@ +/* + * 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> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#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, +};
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> --- arch/arm/dts/armada-37xx.dtsi | 20 ++ drivers/clk/Kconfig | 1 + drivers/clk/Makefile | 1 + drivers/clk/mvebu/Kconfig | 11 + drivers/clk/mvebu/Makefile | 1 + drivers/clk/mvebu/armada-37xx-periph.c | 464 +++++++++++++++++++++++++++++++++ drivers/clk/mvebu/armada-37xx-tbg.c | 153 +++++++++++ 7 files changed, 651 insertions(+)