[RFC] i.MX clock support

Sascha Hauer s.hauer at pengutronix.de
Mon Dec 13 05:25:38 EST 2010


Hi,

I played around with Jeremys common struct clk patches and I think they
offer a great potential for cleaning up the i.MX51 (and i.MX in general)
clock support.

Currently on i.MX we have clocks implementing varying sets of the clk
functions and most of the time the functions are reimplemented for
each clock. The i.MX51 clock support shows that this becomes
unmaintainable. The following patch allows for a different approach.
Instead of making each clock a full featured clock we split the clocks
into their basic building blocks. Currently we have:

* multiplexer   Choose from a set of parent clocks (clk_[get|set]_parent)
* divider       clock divider (clk_[get|set|round]_rate)
* gates         clk_[en|dis]able
* groups        Group together clocks which should be enabled at once.

Of course these are the building blocks on other architectures aswell,
so we may move parts of the patch to a more generic place.

I made a experimental implementation for i.MX27 and i.MX51, find the
patches here:

git://git.pengutronix.de/git/imx/linux-2.6.git clk-common-imx

The i.MX27 patches are boot tested, the i.MX51 patches are compile tested
only and probably contain bugs preventing the tree from booting on an
i.MX51.

I am not willing to accept patches for adding i.MX50 support in the mess
we currently have. These patches offer a way to cleanup the clock support
and the i.MX50 may be a good test bed for an implementation without
old cruft to worry about. That said the following patch is not set in
stone, it's a request for comments and I'm of course open to other
suggestions, but it's clear that we have to do something.

Sascha

Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 arch/arm/plat-mxc/clock-common.c       |  406 ++++++++++++++++++++++++++++++++
 arch/arm/plat-mxc/include/mach/clock.h |  234 ++++++++++++++++++-
 2 files changed, 637 insertions(+), 3 deletions(-)

diff --git a/arch/arm/plat-mxc/clock-common.c b/arch/arm/plat-mxc/clock-common.c
index fbe1873..6f83c17 100644
--- a/arch/arm/plat-mxc/clock-common.c
+++ b/arch/arm/plat-mxc/clock-common.c
@@ -166,6 +166,412 @@ struct clk_ops clk_mxc_ops = {
 	.set_parent	= clk_mxc_set_parent,
 };
 
+static int clk_parent_enable(struct clk *clk)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (IS_ERR(parent))
+		return -ENOSYS;
+
+	return clk_enable(parent);
+}
+
+static void clk_parent_disable(struct clk *clk)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (IS_ERR(parent))
+		return;
+
+	clk_disable(parent);
+}
+
+static unsigned long clk_parent_get_rate(struct clk *clk)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (IS_ERR(parent))
+		return 0;
+
+	return clk_get_rate(parent);
+}
+
+static long clk_parent_round_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (IS_ERR(parent))
+		return -ENOSYS;
+
+	return clk_round_rate(parent, rate);
+}
+
+static int clk_parent_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (IS_ERR(parent))
+		return -ENOSYS;
+
+	return clk_set_rate(parent, rate);
+}
+
+/* clk gate support */
+
+#define to_clk_gate(clk) (container_of(clk, struct clk_gate, clk))
+
+static int clk_gate_enable(struct clk *clk)
+{
+	struct clk_gate *gate = to_clk_gate(clk);
+	u32 val, mask;
+
+	if (gate->flags & CLK_GATE_TWO_BITS)
+		mask = 3 << (gate->shift * 2);
+	else
+		mask = 1 << (gate->shift);
+
+	val = readl(gate->reg);
+	val |= mask;
+	writel(val, gate->reg);
+
+	return 0;
+}
+
+static void clk_gate_disable(struct clk *clk)
+{
+	struct clk_gate *gate = to_clk_gate(clk);
+	u32 val, mask;
+
+	if (gate->flags & CLK_GATE_TWO_BITS)
+		mask = 3 << (gate->shift * 2);
+	else
+		mask = 1 << (gate->shift);
+
+	val = readl(gate->reg);
+	val &= ~(mask);
+	writel(val, gate->reg);
+}
+
+static struct clk *clk_gate_get_parent(struct clk *clk)
+{
+	struct clk_gate *gate = to_clk_gate(clk);
+
+	return gate->parent;
+}
+
+struct clk_ops clk_gate_ops = {
+	.enable = clk_gate_enable,
+	.disable = clk_gate_disable,
+	.get_rate = clk_parent_get_rate,
+	.round_rate = clk_parent_round_rate,
+	.set_rate = clk_parent_set_rate,
+	.get_parent = clk_gate_get_parent,
+};
+EXPORT_SYMBOL_GPL(clk_gate_ops);
+
+#define to_clk_divider(clk) (container_of(clk, struct clk_divider, clk))
+
+static unsigned long clk_divider_get_rate(struct clk *clk)
+{
+	struct clk_divider *divider = to_clk_divider(clk);
+
+	unsigned long rate = clk_get_rate(divider->parent);
+	unsigned int div = 1;
+
+	if (divider->reg) {
+		div = readl(divider->reg) >> divider->shift;
+		div &= (1 << divider->width) - 1;
+		div++;
+	}
+
+	return rate / div / divider->div * divider->mult;
+}
+
+static long clk_divider_round_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_divider *divider = to_clk_divider(clk);
+	unsigned long parent_rate = clk_get_rate(divider->parent);
+	unsigned int max_div, div;
+
+	if (rate > parent_rate)
+		return parent_rate;
+
+	max_div = 1 << divider->width;
+
+	div = parent_rate / rate;
+	div = max(div, max_div);
+
+	return parent_rate / div / divider->div * divider->mult;
+}
+
+static int clk_divider_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_divider *divider = to_clk_divider(clk);
+	unsigned long parent_rate = clk_get_rate(divider->parent);
+	unsigned int max_div, div;
+	u32 val;
+
+	parent_rate /= divider->div;
+	parent_rate *= divider->mult;
+
+	if (rate > parent_rate)
+		rate = parent_rate;
+
+	max_div = 1 << divider->width;
+
+	div = parent_rate / rate;
+
+	div = max(div, max_div);
+	div--;
+
+	val = readl(divider->reg);
+	val &= ~(((1 << divider->width) - 1) << divider->shift);
+	val |= div << divider->shift;
+	writel(val, divider->reg);
+
+	return 0;
+}
+
+static struct clk *clk_divider_get_parent(struct clk *clk)
+{
+	struct clk_divider *divider = to_clk_divider(clk);
+
+	return divider->parent;
+}
+
+struct clk_ops clk_divider_ops = {
+	.enable = clk_parent_enable,
+	.disable = clk_parent_disable,
+	.get_rate = clk_divider_get_rate,
+	.round_rate = clk_divider_round_rate,
+	.set_rate = clk_divider_set_rate,
+	.get_parent = clk_divider_get_parent,
+};
+EXPORT_SYMBOL_GPL(clk_divider_ops);
+
+#define to_clk_multiplexer(clk) (container_of(clk, struct clk_multiplexer, clk))
+
+static struct clk *clk_multiplexer_get_parent(struct clk *clk)
+{
+	struct clk_multiplexer *multiplexer = to_clk_multiplexer(clk);
+	u32 val;
+
+	val = readl(multiplexer->reg) >> multiplexer->shift;
+	val &= (1 << multiplexer->width) - 1;
+
+	if (val >= multiplexer->num_clks)
+		return ERR_PTR(-EINVAL);
+
+	return multiplexer->clks[val];
+}
+
+static int clk_multiplexer_set_parent(struct clk *clk, struct clk *parent)
+{
+	struct clk_multiplexer *multiplexer = to_clk_multiplexer(clk);
+	u32 val;
+	int i;
+
+	for (i = 0; i < multiplexer->num_clks; i++)
+		if (multiplexer->clks[i] == parent)
+			break;
+
+	if (i == multiplexer->num_clks)
+		return -EINVAL;
+
+	val = readl(multiplexer->reg);
+	val &= ((1 << multiplexer->width) - 1) << multiplexer->shift;
+	val |= i << multiplexer->shift;
+	writel(val, multiplexer->reg);
+
+	return 0;
+}
+
+struct clk_ops clk_multiplexer_ops = {
+	.enable = clk_parent_enable,
+	.disable = clk_parent_disable,
+	.get_rate = clk_parent_get_rate,
+	.round_rate = clk_parent_round_rate,
+	.set_rate = clk_parent_set_rate,
+	.get_parent = clk_multiplexer_get_parent,
+	.set_parent = clk_multiplexer_set_parent,
+};
+EXPORT_SYMBOL_GPL(clk_multiplexer_ops);
+
+#define to_clk_group(clk) (container_of(clk, struct clk_group, clk))
+
+static int clk_group_enable(struct clk *clk)
+{
+	struct clk_group *group = to_clk_group(clk);
+	int i, ret;
+
+	for (i = 0; i < group->num_clks; i++) {
+		ret = clk_enable(group->clks[i]);
+			if (ret)
+				goto out;
+	}
+
+	return 0;
+out:
+	while (i-- > 0)
+		clk_disable(group->clks[i]);
+	return ret;
+}
+
+static void clk_group_disable(struct clk *clk)
+{
+	struct clk_group *group = to_clk_group(clk);
+	int i;
+
+	for (i = 0; i < group->num_clks; i++)
+		clk_disable(group->clks[i]);
+}
+
+static unsigned long clk_group_get_rate(struct clk *clk)
+{
+	struct clk_group *group = to_clk_group(clk);
+
+	return clk_get_rate(group->clks[0]);
+}
+
+static long clk_group_round_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_group *group = to_clk_group(clk);
+
+	return clk_round_rate(group->clks[0], rate);
+}
+
+static int clk_group_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_group *group = to_clk_group(clk);
+
+	return clk_set_rate(group->clks[0], rate);
+}
+
+struct clk_ops clk_group_ops = {
+	.enable = clk_group_enable,
+	.disable = clk_group_disable,
+	.get_rate = clk_group_get_rate,
+	.round_rate = clk_group_round_rate,
+	.set_rate = clk_group_set_rate,
+};
+
+#define to_clk_divider_pre_post(clk) (container_of(clk, struct clk_divider_pre_post, clk))
+
+static unsigned long clk_divider_pre_post_get_rate(struct clk *clk)
+{
+	struct clk_divider_pre_post *divider = to_clk_divider_pre_post(clk);
+	struct clk *parent = divider->parent;
+	unsigned long parent_rate = clk_get_rate(parent);
+	unsigned int div1, div2;
+
+	div1 = ((readl(divider->reg1) >> divider->shift1) & divider->width1) + 1;
+	div2 = ((readl(divider->reg2) >> divider->shift2) & divider->width2) + 1;
+
+	return parent_rate / (div1 + 1) / (div2 + 1);
+}
+
+/* calculate best pre and post dividers to get the required divider */
+static void calc_two_dividers(u32 div, unsigned int *div1, unsigned int *div2,
+	unsigned int max_pre, unsigned int max_post)
+{
+	if (div >= max_pre * max_post) {
+		*div1 = max_pre;
+		*div2 = max_post;
+	} else if (div >= max_pre) {
+		u32 min_pre, temp_pre, old_err, err;
+		min_pre = DIV_ROUND_UP(div, max_post);
+		old_err = max_pre;
+		for (temp_pre = max_pre; temp_pre >= min_pre; temp_pre--) {
+			err = div % temp_pre;
+			if (err == 0) {
+				*div1 = temp_pre;
+				break;
+			}
+			err = temp_pre - err;
+			if (err < old_err) {
+				old_err = err;
+				*div1 = temp_pre;
+			}
+		}
+		*div2 = DIV_ROUND_UP(div, *div1);
+	} else {
+		*div1 = div;
+		*div2 = 1;
+	}
+}
+
+static long clk_divider_pre_post_round_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_divider_pre_post *divider = to_clk_divider_pre_post(clk);
+	struct clk *parent = divider->parent;
+	unsigned long parent_rate = clk_get_rate(parent);
+	unsigned int div, div1, div2;
+
+	div = parent_rate / rate;
+
+	calc_two_dividers(div, &div1, &div2, 1 << divider->width1,
+			1 << divider->width2);
+
+	return parent_rate / (div1 + 1) / (div2 + 1);
+}
+
+static int clk_divider_pre_post_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk_divider_pre_post *divider = to_clk_divider_pre_post(clk);
+	struct clk *parent = divider->parent;
+	unsigned long parent_rate = clk_get_rate(parent);
+	unsigned int div, div1, div2;
+	u32 val;
+
+	div = parent_rate / rate;
+
+	calc_two_dividers(div, &div1, &div2, 1 << divider->width1,
+			1 << divider->width2);
+
+	val = readl(divider->reg1);
+	val &= ~(((1 << divider->width1) - 1 ) << divider->shift1);
+	val &= ~(((1 << divider->width2) - 1 ) << divider->shift2);
+	val |= div1 << divider->shift1;
+	val |= div2 << divider->shift2;
+	writel(val, divider->reg1);
+
+	return 0;
+}
+
+struct clk_ops clk_divider_pre_post_ops = {
+	.enable = clk_parent_enable,
+	.disable = clk_parent_disable,
+	.get_rate = clk_divider_pre_post_get_rate,
+	.round_rate = clk_divider_pre_post_round_rate,
+	.set_rate = clk_divider_pre_post_set_rate,
+};
+
+#define to_clk_parent(clk) (container_of(clk, struct clk_parent, clk))
+
+static struct clk *clk_parent_get_parent(struct clk *clk)
+{
+	struct clk_parent *parent = to_clk_parent(clk);
+
+	return parent->parent;
+}
+
+static int clk_parent_set_parent(struct clk *clk, struct clk *parentclk)
+{
+	struct clk_parent *parent = to_clk_parent(clk);
+
+	return clk_set_parent(parent->parent, parentclk);
+}
+
+struct clk_ops clk_parent_ops = {
+	.enable = clk_parent_enable,
+	.disable = clk_parent_disable,
+	.get_rate = clk_parent_get_rate,
+	.round_rate = clk_parent_round_rate,
+	.set_rate = clk_parent_set_rate,
+	.get_parent = clk_parent_get_parent,
+	.set_parent = clk_parent_set_parent,
+};
+
 /*
  * Get the resulting clock rate from a PLL register value and the input
  * frequency. PLLs with this register layout can at least be found on
diff --git a/arch/arm/plat-mxc/include/mach/clock.h b/arch/arm/plat-mxc/include/mach/clock.h
index 9331dbb..b432727 100644
--- a/arch/arm/plat-mxc/include/mach/clock.h
+++ b/arch/arm/plat-mxc/include/mach/clock.h
@@ -62,12 +62,240 @@ struct clk_mxc {
 	int (*set_parent) (struct clk_mxc *, struct clk *);
 };
 
-int clk_register(struct clk *clk);
-void clk_unregister(struct clk *clk);
-
 unsigned long mxc_decode_pll(unsigned int pll, u32 f_ref);
 
 extern struct clk_ops clk_mxc_ops;
 
+/**
+ * clock gate
+ *
+ * @clk		clock source
+ * @reg		register containing the gate
+ * @shift	shift to the gate
+ * @parent	parent clock
+ * @flags	flags
+ *
+ * This clock implements clk_enable/clk_disable. The rate functions are passed
+ * through to the parent.
+ * When CLK_GATE_TWO_BITS is given each gate is two bits wide. Used on i.MX51.
+ */
+struct clk_gate {
+	struct clk	clk;
+	void __iomem	*reg;
+	unsigned	shift;
+	struct clk	*parent;
+	unsigned long	flags;
+};
+
+extern struct clk_ops clk_gate_ops;
+
+#define DEFINE_CLK_GATE_FLAGS(name, _parent, _reg, _shift, _flags) \
+	struct clk_gate name = { \
+		.clk = INIT_CLK(name.clk, clk_gate_ops), \
+		.parent = (_parent), \
+		.reg = (_reg), \
+		.shift = (_shift), \
+		.flags = (_flags), \
+	}
+
+#define CLK_GATE_TWO_BITS	(1 << 0)
+
+#define DEFINE_CLK_GATE(name, _parent, _reg, _shift) \
+	DEFINE_CLK_GATE_FLAGS(name, _parent, _reg, _shift, 0)
+#define DEFINE_CLK_GATE_2(name, _parent, _reg, _shift) \
+	DEFINE_CLK_GATE_FLAGS(name, _parent, _reg, _shift, CLK_GATE_TWO_BITS)
+
+/**
+ * clock divider
+ *
+ * @clk		clock source
+ * @reg		register containing the divider
+ * @shift	shift to the divider
+ * @width	width of the divider
+ * @parent	parent clock
+ *
+ * This clock implements get_rate/set_rate/round_rate. clk_enable/clk_disable
+ * are passed through to the parent.
+ *
+ * The divider is calculated as div = reg_val + 1
+ */
+struct clk_divider {
+	struct clk	clk;
+	void __iomem	*reg;
+	unsigned char	shift;
+	unsigned char	width;
+	unsigned int	mult;
+	unsigned int	div;
+	struct clk	*parent;
+};
+
+extern struct clk_ops clk_divider_ops;
+
+#define DEFINE_CLK_DIVIDER(name, _parent, _reg, _shift, _width) \
+	struct clk_divider name = { \
+		.clk = INIT_CLK(name.clk, clk_divider_ops), \
+		.parent = (_parent), \
+		.reg = (_reg), \
+		.shift = (_shift), \
+		.width = (_width), \
+		.mult = 1, \
+		.div = 1, \
+	}
+
+/**
+ * fixed clock divider
+ *
+ * @clk		clock source
+ * @mult	fixed multiplier value
+ * @div		fixed divider value
+ * @parent	parent clock
+ *
+ * This clock implements a fixed divider with an additional multiplier to
+ * specify fractional values. clk_enable/clk_disable are passed through
+ * to the parent. Note that the divider is applied before the multiplier
+ * to prevent overflows. This may result in a less accurat result.
+ */
+struct clk_divider_fixed {
+	struct clk	clk;
+	unsigned int	mult;
+	unsigned int	div;
+	struct clk	*parent;
+};
+
+#define DEFINE_CLK_DIVIDER_FIXED(name, _parent, _mult, _div) \
+	struct clk_divider name = { \
+		.clk = INIT_CLK(name.clk, clk_divider_ops), \
+		.parent = (_parent), \
+		.mult = (_mult), \
+		.div = (_div), \
+	}
+
+/**
+ * Two cascaded dividers
+ *
+ * @clk		clock source
+ * @reg1	register containing the first divider
+ * @reg2	register containing the second divider
+ * @shift1	shift to the first divider
+ * @shift2	shift to the second divider
+ * @width1	width of the first divider
+ * @width2	width of the second divider
+ * @parent	parent clock
+ *
+ * This clock implements get_rate/set_rate/round_rate. clk_enable/clk_disable
+ * are passed through to the parent.
+ *
+ * The resulting divider is calculated as div = (reg_val1 + 1) * (reg_val2 + 1)
+ */
+struct clk_divider_pre_post {
+	struct clk	clk;
+	void __iomem	*reg1, *reg2;
+	unsigned char	shift1, shift2;
+	unsigned char	width1, width2;
+	struct clk	*parent;
+};
+
+extern struct clk_ops clk_divider_pre_post_ops;
+
+#define DEFINE_CLK_DIVIDER_PRE_POST(name, _parent, _reg1, _shift1, _width1, _reg2, _shift2, _width2) \
+	struct clk_divider_pre_post name = { \
+		.clk = INIT_CLK(name.clk, clk_divider_pre_post_ops), \
+		.parent = (_parent), \
+		.reg1 = (_reg2), \
+		.shift1 = (_shift2), \
+		.width1 = (_width2), \
+		.reg2 = (_reg2), \
+		.shift2 = (_shift2), \
+		.width2 = (_width2), \
+	}
+
+/**
+ * clock multiplexer
+ *
+ * @clk		clock source
+ * @reg		the register this multiplexer can be configured with
+ * @shift	the shift to the start bit of this multiplexer
+ * @width	the width in bits of this multiplexer
+ * @num_clks	number of parent clocks
+ * @clks	array of possible parents for this multiplexer. Can contain
+ *		holes with NULL in it for invalid register settings
+ *
+ * This clock implements get_parent/set_parent. All other clock
+ * operations are passed through to the parent.
+ */
+struct clk_multiplexer {
+	struct clk	clk;
+	void __iomem	*reg;
+	unsigned char	shift;
+	unsigned char	width;
+	unsigned char	num_clks;
+	struct clk	**clks;
+};
+
+extern struct clk_ops clk_multiplexer_ops;
+
+#define DEFINE_CLK_MULTIPLEXER(name, _reg, _shift, _width, _clks) \
+	struct clk_multiplexer name = { \
+		.clk = INIT_CLK(name.clk, clk_multiplexer_ops), \
+		.reg = (_reg), \
+		.shift = (_shift), \
+		.width = (_width), \
+		.clks = (_clks), \
+		.num_clks = ARRAY_SIZE(_clks), \
+	}
+
+/**
+ * clock group
+ *
+ * @clk		clock source
+ * @num_clks	number of parent clocks to enable
+ * @clks	array of parents to enable/disable
+ *
+ * This clock is a groups of clocks useful for specifying clocks for
+ * drivers which consist of multiple clocks. it enables/disables
+ * all clocks in @clks, clk_get_rate/clk_set_rate are passed through
+ * to the first member of @clks.
+ */
+struct clk_group {
+	struct clk	clk;
+	unsigned char	num_clks;
+	struct clk	**clks;
+};
+
+extern struct clk_ops clk_group_ops;
+
+#define DEFINE_CLK_GROUP(name, _clks) \
+	struct clk_group name = { \
+		.clk = INIT_CLK(name.clk, clk_group_ops), \
+		.clks = (_clks), \
+		.num_clks = ARRAY_SIZE(_clks), \
+	}
+
+#define DEFINE_CLK_FIXED(name, rate) \
+	struct clk_fixed name = INIT_CLK_FIXED(name, rate)
+
+/**
+ * clock parent
+ *
+ * @clk		clock source
+ * @parent	the parent clock
+ *
+ * This clocks passes all operations to the parent clock. It is
+ * useful as a placeholder for static initialization of a clock
+ * tree.
+ */
+struct clk_parent {
+	struct clk	clk;
+	struct clk	*parent;
+};
+
+extern struct clk_ops clk_parent_ops;
+
+#define DEFINE_CLK_PARENT(name, _parent) \
+	struct clk_parent name = { \
+		.clk = INIT_CLK(name.clk, clk_parent_ops), \
+		.parent = (_parent), \
+	}
+
 #endif /* __ASSEMBLY__ */
 #endif /* __ASM_ARCH_MXC_CLOCK_H__ */
-- 
1.7.2.3


-- 
Pengutronix e.K.                           |                             |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



More information about the linux-arm-kernel mailing list