[v2,05/10] clk: imx: Add imx6q clock tree support

Message ID 20190402112843.992-6-jagan@amarulasolutions.com
State New
Headers show
Series
  • clk: imx: Add i.MX6 CLK support
Related show

Commit Message

Jagan Teki April 2, 2019, 11:28 a.m. UTC
i.MX6 clock control module comprise of parent clocks, gates, multiplexers,
dividers, PODF, PLL, fixed rate and etc.

So, the U-Boot implementation of ccm has divided into gates and tree.

1) gate clocks are generic clock configuration of enable/disable bit management
   which can be handle via imx6_clock_gate.
2) tree clocks are handle via tree clock management where it link the clocks
   based on the parent clock which usually required to get and set the
   clock rates.

This patch add tree clock management for imx6q USDHC clocks, so the mmc driver
from imx6 can eventually use this so getting the USDHC clock rates.

Unlike Linux, U-Boot implementation may not require to maintain exact clock tree
due to various constrains and use cases. So here is how the clock tree differs
between them.

usdhc clock tree in Linux:
-------------------------
USDHC1 => USDHC1_PODF => USDHC1_SEL => PLL2_PFD2 => PLL2_BUS => PLL2_BYPASS => PLL2 => OSC

usdhc clock tree in U-Boot:
---------------------------
USDHC1 => USDHC1_PODF => USDHC1_SEL => PLL2_PFD2 => PLL2_BUS => OSC

Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
---
 arch/arm/include/asm/arch-mx6/clock.h |  65 ++++++++++++++++
 drivers/clk/imx/clk-imx6-common.c     | 103 ++++++++++++++++++++++++++
 drivers/clk/imx/clk-imx6q.c           |  70 +++++++++++++++++
 3 files changed, 238 insertions(+)

Comments

Lukasz Majewski April 19, 2019, 7:54 a.m. UTC | #1
Hi Jagan,

> i.MX6 clock control module comprise of parent clocks, gates,
> multiplexers, dividers, PODF, PLL, fixed rate and etc.
> 
> So, the U-Boot implementation of ccm has divided into gates and tree.
> 
> 1) gate clocks are generic clock configuration of enable/disable bit
> management which can be handle via imx6_clock_gate.
> 2) tree clocks are handle via tree clock management where it link the
> clocks based on the parent clock which usually required to get and
> set the clock rates.

If I understood the patch series:

The clock hierarchy has been flattened to only two kinds of elements:
gates and "tree".

I'm not sure if we will not find this not enough in the future when one
wants to implement/use video/USB/other clocks.

For example only pllv3 type has several variants:

+enum imx_pllv3_type {
+	IMX_PLLV3_GENERIC,
+	IMX_PLLV3_SYS,
+	IMX_PLLV3_USB,
+	IMX_PLLV3_USB_VF610,
+	IMX_PLLV3_AV,
+	IMX_PLLV3_ENET,
+	IMX_PLLV3_ENET_IMX7,
+	IMX_PLLV3_SYS_VF610,
+	IMX_PLLV3_DDR_IMX7,

USB, USB_VF610 (vybrid), Generic, ENET (which is not implemented by
this series).

With porting Linux approach I'm pretty sure that I can add support for
vybrid in the future when e.g. gadget is converted to DM.

Is this also so predictable with your approach ?

> 
> This patch add tree clock management for imx6q USDHC clocks, so the
> mmc driver from imx6 can eventually use this so getting the USDHC
> clock rates.

But for USDHC you only need gate and read the clock value (which is not
so deepen hidden in the hierarchy).

> 
> Unlike Linux, U-Boot implementation may not require to maintain exact
> clock tree due to various constrains and use cases.

Another use case - ECSPI / ENET.

> So here is how
> the clock tree differs between them.
> 
> usdhc clock tree in Linux:
> -------------------------
> USDHC1 => USDHC1_PODF => USDHC1_SEL => PLL2_PFD2 => PLL2_BUS =>
> PLL2_BYPASS => PLL2 => OSC
> 
> usdhc clock tree in U-Boot:
> ---------------------------
> USDHC1 => USDHC1_PODF => USDHC1_SEL => PLL2_PFD2 => PLL2_BUS => OSC
> 
> Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
> ---
>  arch/arm/include/asm/arch-mx6/clock.h |  65 ++++++++++++++++
>  drivers/clk/imx/clk-imx6-common.c     | 103
> ++++++++++++++++++++++++++ drivers/clk/imx/clk-imx6q.c           |
> 70 +++++++++++++++++ 3 files changed, 238 insertions(+)
> 
> diff --git a/arch/arm/include/asm/arch-mx6/clock.h
> b/arch/arm/include/asm/arch-mx6/clock.h index fa921a9f08..424231c691
> 100644 --- a/arch/arm/include/asm/arch-mx6/clock.h
> +++ b/arch/arm/include/asm/arch-mx6/clock.h
> @@ -21,6 +21,67 @@
>  #define MXC_CLK32	32768
>  #endif
>  
> +#define OSC_24M_ULL	24000000ULL
> +
> +enum imx6_clk_type {
> +	IMX6_CLK_TYPE_SIMPLE	= 0,
> +	IMX6_CLK_TYPE_FIXED,
> +	IMX6_CLK_TYPE_DIV,
> +	IMX6_CLK_TYPE_MUX,
> +	IMX6_CLK_TYPE_PLL_PFD,
> +	IMX6_CLK_TYPE_PLL_DIV,
> +};
> +
> +/**
> + * struct imx6_clk_tree - imx6 ccm clock tree
> + *
> + * @parent:		parent clock tree
> + * @type:		clock type
> + * @off:		register offset of the specified clock
> + * @shift:		number of bits to shift the bitfield
> + * @width:		width of the bitfield
> + * @idx:		index of the specified clock
> + * @fixed_rate:		fixed clock rate
> + */
> +struct imx6_clk_tree {
> +	const unsigned long *parent;
> +	enum imx6_clk_type type;
> +	u16 off;
> +
> +	u8 shift;
> +	u8 width;
> +	u8 idx;
> +	ulong fixed_rate;
> +};
> +
> +#define TREE(_parent, _type, _off, _shift, _width, _idx,
> _fixed_rate) {	\
> +	.parent =
> _parent,						\
> +	.type =
> _type,							\
> +	.off =
> _off,							\
> +	.shift =
> _shift,						\
> +	.width =
> _width,						\
> +	.idx =
> _idx,							\
> +	.fixed_rate =
> _fixed_rate,					\ +}
> +
> +#define
> SIMPLE(_parent)
> \
> +	TREE(_parent, IMX6_CLK_TYPE_SIMPLE, 0, 0, 0, 0, 0)
> +
> +#define
> FIXED(_fixed_rate)						\
> +	TREE(NULL, IMX6_CLK_TYPE_FIXED, 0, 0, 0, 0, _fixed_rate)
> +
> +#define DIV(_parent, _off, _shift,
> _width)				\
> +	TREE(_parent, IMX6_CLK_TYPE_DIV, _off, _shift, _width, 0, 0)
> +
> +#define MUX(_parent, _off, _shift,
> _width)				\
> +	TREE(_parent, IMX6_CLK_TYPE_MUX, _off, _shift, _width, 0, 0)
> +
> +#define PLL_PFD(_parent, _off, _width,
> _idx)				\
> +	TREE(_parent, IMX6_CLK_TYPE_PLL_PFD, _off, 0, _width, _idx,
> 0) +
> +#define PLL_DIV(_parent, _off, _shift,
> _width)				\
> +	TREE(_parent, IMX6_CLK_TYPE_PLL_DIV, _off, _shift, _width,
> 0, 0) +
>  /**
>   * struct imx6_clk_gate - imx6 ccm clock gate
>   *
> @@ -41,19 +102,23 @@ struct imx6_clk_gate {
>   * struct imx6_clk_desc - imx6 clock control module descriptor
>   *
>   * @gates:	ccm clock gates
> + * @tree:	ccm clock tree
>   */
>  struct imx6_clk_desc {
>  	const struct imx6_clk_gate *gates;
> +	const struct imx6_clk_tree *tree;
>  };
>  
>  /**
>   * struct imx6_clk_priv - imx6 clock control module
>   *
>   * @base:	ccm base address
> + * @anatop:	anatop base address
>   * @desc:	ccm descriptor
>   */
>  struct imx6_clk_priv {
>  	void *base;
> +	void *anatop;
>  	const struct imx6_clk_desc *desc;
>  };
>  
> diff --git a/drivers/clk/imx/clk-imx6-common.c
> b/drivers/clk/imx/clk-imx6-common.c index 1d38f51f7e..d21facf2e5
> 100644 --- a/drivers/clk/imx/clk-imx6-common.c
> +++ b/drivers/clk/imx/clk-imx6-common.c
> @@ -6,18 +6,117 @@
>  
>  #include <common.h>
>  #include <clk-uclass.h>
> +#include <div64.h>
>  #include <dm.h>
>  #include <errno.h>
>  #include <asm/io.h>
>  #include <asm/arch/clock.h>
>  #include <linux/log2.h>
>  
> +#include <dt-bindings/clock/imx6qdl-clock.h>
> +
>  static const struct imx6_clk_gate *priv_to_gate(struct imx6_clk_priv
> *priv, unsigned long id)
>  {
>  	return &priv->desc->gates[id];
>  }
>  
> +static const struct imx6_clk_tree *priv_to_tree(struct imx6_clk_priv
> *priv,
> +						unsigned long id)
> +{
> +	return &priv->desc->tree[id];
> +}
> +
> +static u8 get_bitfield(void *base, u8 shift, u8 width)
> +{
> +	return (readl(base) >> shift) & clk_div_mask(width);
> +}
> +
> +static u8 get_mux_parent(const struct imx6_clk_tree *tree, void
> *base) +{
> +	u8 idx = get_bitfield(base + tree->off, tree->shift,
> tree->width); +
> +	return tree->parent[idx];
> +}
> +
> +static ulong get_pll2_bus_rate(struct imx6_clk_priv *priv, unsigned
> long id,
> +			       ulong parent_rate)
> +{
> +	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
> +	u8 div;
> +
> +	div = get_bitfield(priv->anatop + tree->off, tree->shift,
> tree->width);
> +	return (div == 1) ? parent_rate * 22 : parent_rate * 20;
> +}
> +
> +static ulong get_pfd_rate(struct imx6_clk_priv *priv, unsigned long
> id,
> +			  ulong parent_rate)
> +{
> +	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
> +	u64 tmp = parent_rate;
> +	u8 frac;
> +
> +	frac = get_bitfield(priv->anatop + tree->off,
> +			    tree->idx * 8, tree->width);
> +	tmp *= 18;
> +	do_div(tmp, frac);
> +
> +	return tmp;
> +}
> +
> +static ulong get_mux_rate(ulong parent_rate)
> +{
> +	/* derive clock from respective parent */
> +	return parent_rate;
> +}
> +
> +static ulong get_div_rate(struct imx6_clk_priv *priv, unsigned long
> id,
> +			  ulong parent_rate)
> +{
> +	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
> +	u8 podf;
> +
> +	podf = get_bitfield(priv->base + tree->off, tree->shift,
> tree->width);
> +	return parent_rate / (podf + 1);
> +}
> +
> +static ulong imx6_calc_clk_rate(struct imx6_clk_priv *priv, unsigned
> long id) +{
> +	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
> +	ulong rate = 0;
> +
> +	switch (tree->type) {
> +	case IMX6_CLK_TYPE_FIXED:
> +		return tree->fixed_rate;
> +	case IMX6_CLK_TYPE_PLL_DIV:
> +		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
> +		return get_pll2_bus_rate(priv, id, rate);
> +	case IMX6_CLK_TYPE_PLL_PFD:
> +		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
> +		return get_pfd_rate(priv, id, rate);
> +	case IMX6_CLK_TYPE_MUX:
> +		rate = imx6_calc_clk_rate(priv,
> +					  get_mux_parent(tree,
> priv->base));
> +		return get_mux_rate(rate);
> +	case IMX6_CLK_TYPE_DIV:
> +		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
> +		return get_div_rate(priv, id, rate);
> +	case IMX6_CLK_TYPE_SIMPLE:
> +		return imx6_calc_clk_rate(priv, tree->parent[0]);
> +	default:
> +		printf("%s: (TYPE#%d) unhandled\n", __func__,
> tree->type);
> +	}
> +
> +	return rate;
> +}
> +
> +static ulong imx6_clk_get_rate(struct clk *clk)
> +{
> +	struct imx6_clk_priv *priv = dev_get_priv(clk->dev);
> +
> +	return imx6_calc_clk_rate(priv, clk->id);
> +}
> +
>  static int imx6_set_gate(struct clk *clk, bool on)
>  {
>  	struct imx6_clk_priv *priv = dev_get_priv(clk->dev);
> @@ -51,6 +150,7 @@ static int imx6_clk_disable(struct clk *clk)
>  struct clk_ops imx6_clk_ops = {
>  	.enable = imx6_clk_enable,
>  	.disable = imx6_clk_disable,
> +	.get_rate = imx6_clk_get_rate,
>  };
>  
>  int imx6_clk_probe(struct udevice *dev)
> @@ -61,6 +161,9 @@ int imx6_clk_probe(struct udevice *dev)
>  	if (!priv->base)
>  		return -ENOMEM;
>  
> +	/* FIXME get the anatop base via OF_LIVE */
> +	priv->anatop = priv->base + 0x4000;
> +
>  	priv->desc = (const struct imx6_clk_desc
> *)dev_get_driver_data(dev); if (!priv->desc)
>  		return -EINVAL;
> diff --git a/drivers/clk/imx/clk-imx6q.c b/drivers/clk/imx/clk-imx6q.c
> index 8ec713298d..a46d2f6f88 100644
> --- a/drivers/clk/imx/clk-imx6q.c
> +++ b/drivers/clk/imx/clk-imx6q.c
> @@ -10,6 +10,75 @@
>  #include <asm/arch/clock.h>
>  #include <dt-bindings/clock/imx6qdl-clock.h>
>  
> +static const unsigned long pll2_bus[] = {
> +	IMX6QDL_CLK_OSC,
> +};
> +
> +static const unsigned long pfd_352m[] = {
> +	IMX6QDL_CLK_PLL2_BUS,
> +};
> +
> +static const unsigned long usdhc_sel[] = {
> +	IMX6QDL_CLK_PLL2_PFD2_396M,
> +	IMX6QDL_CLK_PLL2_PFD0_352M,
> +};
> +
> +static const unsigned long usdhc1_podf[] = {
> +	IMX6QDL_CLK_USDHC1_SEL,
> +};
> +
> +static const unsigned long usdhc2_podf[] = {
> +	IMX6QDL_CLK_USDHC2_SEL,
> +};
> +
> +static const unsigned long usdhc3_podf[] = {
> +	IMX6QDL_CLK_USDHC3_SEL,
> +};
> +
> +static const unsigned long usdhc4_podf[] = {
> +	IMX6QDL_CLK_USDHC4_SEL,
> +};
> +
> +static const unsigned long usdhc1[] = {
> +	IMX6QDL_CLK_USDHC1_PODF,
> +};
> +
> +static const unsigned long usdhc2[] = {
> +	IMX6QDL_CLK_USDHC2_PODF,
> +};
> +
> +static const unsigned long usdhc3[] = {
> +	IMX6QDL_CLK_USDHC3_PODF,
> +};
> +
> +static const unsigned long usdhc4[] = {
> +	IMX6QDL_CLK_USDHC4_PODF,
> +};
> +
> +static const struct imx6_clk_tree imx6q_tree[] = {
> +	[IMX6QDL_CLK_OSC]		= FIXED(OSC_24M_ULL),
> +
> +	[IMX6QDL_CLK_PLL2_BUS]		= PLL_DIV(pll2_bus,
> 0x30, 13, 1), +
> +	[IMX6QDL_CLK_PLL2_PFD0_352M]	= PLL_PFD(pfd_352m,
> 0x100, 6, 0),
> +	[IMX6QDL_CLK_PLL2_PFD2_396M]	= PLL_PFD(pfd_352m,
> 0x100, 6, 2), +
> +	[IMX6QDL_CLK_USDHC1_SEL]	= MUX(usdhc_sel, 0x01c, 16,
> 1),
> +	[IMX6QDL_CLK_USDHC2_SEL]	= MUX(usdhc_sel, 0x01c, 17,
> 1),
> +	[IMX6QDL_CLK_USDHC3_SEL]	= MUX(usdhc_sel, 0x01c, 18,
> 1),
> +	[IMX6QDL_CLK_USDHC4_SEL]	= MUX(usdhc_sel, 0x01c, 19,
> 1), +
> +	[IMX6QDL_CLK_USDHC1_PODF]	= DIV(usdhc1_podf, 0x024,
> 11, 3),
> +	[IMX6QDL_CLK_USDHC2_PODF]	= DIV(usdhc2_podf, 0x024,
> 16, 3),
> +	[IMX6QDL_CLK_USDHC3_PODF]	= DIV(usdhc3_podf, 0x024,
> 19, 3),
> +	[IMX6QDL_CLK_USDHC4_PODF]	= DIV(usdhc4_podf, 0x024,
> 22, 3), +
> +	[IMX6QDL_CLK_USDHC1]		= SIMPLE(usdhc1),
> +	[IMX6QDL_CLK_USDHC2]		= SIMPLE(usdhc2),
> +	[IMX6QDL_CLK_USDHC3]		= SIMPLE(usdhc3),
> +	[IMX6QDL_CLK_USDHC4]		= SIMPLE(usdhc4),
> +};
> +
>  static const struct imx6_clk_gate imx6q_gates[] = {
>  	[IMX6QDL_CLK_USDHC1]		= GATE(0x080, GENMASK(3,
> 2)), [IMX6QDL_CLK_USDHC2]		= GATE(0x080, GENMASK(5, 4)),
> @@ -19,6 +88,7 @@ static const struct imx6_clk_gate imx6q_gates[] = {
>  
>  static const struct imx6_clk_desc imx6q_clk_desc = {
>  	.gates = imx6q_gates,
> +	.tree = imx6q_tree,
>  };
>  
>  static const struct udevice_id clk_imx6q_ids[] = {




Best regards,

Lukasz Majewski

--

DENX Software Engineering GmbH,      Managing Director: Wolfgang Denk
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-59 Fax: (+49)-8142-66989-80 Email: lukma@denx.de

Patch

diff --git a/arch/arm/include/asm/arch-mx6/clock.h b/arch/arm/include/asm/arch-mx6/clock.h
index fa921a9f08..424231c691 100644
--- a/arch/arm/include/asm/arch-mx6/clock.h
+++ b/arch/arm/include/asm/arch-mx6/clock.h
@@ -21,6 +21,67 @@ 
 #define MXC_CLK32	32768
 #endif
 
+#define OSC_24M_ULL	24000000ULL
+
+enum imx6_clk_type {
+	IMX6_CLK_TYPE_SIMPLE	= 0,
+	IMX6_CLK_TYPE_FIXED,
+	IMX6_CLK_TYPE_DIV,
+	IMX6_CLK_TYPE_MUX,
+	IMX6_CLK_TYPE_PLL_PFD,
+	IMX6_CLK_TYPE_PLL_DIV,
+};
+
+/**
+ * struct imx6_clk_tree - imx6 ccm clock tree
+ *
+ * @parent:		parent clock tree
+ * @type:		clock type
+ * @off:		register offset of the specified clock
+ * @shift:		number of bits to shift the bitfield
+ * @width:		width of the bitfield
+ * @idx:		index of the specified clock
+ * @fixed_rate:		fixed clock rate
+ */
+struct imx6_clk_tree {
+	const unsigned long *parent;
+	enum imx6_clk_type type;
+	u16 off;
+
+	u8 shift;
+	u8 width;
+	u8 idx;
+	ulong fixed_rate;
+};
+
+#define TREE(_parent, _type, _off, _shift, _width, _idx, _fixed_rate) {	\
+	.parent = _parent,						\
+	.type = _type,							\
+	.off = _off,							\
+	.shift = _shift,						\
+	.width = _width,						\
+	.idx = _idx,							\
+	.fixed_rate = _fixed_rate,					\
+}
+
+#define SIMPLE(_parent)							\
+	TREE(_parent, IMX6_CLK_TYPE_SIMPLE, 0, 0, 0, 0, 0)
+
+#define FIXED(_fixed_rate)						\
+	TREE(NULL, IMX6_CLK_TYPE_FIXED, 0, 0, 0, 0, _fixed_rate)
+
+#define DIV(_parent, _off, _shift, _width)				\
+	TREE(_parent, IMX6_CLK_TYPE_DIV, _off, _shift, _width, 0, 0)
+
+#define MUX(_parent, _off, _shift, _width)				\
+	TREE(_parent, IMX6_CLK_TYPE_MUX, _off, _shift, _width, 0, 0)
+
+#define PLL_PFD(_parent, _off, _width, _idx)				\
+	TREE(_parent, IMX6_CLK_TYPE_PLL_PFD, _off, 0, _width, _idx, 0)
+
+#define PLL_DIV(_parent, _off, _shift, _width)				\
+	TREE(_parent, IMX6_CLK_TYPE_PLL_DIV, _off, _shift, _width, 0, 0)
+
 /**
  * struct imx6_clk_gate - imx6 ccm clock gate
  *
@@ -41,19 +102,23 @@  struct imx6_clk_gate {
  * struct imx6_clk_desc - imx6 clock control module descriptor
  *
  * @gates:	ccm clock gates
+ * @tree:	ccm clock tree
  */
 struct imx6_clk_desc {
 	const struct imx6_clk_gate *gates;
+	const struct imx6_clk_tree *tree;
 };
 
 /**
  * struct imx6_clk_priv - imx6 clock control module
  *
  * @base:	ccm base address
+ * @anatop:	anatop base address
  * @desc:	ccm descriptor
  */
 struct imx6_clk_priv {
 	void *base;
+	void *anatop;
 	const struct imx6_clk_desc *desc;
 };
 
diff --git a/drivers/clk/imx/clk-imx6-common.c b/drivers/clk/imx/clk-imx6-common.c
index 1d38f51f7e..d21facf2e5 100644
--- a/drivers/clk/imx/clk-imx6-common.c
+++ b/drivers/clk/imx/clk-imx6-common.c
@@ -6,18 +6,117 @@ 
 
 #include <common.h>
 #include <clk-uclass.h>
+#include <div64.h>
 #include <dm.h>
 #include <errno.h>
 #include <asm/io.h>
 #include <asm/arch/clock.h>
 #include <linux/log2.h>
 
+#include <dt-bindings/clock/imx6qdl-clock.h>
+
 static const struct imx6_clk_gate *priv_to_gate(struct imx6_clk_priv *priv,
 						unsigned long id)
 {
 	return &priv->desc->gates[id];
 }
 
+static const struct imx6_clk_tree *priv_to_tree(struct imx6_clk_priv *priv,
+						unsigned long id)
+{
+	return &priv->desc->tree[id];
+}
+
+static u8 get_bitfield(void *base, u8 shift, u8 width)
+{
+	return (readl(base) >> shift) & clk_div_mask(width);
+}
+
+static u8 get_mux_parent(const struct imx6_clk_tree *tree, void *base)
+{
+	u8 idx = get_bitfield(base + tree->off, tree->shift, tree->width);
+
+	return tree->parent[idx];
+}
+
+static ulong get_pll2_bus_rate(struct imx6_clk_priv *priv, unsigned long id,
+			       ulong parent_rate)
+{
+	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
+	u8 div;
+
+	div = get_bitfield(priv->anatop + tree->off, tree->shift, tree->width);
+	return (div == 1) ? parent_rate * 22 : parent_rate * 20;
+}
+
+static ulong get_pfd_rate(struct imx6_clk_priv *priv, unsigned long id,
+			  ulong parent_rate)
+{
+	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
+	u64 tmp = parent_rate;
+	u8 frac;
+
+	frac = get_bitfield(priv->anatop + tree->off,
+			    tree->idx * 8, tree->width);
+	tmp *= 18;
+	do_div(tmp, frac);
+
+	return tmp;
+}
+
+static ulong get_mux_rate(ulong parent_rate)
+{
+	/* derive clock from respective parent */
+	return parent_rate;
+}
+
+static ulong get_div_rate(struct imx6_clk_priv *priv, unsigned long id,
+			  ulong parent_rate)
+{
+	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
+	u8 podf;
+
+	podf = get_bitfield(priv->base + tree->off, tree->shift, tree->width);
+	return parent_rate / (podf + 1);
+}
+
+static ulong imx6_calc_clk_rate(struct imx6_clk_priv *priv, unsigned long id)
+{
+	const struct imx6_clk_tree *tree = priv_to_tree(priv, id);
+	ulong rate = 0;
+
+	switch (tree->type) {
+	case IMX6_CLK_TYPE_FIXED:
+		return tree->fixed_rate;
+	case IMX6_CLK_TYPE_PLL_DIV:
+		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
+		return get_pll2_bus_rate(priv, id, rate);
+	case IMX6_CLK_TYPE_PLL_PFD:
+		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
+		return get_pfd_rate(priv, id, rate);
+	case IMX6_CLK_TYPE_MUX:
+		rate = imx6_calc_clk_rate(priv,
+					  get_mux_parent(tree, priv->base));
+		return get_mux_rate(rate);
+	case IMX6_CLK_TYPE_DIV:
+		rate = imx6_calc_clk_rate(priv, tree->parent[0]);
+		return get_div_rate(priv, id, rate);
+	case IMX6_CLK_TYPE_SIMPLE:
+		return imx6_calc_clk_rate(priv, tree->parent[0]);
+	default:
+		printf("%s: (TYPE#%d) unhandled\n", __func__, tree->type);
+	}
+
+	return rate;
+}
+
+static ulong imx6_clk_get_rate(struct clk *clk)
+{
+	struct imx6_clk_priv *priv = dev_get_priv(clk->dev);
+
+	return imx6_calc_clk_rate(priv, clk->id);
+}
+
 static int imx6_set_gate(struct clk *clk, bool on)
 {
 	struct imx6_clk_priv *priv = dev_get_priv(clk->dev);
@@ -51,6 +150,7 @@  static int imx6_clk_disable(struct clk *clk)
 struct clk_ops imx6_clk_ops = {
 	.enable = imx6_clk_enable,
 	.disable = imx6_clk_disable,
+	.get_rate = imx6_clk_get_rate,
 };
 
 int imx6_clk_probe(struct udevice *dev)
@@ -61,6 +161,9 @@  int imx6_clk_probe(struct udevice *dev)
 	if (!priv->base)
 		return -ENOMEM;
 
+	/* FIXME get the anatop base via OF_LIVE */
+	priv->anatop = priv->base + 0x4000;
+
 	priv->desc = (const struct imx6_clk_desc *)dev_get_driver_data(dev);
 	if (!priv->desc)
 		return -EINVAL;
diff --git a/drivers/clk/imx/clk-imx6q.c b/drivers/clk/imx/clk-imx6q.c
index 8ec713298d..a46d2f6f88 100644
--- a/drivers/clk/imx/clk-imx6q.c
+++ b/drivers/clk/imx/clk-imx6q.c
@@ -10,6 +10,75 @@ 
 #include <asm/arch/clock.h>
 #include <dt-bindings/clock/imx6qdl-clock.h>
 
+static const unsigned long pll2_bus[] = {
+	IMX6QDL_CLK_OSC,
+};
+
+static const unsigned long pfd_352m[] = {
+	IMX6QDL_CLK_PLL2_BUS,
+};
+
+static const unsigned long usdhc_sel[] = {
+	IMX6QDL_CLK_PLL2_PFD2_396M,
+	IMX6QDL_CLK_PLL2_PFD0_352M,
+};
+
+static const unsigned long usdhc1_podf[] = {
+	IMX6QDL_CLK_USDHC1_SEL,
+};
+
+static const unsigned long usdhc2_podf[] = {
+	IMX6QDL_CLK_USDHC2_SEL,
+};
+
+static const unsigned long usdhc3_podf[] = {
+	IMX6QDL_CLK_USDHC3_SEL,
+};
+
+static const unsigned long usdhc4_podf[] = {
+	IMX6QDL_CLK_USDHC4_SEL,
+};
+
+static const unsigned long usdhc1[] = {
+	IMX6QDL_CLK_USDHC1_PODF,
+};
+
+static const unsigned long usdhc2[] = {
+	IMX6QDL_CLK_USDHC2_PODF,
+};
+
+static const unsigned long usdhc3[] = {
+	IMX6QDL_CLK_USDHC3_PODF,
+};
+
+static const unsigned long usdhc4[] = {
+	IMX6QDL_CLK_USDHC4_PODF,
+};
+
+static const struct imx6_clk_tree imx6q_tree[] = {
+	[IMX6QDL_CLK_OSC]		= FIXED(OSC_24M_ULL),
+
+	[IMX6QDL_CLK_PLL2_BUS]		= PLL_DIV(pll2_bus, 0x30, 13, 1),
+
+	[IMX6QDL_CLK_PLL2_PFD0_352M]	= PLL_PFD(pfd_352m, 0x100, 6, 0),
+	[IMX6QDL_CLK_PLL2_PFD2_396M]	= PLL_PFD(pfd_352m, 0x100, 6, 2),
+
+	[IMX6QDL_CLK_USDHC1_SEL]	= MUX(usdhc_sel, 0x01c, 16, 1),
+	[IMX6QDL_CLK_USDHC2_SEL]	= MUX(usdhc_sel, 0x01c, 17, 1),
+	[IMX6QDL_CLK_USDHC3_SEL]	= MUX(usdhc_sel, 0x01c, 18, 1),
+	[IMX6QDL_CLK_USDHC4_SEL]	= MUX(usdhc_sel, 0x01c, 19, 1),
+
+	[IMX6QDL_CLK_USDHC1_PODF]	= DIV(usdhc1_podf, 0x024, 11, 3),
+	[IMX6QDL_CLK_USDHC2_PODF]	= DIV(usdhc2_podf, 0x024, 16, 3),
+	[IMX6QDL_CLK_USDHC3_PODF]	= DIV(usdhc3_podf, 0x024, 19, 3),
+	[IMX6QDL_CLK_USDHC4_PODF]	= DIV(usdhc4_podf, 0x024, 22, 3),
+
+	[IMX6QDL_CLK_USDHC1]		= SIMPLE(usdhc1),
+	[IMX6QDL_CLK_USDHC2]		= SIMPLE(usdhc2),
+	[IMX6QDL_CLK_USDHC3]		= SIMPLE(usdhc3),
+	[IMX6QDL_CLK_USDHC4]		= SIMPLE(usdhc4),
+};
+
 static const struct imx6_clk_gate imx6q_gates[] = {
 	[IMX6QDL_CLK_USDHC1]		= GATE(0x080, GENMASK(3, 2)),
 	[IMX6QDL_CLK_USDHC2]		= GATE(0x080, GENMASK(5, 4)),
@@ -19,6 +88,7 @@  static const struct imx6_clk_gate imx6q_gates[] = {
 
 static const struct imx6_clk_desc imx6q_clk_desc = {
 	.gates = imx6q_gates,
+	.tree = imx6q_tree,
 };
 
 static const struct udevice_id clk_imx6q_ids[] = {