[PATCH 03/23] clk: initial common clk support

Sascha Hauer s.hauer at pengutronix.de
Mon Sep 24 07:04:32 EDT 2012


This adds barebox common clk support loosely based on the Kernel common
clk support. differences are:

- barebox does not need prepare/unprepare
- no parent rate propagation for set_rate
- struct clk is not really encapsulated from the drivers

Along with the clk support we have support for some basic clk building
blocks:

- clk-fixed
- clk-fixed-factor
- clk-mux
- clk-divider

clk-fixed and clk-fixed-factor are completely generic, clk-mux and clk-divider
are currently the way i.MX muxes/dividers are implemented.

Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 drivers/clk/Kconfig            |    3 +
 drivers/clk/Makefile           |    2 +-
 drivers/clk/clk-divider.c      |   98 ++++++++++++++++++
 drivers/clk/clk-fixed-factor.c |   63 +++++++++++
 drivers/clk/clk-fixed.c        |   55 ++++++++++
 drivers/clk/clk-mux.c          |   77 ++++++++++++++
 drivers/clk/clk.c              |  224 ++++++++++++++++++++++++++++++++++++++++
 include/linux/clk.h            |   42 ++++++++
 8 files changed, 563 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clk/clk-divider.c
 create mode 100644 drivers/clk/clk-fixed-factor.c
 create mode 100644 drivers/clk/clk-fixed.c
 create mode 100644 drivers/clk/clk-mux.c
 create mode 100644 drivers/clk/clk.c

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 4168c88..66c1c46 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -2,3 +2,6 @@
 config CLKDEV_LOOKUP
 	bool
 	select HAVE_CLK
+
+config COMMON_CLK
+	bool
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 07613fa..39a75a4 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -1,2 +1,2 @@
-
+obj-$(CONFIG_COMMON_CLK)	+= clk.o clk-fixed.o clk-divider.o clk-fixed-factor.o clk-mux.o
 obj-$(CONFIG_CLKDEV_LOOKUP)	+= clkdev.o
diff --git a/drivers/clk/clk-divider.c b/drivers/clk/clk-divider.c
new file mode 100644
index 0000000..58a7ea5
--- /dev/null
+++ b/drivers/clk/clk-divider.c
@@ -0,0 +1,98 @@
+/*
+ * clk-divider.c - generic barebox clock support. Based on Linux clk support
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <io.h>
+#include <malloc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_divider {
+	struct clk clk;
+	u8 shift;
+	u8 width;
+	void __iomem *reg;
+	const char *parent;
+};
+
+static int clk_divider_set_rate(struct clk *clk, unsigned long rate,
+		unsigned long parent_rate)
+{
+	struct clk_divider *div = container_of(clk, struct clk_divider, clk);
+	unsigned int val, divval;
+
+	if (rate > parent_rate)
+		rate = parent_rate;
+	if (!rate)
+		rate = 1;
+
+	divval = DIV_ROUND_UP(parent_rate, rate);
+
+	if (divval > (1 << div->width))
+		divval = 1 << (div->width);
+
+	divval--;
+
+	val = readl(div->reg);
+	val &= ~(((1 << div->width) - 1) << div->shift);
+	val |= divval << div->shift;
+	writel(val, div->reg);
+
+	return 0;
+}
+
+static unsigned long clk_divider_recalc_rate(struct clk *clk,
+		unsigned long parent_rate)
+{
+	struct clk_divider *div = container_of(clk, struct clk_divider, clk);
+	unsigned int val;
+
+	val = readl(div->reg) >> div->shift;
+	val &= (1 << div->width) - 1;
+
+	val++;
+
+	return parent_rate / val;
+}
+
+struct clk_ops clk_divider_ops = {
+	.set_rate = clk_divider_set_rate,
+	.recalc_rate = clk_divider_recalc_rate,
+};
+
+struct clk *clk_divider(const char *name, const char *parent,
+		void __iomem *reg, u8 shift, u8 width)
+{
+	struct clk_divider *div = xzalloc(sizeof(*div));
+	int ret;
+
+	div->shift = shift;
+	div->reg = reg;
+	div->width = width;
+	div->parent = parent;
+	div->clk.ops = &clk_divider_ops;
+	div->clk.name = name;
+	div->clk.parent_names = &div->parent;
+	div->clk.num_parents = 1;
+
+	ret = clk_register(&div->clk);
+	if (ret) {
+		free(div);
+		return ERR_PTR(ret);
+	}
+
+	return &div->clk;
+}
diff --git a/drivers/clk/clk-fixed-factor.c b/drivers/clk/clk-fixed-factor.c
new file mode 100644
index 0000000..52e7c16
--- /dev/null
+++ b/drivers/clk/clk-fixed-factor.c
@@ -0,0 +1,63 @@
+/*
+ * clk-fixed-factor.c - generic barebox clock support. Based on Linux clk support
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <io.h>
+#include <malloc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_fixed_factor {
+	struct clk clk;
+	int mult;
+	int div;
+	const char *parent;
+};
+
+static unsigned long clk_fixed_factor_recalc_rate(struct clk *clk,
+		unsigned long parent_rate)
+{
+	struct clk_fixed_factor *f = container_of(clk, struct clk_fixed_factor, clk);
+
+	return (parent_rate / f->div) * f->mult;
+}
+
+struct clk_ops clk_fixed_factor_ops = {
+	.recalc_rate = clk_fixed_factor_recalc_rate,
+};
+
+struct clk *clk_fixed_factor(const char *name,
+		const char *parent, unsigned int mult, unsigned int div)
+{
+	struct clk_fixed_factor *f = xzalloc(sizeof(*f));
+	int ret;
+
+	f->mult = mult;
+	f->div = div;
+	f->parent = parent;
+	f->clk.ops = &clk_fixed_factor_ops;
+	f->clk.name = name;
+	f->clk.parent_names = &f->parent;
+	f->clk.num_parents = 1;
+
+	ret = clk_register(&f->clk);
+	if (ret) {
+		free(f);
+		return ERR_PTR(ret);
+	}
+
+	return &f->clk;
+}
diff --git a/drivers/clk/clk-fixed.c b/drivers/clk/clk-fixed.c
new file mode 100644
index 0000000..fa89cb2
--- /dev/null
+++ b/drivers/clk/clk-fixed.c
@@ -0,0 +1,55 @@
+/*
+ * clk-fixed.c - generic barebox clock support. Based on Linux clk support
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <malloc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_fixed {
+	struct clk clk;
+	unsigned long rate;
+};
+
+static unsigned long clk_fixed_recalc_rate(struct clk *clk,
+		unsigned long parent_rate)
+{
+	struct clk_fixed *fix = container_of(clk, struct clk_fixed, clk);
+
+	return fix->rate;
+}
+
+struct clk_ops clk_fixed_ops = {
+	.recalc_rate = clk_fixed_recalc_rate,
+};
+
+struct clk *clk_fixed(const char *name, int rate)
+{
+	struct clk_fixed *fix = xzalloc(sizeof *fix);
+	int ret;
+
+	fix->rate = rate;
+	fix->clk.ops = &clk_fixed_ops;
+	fix->clk.name = name;
+
+	ret = clk_register(&fix->clk);
+	if (ret) {
+		free(fix);
+		return ERR_PTR(ret);
+	}
+
+	return &fix->clk;
+}
diff --git a/drivers/clk/clk-mux.c b/drivers/clk/clk-mux.c
new file mode 100644
index 0000000..1794380
--- /dev/null
+++ b/drivers/clk/clk-mux.c
@@ -0,0 +1,77 @@
+/*
+ * clk-mux.c - generic barebox clock support. Based on Linux clk support
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <io.h>
+#include <malloc.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+struct clk_mux {
+	struct clk clk;
+	void __iomem *reg;
+	int shift;
+	int width;
+};
+
+static int clk_mux_get_parent(struct clk *clk)
+{
+	struct clk_mux *m = container_of(clk, struct clk_mux, clk);
+	int idx = readl(m->reg) >> m->shift & ((1 << m->width) - 1);
+
+	return idx;
+}
+
+static int clk_mux_set_parent(struct clk *clk, u8 idx)
+{
+	struct clk_mux *m = container_of(clk, struct clk_mux, clk);
+	u32 val;
+
+	val = readl(m->reg);
+	val &= ~(((1 << m->width) - 1) << m->shift);
+	val |= idx << m->shift;
+	writel(val, m->reg);
+
+	return 0;
+}
+
+struct clk_ops clk_mux_ops = {
+	.get_parent = clk_mux_get_parent,
+	.set_parent = clk_mux_set_parent,
+};
+
+struct clk *clk_mux(const char *name, void __iomem *reg,
+		u8 shift, u8 width, const char **parents, int num_parents)
+{
+	struct clk_mux *m = xzalloc(sizeof(*m));
+	int ret;
+
+	m->reg = reg;
+	m->shift = shift;
+	m->width = width;
+	m->clk.ops = &clk_mux_ops;
+	m->clk.name = name;
+	m->clk.parent_names = parents;
+	m->clk.num_parents = num_parents;
+
+	ret = clk_register(&m->clk);
+	if (ret) {
+		free(m);
+		return ERR_PTR(ret);
+	}
+
+	return &m->clk;
+}
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
new file mode 100644
index 0000000..bf61e5d
--- /dev/null
+++ b/drivers/clk/clk.c
@@ -0,0 +1,224 @@
+/*
+ * clk.c - generic barebox clock support. Based on Linux clk support
+ *
+ * Copyright (c) 2012 Sascha Hauer <s.hauer at pengutronix.de>, Pengutronix
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <common.h>
+#include <errno.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+static LIST_HEAD(clks);
+
+static int clk_parent_enable(struct clk *clk)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (!IS_ERR_OR_NULL(parent))
+		return clk_enable(parent);
+
+	return 0;
+}
+
+static void clk_parent_disable(struct clk *clk)
+{
+	struct clk *parent = clk_get_parent(clk);
+
+	if (!IS_ERR_OR_NULL(parent))
+		clk_disable(parent);
+}
+
+int clk_enable(struct clk *clk)
+{
+	int ret;
+
+	if (!clk->enable_count) {
+		ret = clk_parent_enable(clk);
+		if (ret)
+			return ret;
+
+		if (clk->ops->enable) {
+			ret = clk->ops->enable(clk);
+			if (ret) {
+				clk_parent_disable(clk);
+				return ret;
+			}
+		}
+	}
+
+	clk->enable_count++;
+
+	return 0;
+}
+
+void clk_disable(struct clk *clk)
+{
+	if (!clk->enable_count)
+		return;
+
+	clk->enable_count--;
+
+	if (!clk->enable_count) {
+		if (clk->ops->disable)
+			clk->ops->disable(clk);
+
+		clk_parent_disable(clk);
+	}
+}
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+	struct clk *parent;
+	unsigned long parent_rate = 0;
+
+	parent = clk_get_parent(clk);
+	if (!IS_ERR_OR_NULL(parent))
+		parent_rate = clk_get_rate(parent);
+
+	if (clk->ops->recalc_rate)
+		return clk->ops->recalc_rate(clk, parent_rate);
+
+	return parent_rate;
+}
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+	return clk_get_rate(clk);
+}
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+	struct clk *parent;
+	unsigned long parent_rate = 0;
+
+	parent = clk_get_parent(clk);
+	if (parent)
+		parent_rate = clk_get_rate(parent);
+
+	if (clk->ops->set_rate)
+		return clk->ops->set_rate(clk, rate, parent_rate);
+
+	return -ENOSYS;
+}
+
+struct clk *clk_lookup(const char *name)
+{
+	struct clk *c;
+
+	if (!name)
+		return ERR_PTR(-ENODEV);
+
+	list_for_each_entry(c, &clks, list) {
+		if (!strcmp(c->name, name))
+			return c;
+	}
+
+	return ERR_PTR(-ENODEV);
+}
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+	int i;
+
+	if (!clk->num_parents)
+		return -EINVAL;
+	if (!clk->ops->set_parent)
+		return -EINVAL;
+
+	for (i = 0; i < clk->num_parents; i++) {
+		if (IS_ERR_OR_NULL(clk->parents[i]))
+			clk->parents[i] = clk_lookup(clk->parent_names[i]);
+
+		if (!IS_ERR_OR_NULL(clk->parents[i]))
+			if (clk->parents[i] == parent)
+				break;
+	}
+
+	if (i == clk->num_parents)
+		return -EINVAL;
+
+	return clk->ops->set_parent(clk, i);
+}
+
+struct clk *clk_get_parent(struct clk *clk)
+{
+	int idx;
+
+	if (!clk->num_parents)
+		return ERR_PTR(-ENODEV);
+
+	if (clk->num_parents != 1) {
+		if (!clk->ops->get_parent)
+			return ERR_PTR(-EINVAL);
+
+		idx = clk->ops->get_parent(clk);
+
+		if (idx >= clk->num_parents)
+			return ERR_PTR(-ENODEV);
+	} else {
+		idx = 0;
+	}
+
+	if (IS_ERR_OR_NULL(clk->parents[idx]))
+		clk->parents[idx] = clk_lookup(clk->parent_names[idx]);
+
+	return clk->parents[idx];
+}
+
+int clk_register(struct clk *clk)
+{
+	clk->parents = xzalloc(sizeof(struct clk *) * clk->num_parents);
+
+	list_add_tail(&clk->list, &clks);
+
+	return 0;
+}
+
+static void dump_one(struct clk *clk, int verbose, int indent)
+{
+	struct clk *c;
+
+	printf("%*s%s (rate %ld, %sabled)\n", indent * 4, "", clk->name, clk_get_rate(clk),
+			clk->enable_count ? "en" : "dis");
+	if (verbose) {
+
+		if (clk->num_parents > 1) {
+			int i;
+			printf("%*s`---- possible parents: ", indent * 4, "");
+			for (i = 0; i < clk->num_parents; i++)
+				printf("%s ", clk->parent_names[i]);
+			printf("\n");
+		}
+	}
+
+	list_for_each_entry(c, &clks, list) {
+		struct clk *parent = clk_get_parent(c);
+
+		if (parent == clk) {
+			dump_one(c, verbose, indent + 1);
+		}
+	}
+}
+
+void clk_dump(int verbose)
+{
+	struct clk *c;
+
+	list_for_each_entry(c, &clks, list) {
+		struct clk *parent = clk_get_parent(c);
+
+		if (IS_ERR_OR_NULL(parent))
+			dump_one(c, verbose, 0);
+	}
+}
diff --git a/include/linux/clk.h b/include/linux/clk.h
index 1478c97..09e5656 100644
--- a/include/linux/clk.h
+++ b/include/linux/clk.h
@@ -155,4 +155,46 @@ struct clk *clk_get_sys(const char *dev_id, const char *con_id);
 int clk_add_alias(const char *alias, const char *alias_dev_name, char *id,
 			struct device_d *dev);
 
+#ifdef CONFIG_COMMON_CLK
+struct clk_ops {
+	int		(*enable)(struct clk *clk);
+	void		(*disable)(struct clk *clk);
+	int		(*is_enabled)(struct clk *clk);
+	unsigned long	(*recalc_rate)(struct clk *clk,
+					unsigned long parent_rate);
+	long		(*round_rate)(struct clk *clk, unsigned long,
+					unsigned long *);
+	int		(*set_parent)(struct clk *clk, u8 index);
+	int		(*get_parent)(struct clk *clk);
+	int		(*set_rate)(struct clk *clk, unsigned long,
+				    unsigned long);
+};
+
+struct clk {
+	const struct clk_ops *ops;
+	int enable_count;
+	struct list_head list;
+	const char *name;
+	const char **parent_names;
+	int num_parents;
+
+	struct clk **parents;
+};
+
+struct clk *clk_fixed(const char *name, int rate);
+struct clk *clk_divider(const char *name, const char *parent,
+		void __iomem *reg, u8 shift, u8 width);
+struct clk *clk_fixed_factor(const char *name,
+		const char *parent, unsigned int mult, unsigned int div);
+struct clk *clk_mux(const char *name, void __iomem *reg,
+		u8 shift, u8 width, const char **parents, int num_parents);
+
+int clk_register(struct clk *clk);
+
+struct clk *clk_lookup(const char *name);
+
+void clk_dump(int verbose);
+
+#endif
+
 #endif
-- 
1.7.10.4




More information about the barebox mailing list