linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/4] ARM: meson: add clock/reset controller
@ 2014-11-19 10:32 Carlo Caione
  2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patchset introduces preliminary support for the clock/reset controller
found on Amlogic MesonX SoCs and enables it for the Meson6.

The reset controller is in charge of shutting off the clock to disable modules
and submodules within the chip. Unfortunately the documentation we have does
not explicit which clock is gated but only which peripheral is reset so the
clocks introduced are not gated but the peripherals are reset using the
reset controller.

DTS patches will follow.

Carlo Caione (4):
  ARM: meson: add basic infrastructure for clocks
  ARM: meson: add reset controller
  ARM: meson: enable reset controller in kconfig
  ARM: meson: add documentation for reset/clock controller

 .../bindings/clock/amlogic,meson6-clkc.txt         |  45 ++++
 arch/arm/mach-meson/Kconfig                        |   2 +
 drivers/clk/Makefile                               |   1 +
 drivers/clk/meson/Makefile                         |   8 +
 drivers/clk/meson/clk-pll.c                        | 298 +++++++++++++++++++++
 drivers/clk/meson/clkc.c                           | 151 +++++++++++
 drivers/clk/meson/clkc.h                           | 174 ++++++++++++
 drivers/clk/meson/meson6-clkc.c                    | 178 ++++++++++++
 drivers/clk/meson/rstc.c                           | 152 +++++++++++
 include/dt-bindings/clock/meson6-clkc.h            |  18 ++
 10 files changed, 1027 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/amlogic,meson6-clkc.txt
 create mode 100644 drivers/clk/meson/Makefile
 create mode 100644 drivers/clk/meson/clk-pll.c
 create mode 100644 drivers/clk/meson/clkc.c
 create mode 100644 drivers/clk/meson/clkc.h
 create mode 100644 drivers/clk/meson/meson6-clkc.c
 create mode 100644 drivers/clk/meson/rstc.c
 create mode 100644 include/dt-bindings/clock/meson6-clkc.h

-- 
1.9.1

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] ARM: meson: add basic infrastructure for clocks
  2014-11-19 10:32 [PATCH 0/4] ARM: meson: add clock/reset controller Carlo Caione
@ 2014-11-19 10:32 ` Carlo Caione
  2014-11-19 22:30   ` Mike Turquette
  2014-11-19 22:42   ` Mike Turquette
  2014-11-19 10:32 ` [PATCH 2/4] ARM: meson: add reset controller Carlo Caione
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

This patchset adds the infrastructure for registering and managing the
core clocks found on Amlogic MesonX SoCs and also adds the support for
the basic Meson6 clocks.

Signed-off-by: Carlo Caione <carlo@caione.org>
---
 drivers/clk/Makefile                    |   1 +
 drivers/clk/meson/Makefile              |   7 +
 drivers/clk/meson/clk-pll.c             | 298 ++++++++++++++++++++++++++++++++
 drivers/clk/meson/clkc.c                | 151 ++++++++++++++++
 drivers/clk/meson/clkc.h                | 159 +++++++++++++++++
 drivers/clk/meson/meson6-clkc.c         | 160 +++++++++++++++++
 include/dt-bindings/clock/meson6-clkc.h |  18 ++
 7 files changed, 794 insertions(+)
 create mode 100644 drivers/clk/meson/Makefile
 create mode 100644 drivers/clk/meson/clk-pll.c
 create mode 100644 drivers/clk/meson/clkc.c
 create mode 100644 drivers/clk/meson/clkc.h
 create mode 100644 drivers/clk/meson/meson6-clkc.c
 create mode 100644 include/dt-bindings/clock/meson6-clkc.h

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d5fba5b..e93d134 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -51,6 +51,7 @@ ifeq ($(CONFIG_COMMON_CLK), y)
 obj-$(CONFIG_ARCH_MMP)			+= mmp/
 endif
 obj-$(CONFIG_PLAT_ORION)		+= mvebu/
+obj-$(CONFIG_ARCH_MESON)		+= meson/
 obj-$(CONFIG_ARCH_MXS)			+= mxs/
 obj-$(CONFIG_COMMON_CLK_PXA)		+= pxa/
 obj-$(CONFIG_COMMON_CLK_QCOM)		+= qcom/
diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
new file mode 100644
index 0000000..8f102e4
--- /dev/null
+++ b/drivers/clk/meson/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Meson specific clk
+#
+
+obj-y += clkc.o
+obj-y += meson6-clkc.o
+obj-y += clk-pll.o
diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c
new file mode 100644
index 0000000..5b6d064
--- /dev/null
+++ b/drivers/clk/meson/clk-pll.c
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "clkc.h"
+
+/*
+ * A generic PLL in a Meson6/Meson8 SOC is composed as follows with a variable
+ * number of output divisors.
+ *
+ *                       PLL+DIVs
+ *     ----------------------------------------------
+ *     |                                            |
+ *     |                                 |---[DIV1]--->> out1
+ * in ---[ 1/N ]---[ *M ]---[ *OD_FB ]---|    ....  |
+ *     |         ^               ^     ^ |---[DIV#]--->> out#
+ *     |         |               |     |            |
+ *     |---------------------------------------------
+ *               |               |     |
+ *              FREF             |    VCO
+ *                          optional
+ *
+ * For each divisor DIV# the output frequency is calculated as:
+ *
+ * out# = in# * M * OD_FB / N / DIV#
+ */
+
+#define PMASK(width)				((1U << (width)) - 1)
+#define SETPMASK(width, shift)			(PMASK(width) << (shift))
+#define CLRPMASK(width, shift)			(~(SETPMASK(width, shift)))
+#define PARM_GET(width, shift, reg)		(((reg) & SETPMASK(width, shift)) >> (shift))
+#define PARM_SET(width, shift, reg, val)	(((reg) & CLRPMASK(width, shift)) | (val << (shift)))
+
+#define MESON_FREF_MIN_MHZ			5
+#define MESON_FREF_MAX_MHZ			30
+
+struct meson_clk_pll {
+	struct clk_hw	hw;
+	void __iomem	*base;
+	struct pll_conf	*conf;
+	spinlock_t	*lock;
+};
+#define to_meson_clk_pll(_hw) container_of(_hw, struct meson_clk_pll, hw)
+
+static void meson_clk_pll_get_parms(struct meson_clk_pll *pll,
+				    unsigned long *rate, unsigned long p_rate,
+				    u16 *best_n, u16 *best_m, u16 *best_od_fb)
+{
+	unsigned long rate_mhz, p_rate_mhz;
+	unsigned long pll_vco_min_mhz, pll_vco_max_mhz;
+	unsigned long cur_rate_mhz, best_rate_mhz;
+	u16 m, m_min, m_max, m_mask;
+	u16 n, n_min, n_max, n_mask, _n_min, _n_max;
+	u16 od_fb, od_fb_max;
+
+	rate_mhz = *rate / 1000000;
+	p_rate_mhz = p_rate / 1000000;
+	pll_vco_min_mhz = pll->conf->vco_min_mhz;
+	pll_vco_max_mhz = pll->conf->vco_max_mhz;
+	best_rate_mhz = ULONG_MAX;
+	*best_n = 0;
+	*best_m = 0;
+	*best_od_fb = 1;
+
+	m_mask = PMASK(pll->conf->m.width);
+	n_mask = PMASK(pll->conf->n.width);
+
+	/* FREF = P_RATE / N */
+	n_min = max_t(u16, DIV_ROUND_UP(p_rate_mhz, MESON_FREF_MAX_MHZ), 1);
+	n_max = min_t(u16, p_rate_mhz / MESON_FREF_MIN_MHZ, n_mask);
+
+	od_fb_max = 1 << PMASK(pll->conf->od_fb.width);
+
+	for (od_fb = 1; od_fb <= od_fb_max; od_fb <<= 1) {
+
+		/* VCO = P_RATE * M * OD_FB / N */
+		m_min = max_t(u16, DIV_ROUND_UP(pll_vco_min_mhz,
+			      p_rate_mhz * od_fb) * n_min, 1);
+		m_max = min_t(u16, (pll_vco_max_mhz * n_max) /
+			      (p_rate_mhz * od_fb), m_mask);
+
+
+		for (m = m_min; m <= m_max; m++) {
+			_n_min = max_t(u16, n_min,
+				       DIV_ROUND_UP(p_rate_mhz * m * od_fb,
+				       pll_vco_max_mhz));
+			_n_max = min_t(u16, n_max,
+				       p_rate_mhz * m * od_fb / pll_vco_min_mhz);
+
+			for (n = _n_min; n <= _n_max; n++) {
+				cur_rate_mhz = p_rate_mhz * m * od_fb / n;
+
+				if (abs(cur_rate_mhz - rate_mhz) <
+				   abs(best_rate_mhz - rate_mhz)) {
+					best_rate_mhz = cur_rate_mhz;
+					*best_n = n;
+					*best_m = m;
+					*best_od_fb = od_fb;
+					if (best_rate_mhz == rate_mhz)
+						goto done;
+				}
+			}
+		}
+	}
+
+done:
+	*best_od_fb >>= 1;
+	*rate = best_rate_mhz * 1000000;
+	return;
+
+}
+
+static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+	struct parm *p_n, *p_m, *p_od_fb;
+	unsigned long parent_rate_mhz = parent_rate / 1000000;
+	unsigned long rate_mhz;
+	u16 n, m, od_fb = 1;
+	u32 reg_n, reg_m, reg_od_fb;
+
+	p_n = &pll->conf->n;
+	p_m = &pll->conf->m;
+	p_od_fb = &pll->conf->od_fb;
+
+	reg_n = readl(pll->base + p_n->reg_off);
+	n = PARM_GET(p_n->width, p_n->shift, reg_n);
+
+	reg_m = readl(pll->base + p_m->reg_off);
+	m = PARM_GET(p_m->width, p_m->shift, reg_m);
+
+	if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) {
+		reg_od_fb = readl(pll->base + p_od_fb->reg_off);
+		od_fb = PARM_GET(p_od_fb->width, p_od_fb->shift, reg_od_fb);
+		od_fb = 1 << od_fb;
+	}
+
+	rate_mhz = parent_rate_mhz * m * od_fb / n;
+
+	return rate_mhz * 1000000;
+}
+
+static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
+				      unsigned long *parent_rate)
+{
+	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+	u16 m, n, od_fb;
+
+	meson_clk_pll_get_parms(pll, &rate, *parent_rate, &n, &m, &od_fb);
+	if (m == 0 || n == 0)
+		return -EINVAL;
+
+	return rate;
+}
+
+static int meson_clk_pll_set_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long parent_rate)
+{
+	struct meson_clk_pll *pll = to_meson_clk_pll(hw);
+	struct parm *p_n, *p_m, *p_od_fb;
+	unsigned long flags = 0;
+	u32 reg_n, reg_m, reg_od_fb;
+	u16 m, n, od_fb;
+
+	spin_lock_irqsave(pll->lock, flags);
+
+	if (parent_rate == 0 || rate == 0)
+		return -EINVAL;
+
+	meson_clk_pll_get_parms(pll, &rate, parent_rate, &n, &m, &od_fb);
+	if (m == 0 || n == 0)
+		return -EINVAL;
+
+	p_n = &pll->conf->n;
+	p_m = &pll->conf->m;
+	p_od_fb = &pll->conf->od_fb;
+
+	reg_n = readl(pll->base + p_n->reg_off);
+	reg_n = PARM_SET(p_n->width, p_n->shift, reg_n, n);
+	writel(reg_n, pll->base + p_n->reg_off);
+
+	reg_m = readl(pll->base + p_m->reg_off);
+	reg_m = PARM_SET(p_m->width, p_m->shift, reg_m, m);
+	writel(reg_m, pll->base + p_m->reg_off);
+
+	if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) {
+		reg_od_fb = readl(pll->base + p_od_fb->reg_off);
+		reg_od_fb = PARM_SET(p_od_fb->width, p_od_fb->shift, reg_od_fb,
+				     od_fb);
+		writel(reg_od_fb, pll->base + p_od_fb->reg_off);
+	}
+
+	spin_unlock_irqrestore(pll->lock, flags);
+
+	return 0;
+}
+
+static const struct clk_ops meson_clk_pll_ops = {
+	.recalc_rate	= meson_clk_pll_recalc_rate,
+	.round_rate	= meson_clk_pll_round_rate,
+	.set_rate	= meson_clk_pll_set_rate,
+};
+
+
+static struct clk * __init meson_clk_pll_setup(struct pll_div_conf *pll_div_conf,
+						void __iomem *reg_base,
+						spinlock_t *lock)
+{
+	struct clk *clk;
+	struct meson_clk_pll *pll;
+	struct clk_init_data init;
+	const char *parent_name = pll_div_conf->clk_parent;
+
+	pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+	if (!pll)
+		return ERR_PTR(-ENOMEM);
+
+	pll->base = reg_base + pll_div_conf->reg_off;
+	pll->lock = lock;
+	pll->conf = pll_div_conf->pll_conf;
+
+	init.name = pll_div_conf->pll_name;
+	init.ops = &meson_clk_pll_ops;
+	init.flags = pll_div_conf->flags;
+	init.parent_names = &parent_name;
+	init.num_parents = 1;
+
+	pll->hw.init = &init;
+
+	clk = clk_register(NULL, &pll->hw);
+	if (IS_ERR(clk))
+		kfree(pll);
+
+	return clk;
+}
+
+void __init meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf,
+				       void __iomem *reg_base,
+				       spinlock_t *lock)
+{
+	struct clk *pclk, *clk;
+	struct div_conf *div;
+	void __iomem *div_base;
+	int d;
+
+	pclk = meson_clk_pll_setup(pll_div_conf, reg_base, lock);
+	if (IS_ERR(pclk)) {
+		pr_warn("%s: Unable to create %s clock\n", __func__,
+			pll_div_conf->pll_name);
+		return;
+	}
+
+	for (d = 0; d < MESON_DIVS_MAX; d++) {
+		div = &pll_div_conf->div[d];
+		div_base = reg_base + pll_div_conf->reg_off + div->od.reg_off;
+
+		if (div->clk_id == CLKID_UNUSED)
+			continue;
+
+		clk = clk_register_divider(NULL, div->clk_name,
+					   pll_div_conf->pll_name,
+					   div->flags, div_base,
+					   div->od.shift, div->od.width,
+					   CLK_DIVIDER_POWER_OF_TWO, lock);
+
+		if (IS_ERR(clk)) {
+			pr_warn("%s: Unable to create %s clock\n", __func__,
+				div->clk_name);
+			continue;
+		}
+
+		meson_clk_add_lookup(clk, div->clk_id);
+	}
+}
+
diff --git a/drivers/clk/meson/clkc.c b/drivers/clk/meson/clkc.c
new file mode 100644
index 0000000..02b409e
--- /dev/null
+++ b/drivers/clk/meson/clkc.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/mfd/syscon.h>
+#include <linux/slab.h>
+
+#include "clkc.h"
+
+static DEFINE_SPINLOCK(clk_lock);
+
+static struct clk **clks;
+static struct clk_onecell_data clk_data;
+
+void __init meson_clk_init(struct device_node *np, unsigned long nr_clks)
+{
+	clks = kcalloc(nr_clks, sizeof(struct clk *), GFP_KERNEL);
+	if (!clks)
+		pr_err("%s: could not allocate clock lookup table\n", __func__);
+
+	clk_data.clks = clks;
+	clk_data.clk_num = nr_clks;
+	of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
+}
+
+void meson_clk_add_lookup(struct clk *clk, unsigned int id)
+{
+	if (clks && id)
+		clks[id] = clk;
+}
+
+void __init meson_clk_register_pll_divs(struct pll_div_conf *pll_divs,
+					unsigned int nr_pll_divs,
+					void __iomem *pll_base)
+{
+	unsigned int i;
+
+	for (i = 0; i < nr_pll_divs; i++)
+		meson_clk_register_pll_div(&pll_divs[i], pll_base, &clk_lock);
+}
+
+static struct clk __init *meson_clk_register_mux_div(struct clk_conf *clk_conf,
+						     void __iomem *clk_base)
+{
+	struct clk *clk;
+	struct clk_mux *mux = NULL;
+	struct clk_divider *div = NULL;
+	const struct clk_ops *mux_ops = NULL, *div_ops = NULL;
+	struct mux_div_conf *mux_div_conf = &clk_conf->conf.mux_div_conf;
+
+	if (clk_conf->num_parents > 1) {
+		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+		if (!mux)
+			return ERR_PTR(-ENOMEM);
+
+		mux->reg = clk_base + mux_div_conf->mux_parm.reg_off;
+		mux->shift = mux_div_conf->mux_parm.shift;
+		mux->mask = BIT(mux_div_conf->mux_parm.width) - 1;
+		mux->flags = mux_div_conf->mux_flags;
+		mux->lock = &clk_lock;
+		mux->table = mux_div_conf->mux_table;
+		mux_ops = (mux_div_conf->mux_flags & CLK_MUX_READ_ONLY) ?
+			  &clk_mux_ro_ops : &clk_mux_ops;
+	}
+
+	if (mux_div_conf->div_parm.width != MESON_PARM_NOT_APPLICABLE) {
+		div = kzalloc(sizeof(*div), GFP_KERNEL);
+		if (!div)
+			return ERR_PTR(-ENOMEM);
+
+		div->flags = mux_div_conf->div_flags;
+		div->reg = clk_base + mux_div_conf->div_parm.reg_off;
+		div->shift = mux_div_conf->div_parm.shift;
+		div->width = mux_div_conf->div_parm.width;
+		div->lock = &clk_lock;
+		div->table = mux_div_conf->div_table;
+		div_ops = (mux_div_conf->div_flags & CLK_DIVIDER_READ_ONLY) ?
+			  &clk_divider_ro_ops : &clk_divider_ops;
+	}
+
+	clk = clk_register_composite(NULL, clk_conf->clk_name,
+				    clk_conf->clks_parent,
+				    clk_conf->num_parents,
+				    mux ? &mux->hw : NULL, mux_ops,
+				    div ? &div->hw : NULL, div_ops,
+				    NULL, NULL, clk_conf->flags);
+
+	return clk;
+}
+
+void __init meson_clk_register_clks(struct clk_conf *clk_confs,
+				     unsigned int nr_confs,
+				     void __iomem *clk_base)
+{
+	unsigned int i;
+	struct clk *clk = NULL;
+
+	for (i = 0; i < nr_confs; i++) {
+		struct clk_conf *clk_conf = &clk_confs[i];
+
+		switch (clk_conf->clk_type) {
+		case clk_fixed_rate:
+			clk = clk_register_fixed_rate(NULL,
+					clk_conf->clk_name,
+					(clk_conf->num_parents) ?
+						clk_conf->clks_parent[0] : NULL,
+					clk_conf->flags,
+					clk_conf->conf.fixed_rate);
+			break;
+		case clk_fixed_factor:
+			clk = clk_register_fixed_factor(NULL,
+					clk_conf->clk_name,
+					clk_conf->clks_parent[0],
+					clk_conf->flags,
+					clk_conf->conf.fixed_fact_conf.mult,
+					clk_conf->conf.fixed_fact_conf.div);
+			break;
+		case clk_mux_div:
+			clk = meson_clk_register_mux_div(clk_conf, clk_base);
+			break;
+		}
+
+		if (!clk) {
+			pr_err("%s: unknown clock type %d\n", __func__,
+			       clk_conf->clk_type);
+			continue;
+		}
+
+		if (IS_ERR(clk)) {
+			pr_warn("%s: Unable to create %s clock\n", __func__,
+				clk_conf->clk_name);
+			continue;
+		}
+
+		meson_clk_add_lookup(clk, clk_conf->clk_id);
+	}
+}
diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h
new file mode 100644
index 0000000..6c0f611
--- /dev/null
+++ b/drivers/clk/meson/clkc.h
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __CLKC_H
+#define __CLKC_H
+
+#include <linux/clk.h>
+
+#define MESON_DIVS_MAX			2
+#define MESON_DIVS_WIDTH		2
+#define MESON_PARM_NOT_APPLICABLE	0
+
+#define CLKID_UNUSED			0
+
+struct parm {
+	u16	reg_off;
+	u8	shift;
+	u8	width;
+};
+#define PARM(_r, _s, _w) { .reg_off = (_r), .shift = (_s), .width = (_w), }
+
+struct pll_conf {
+	unsigned long	vco_min_mhz;
+	unsigned long	vco_max_mhz;
+	struct parm	m;
+	struct parm	n;
+	struct parm	od_fb;
+};
+#define PLL_CONF3(_vmin, _vmax, _pm, _pn, _po)				\
+	{								\
+		.vco_min_mhz	= (_vmin),				\
+		.vco_max_mhz	= (_vmax),				\
+		.m		= _pm,					\
+		.n		= _pn,					\
+		.od_fb		= _po,					\
+	}
+
+#define PLL_CONF2(_vmin, _vmax, _pm, _pn)				\
+	{								\
+		.vco_min_mhz	= (_vmin),				\
+		.vco_max_mhz	= (_vmax),				\
+		.m		= _pm,					\
+		.n		= _pn,					\
+	}
+
+struct div_conf {
+	unsigned int	clk_id;
+	const char	*clk_name;
+	struct parm	od;
+	unsigned long	flags;
+};
+#define DIV_CONF(_name, _pod, _id, _f)					\
+	{								\
+		.clk_name	= (_name),				\
+		.od		= PARM(0x00, (_pod), MESON_DIVS_WIDTH),	\
+		.clk_id		= (_id),				\
+		.flags		= (_f),					\
+	}
+
+struct pll_div_conf {
+	u16		reg_off;
+	const char	*pll_name;
+	const char	*clk_parent;
+	unsigned long	flags;
+	struct pll_conf	*pll_conf;
+	struct div_conf	div[MESON_DIVS_MAX];
+};
+
+struct fixed_fact_conf {
+	unsigned int	div;
+	unsigned int	mult;
+};
+
+struct mux_div_conf {
+	u32			*mux_table;
+	u8			mux_flags;
+	struct clk_div_table	*div_table;
+
+	u8			div_flags;
+	struct parm		mux_parm;
+	struct parm		div_parm;
+};
+
+#define PNAME(x) static const char *x[] __initconst
+
+enum clk_type { clk_fixed_factor, clk_fixed_rate, clk_mux_div };
+
+struct clk_conf {
+	enum clk_type			clk_type;
+	unsigned int			clk_id;
+	const char			*clk_name;
+	const char			**clks_parent;
+	int				num_parents;
+	unsigned long			flags;
+	union {
+		struct fixed_fact_conf	fixed_fact_conf;
+		struct mux_div_conf	mux_div_conf;
+		unsigned long		fixed_rate;
+	} conf;
+};
+
+#define FIXED_ROOT(_id, _cn, _f, _r)					\
+	{								\
+		.clk_type		= clk_fixed_rate,		\
+		.clk_id			= (_id),			\
+		.clk_name		= (_cn),			\
+		.flags			= CLK_IS_ROOT | (_f),		\
+		.conf.fixed_rate	= (_r),				\
+	}
+
+#define FIXED_DIV(_id, _cn, _cp, _f, _div)				\
+	{								\
+		.clk_type			= clk_fixed_factor,	\
+		.clk_id				= (_id),		\
+		.clk_name			= (_cn),		\
+		.clks_parent			= (_cp),		\
+		.num_parents			= ARRAY_SIZE(_cp),	\
+		.flags				= (_f),			\
+		.conf.fixed_fact_conf.div	= (_div),		\
+		.conf.fixed_fact_conf.mult	= 1,			\
+	}
+
+#define MUX_DIV(_id, _cn, _cp, _f, _mt, _mp, _dp)			\
+	{								\
+		.clk_type			= clk_mux_div,		\
+		.clk_id				= (_id),		\
+		.clk_name			= (_cn),		\
+		.clks_parent			= (_cp),		\
+		.num_parents			= ARRAY_SIZE(_cp),	\
+		.flags				= (_f),			\
+		.conf.mux_div_conf.mux_table	= _mt,			\
+		.conf.mux_div_conf.mux_parm	= _mp,			\
+		.conf.mux_div_conf.div_parm	= _dp,			\
+	}
+
+
+void meson_clk_init(struct device_node *np, unsigned long nr_clks);
+void meson_clk_add_lookup(struct clk *clk, unsigned int id);
+void meson_clk_register_pll_divs(struct pll_div_conf *pll_divs,
+				 unsigned int nr_pll_divs,
+				 void __iomem *reg_base);
+void meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf,
+				void __iomem *reg_base, spinlock_t *lock);
+void meson_clk_register_clks(struct clk_conf *clk_confs,
+			     unsigned int nr_confs,
+			     void __iomem *clk_base);
diff --git a/drivers/clk/meson/meson6-clkc.c b/drivers/clk/meson/meson6-clkc.c
new file mode 100644
index 0000000..9f809e1
--- /dev/null
+++ b/drivers/clk/meson/meson6-clkc.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <dt-bindings/clock/meson6-clkc.h>
+
+#include "clkc.h"
+
+#define MESON6_REG_MPLL_FIXED	0x0280
+#define MESON6_REG_DPLL_SYS	0x0260
+#define MESON6_REG_DPLL_DDR	0x01a0
+#define MESON6_REG_DPLL_VID2	0x011c
+#define MESON6_REG_HPLL		0x0270
+#define MESON6_REG_HHI_MPEG	0x0174
+
+#define MESON6_XTAL		"xtal"
+
+static struct pll_conf dpll_conf = PLL_CONF2(750, 1512, PARM(0x00, 0, 9),
+							PARM(0x00, 9, 5));
+
+static struct pll_conf mpll_conf = PLL_CONF3(1000, 2000, PARM(0x00, 0, 9),
+							 PARM(0x00, 9, 5),
+							 PARM(0x0c, 4, 1));
+
+static struct pll_conf hpll_conf = PLL_CONF3(750, 1512, PARM(0x00, 0, 10),
+							PARM(0x00, 10, 5),
+							PARM(0x00, 20, 2));
+
+static struct pll_div_conf meson_pll_divs[] __initdata = {
+	{
+		.clk_parent	= MESON6_XTAL,
+		.reg_off	= MESON6_REG_DPLL_SYS,
+		.pll_name	= "vco_sys",
+		.pll_conf	= &dpll_conf,
+		.flags		= CLK_SET_RATE_PARENT,
+		.div		= {
+					DIV_CONF("dpll_sys", 16,
+						 CLKID_DPLL_SYS,
+						 CLK_SET_RATE_PARENT),
+		}
+	},
+	{
+		.clk_parent	= MESON6_XTAL,
+		.reg_off	= MESON6_REG_DPLL_DDR,
+		.pll_name	= "vco_ddr",
+		.pll_conf	= &dpll_conf,
+		.flags		= CLK_SET_RATE_PARENT,
+		.div		= {
+					DIV_CONF("dpll_ddr", 16,
+						 CLKID_DPLL_DDR,
+						 CLK_SET_RATE_PARENT),
+		}
+	},
+	{
+		.clk_parent	= MESON6_XTAL,
+		.reg_off	= MESON6_REG_DPLL_VID2,
+		.pll_name	= "vco_vid2",
+		.pll_conf	= &dpll_conf,
+		.flags		= CLK_SET_RATE_PARENT,
+		.div		= {
+					DIV_CONF("dpll_vid2", 16,
+						 CLKID_DPLL_VID2,
+						 CLK_SET_RATE_PARENT),
+		}
+	},
+	{
+		.clk_parent	= MESON6_XTAL,
+		.reg_off	= MESON6_REG_MPLL_FIXED,
+		.pll_name	= "vco_fixed",
+		.pll_conf	= &mpll_conf,
+		.flags		= CLK_SET_RATE_PARENT,
+		.div		= {
+					DIV_CONF("mpll_fixed", 16,
+						 CLKID_MPLL_FIXED,
+						 CLK_SET_RATE_PARENT),
+		}
+	},
+	{
+		.clk_parent	= MESON6_XTAL,
+		.reg_off	= MESON6_REG_HPLL,
+		.pll_name	= "vco_hpll",
+		.pll_conf	= &hpll_conf,
+		.flags		= CLK_SET_RATE_PARENT,
+		.div		= {
+					DIV_CONF("hpll_lvds", 16,
+						 CLKID_HPLL_LVDS,
+						 CLK_SET_RATE_PARENT),
+					DIV_CONF("hpll_hdmi", 18,
+						 CLKID_HPLL_HDMI,
+						 CLK_SET_RATE_PARENT),
+		}
+	},
+};
+
+PNAME(p_fclk_div) = { "mpll_fixed" };
+PNAME(p_clk81) = { "fclk_div2", "fclk_div3", "fclk_div5" };
+
+static u32 mux_table_clk81[] __initdata = { 5, 6, 7 };
+
+static struct clk_conf meson_clk_confs[] __initdata = {
+	FIXED_ROOT(CLKID_XTAL, MESON6_XTAL, 0, 0),
+	FIXED_ROOT(CLKID_RTC_XTAL, "rtc_xtal", 0, 32000),
+	FIXED_DIV(CLKID_FCLK_DIV2, "fclk_div2", p_fclk_div, 0, 2),
+	FIXED_DIV(CLKID_FCLK_DIV3, "fclk_div3", p_fclk_div, 0, 3),
+	FIXED_DIV(CLKID_FCLK_DIV5, "fclk_div5", p_fclk_div, 0, 5),
+	MUX_DIV(CLKID_CLK81, "clk81", p_clk81, 0, mux_table_clk81,
+		PARM(MESON6_REG_HHI_MPEG, 12, 3),
+		PARM(MESON6_REG_HHI_MPEG, 0, 7)),
+};
+
+static void __init meson_clkc_init(struct device_node *np)
+{
+	void __iomem *clk_base;
+	u32 xtal_rate;
+
+	/* XTAL */
+	clk_base = of_iomap(np, 0);
+	if (!clk_base) {
+		pr_err("%s: Unable to map xtal base\n", __func__);
+		return;
+	}
+
+	xtal_rate = readl(clk_base) >> 4 & 0x3f;
+	xtal_rate *= 1000000;
+	meson_clk_confs[0].conf.fixed_rate = xtal_rate;
+	iounmap(clk_base);
+
+	/*  Generic clocks and PLLs */
+	clk_base = of_iomap(np, 1);
+	if (!clk_base) {
+		pr_err("%s: Unable to map clk base\n", __func__);
+		return;
+	}
+
+	meson_clk_init(np, CLK_NR_CLKS);
+
+	meson_clk_register_clks(meson_clk_confs, ARRAY_SIZE(meson_clk_confs),
+				clk_base);
+	meson_clk_register_pll_divs(meson_pll_divs, ARRAY_SIZE(meson_pll_divs),
+				    clk_base);
+}
+CLK_OF_DECLARE(meson6_clock, "amlogic,meson6-clkc", meson_clkc_init);
diff --git a/include/dt-bindings/clock/meson6-clkc.h b/include/dt-bindings/clock/meson6-clkc.h
new file mode 100644
index 0000000..06efef3
--- /dev/null
+++ b/include/dt-bindings/clock/meson6-clkc.h
@@ -0,0 +1,18 @@
+/*
+ * Meson6 clock tree IDs
+ */
+
+#define CLKID_XTAL		1
+#define CLKID_RTC_XTAL		2
+#define CLKID_DPLL_SYS		3
+#define CLKID_DPLL_DDR		4
+#define CLKID_DPLL_VID2		5
+#define CLKID_MPLL_FIXED	6
+#define CLKID_HPLL_HDMI		7
+#define CLKID_HPLL_LVDS		8
+#define CLKID_FCLK_DIV2		9
+#define CLKID_FCLK_DIV3		10
+#define CLKID_FCLK_DIV5		11
+#define CLKID_CLK81		12
+
+#define CLK_NR_CLKS		(CLKID_CLK81 + 1)
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/4] ARM: meson: add reset controller
  2014-11-19 10:32 [PATCH 0/4] ARM: meson: add clock/reset controller Carlo Caione
  2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
@ 2014-11-19 10:32 ` Carlo Caione
  2014-11-19 10:32 ` [PATCH 3/4] ARM: meson: enable reset controller in kconfig Carlo Caione
  2014-11-19 10:32 ` [PATCH 4/4] ARM: meson: add documentation for reset/clock controller Carlo Caione
  3 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

Modules and submodules within Meson6 and Meson8 SoCs can be disabled by
shutting off the clock. The control for these clocks comes from several
registers that are mapped contiguously except a special register that
controls peripherals in the AO (Always-On) power domain. The reset
controller manages both the cases according to the reset ID.

Signed-off-by: Carlo Caione <carlo@caione.org>
---
 drivers/clk/meson/Makefile      |   1 +
 drivers/clk/meson/clkc.h        |  15 ++++
 drivers/clk/meson/meson6-clkc.c |  18 +++++
 drivers/clk/meson/rstc.c        | 152 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 186 insertions(+)
 create mode 100644 drivers/clk/meson/rstc.c

diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index 8f102e4..a962d57 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -5,3 +5,4 @@
 obj-y += clkc.o
 obj-y += meson6-clkc.o
 obj-y += clk-pll.o
+obj-$(CONFIG_RESET_CONTROLLER)  += rstc.o
diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h
index 6c0f611..41e92df 100644
--- a/drivers/clk/meson/clkc.h
+++ b/drivers/clk/meson/clkc.h
@@ -157,3 +157,18 @@ void meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf,
 void meson_clk_register_clks(struct clk_conf *clk_confs,
 			     unsigned int nr_confs,
 			     void __iomem *clk_base);
+
+#ifdef CONFIG_RESET_CONTROLLER
+void meson_register_rstc(struct device_node *np, unsigned int num_regs,
+			 void __iomem *ao_base, void __iomem *ot_base,
+			 unsigned int ao_off_id, u8 flags);
+#else
+static inline void meson_register_rstc(struct device_node *np,
+				       unsigned int num_regs,
+				       void __iomem *ao_base,
+				       void __iomem *ot_base,
+				       unsigned int ao_off_id, u8 flags)
+{
+}
+#endif
+#endif /* __CLKC_H */
diff --git a/drivers/clk/meson/meson6-clkc.c b/drivers/clk/meson/meson6-clkc.c
index 9f809e1..177c6fa 100644
--- a/drivers/clk/meson/meson6-clkc.c
+++ b/drivers/clk/meson/meson6-clkc.c
@@ -30,9 +30,16 @@
 #define MESON6_REG_DPLL_VID2	0x011c
 #define MESON6_REG_HPLL		0x0270
 #define MESON6_REG_HHI_MPEG	0x0174
+#define MESON6_REG_RSTC		0x0140
 
 #define MESON6_XTAL		"xtal"
 
+/*
+ * Every reset ID >= 163 is mapped to the AO domain register
+ */
+#define MESON6_RSTC_N_REGS	6
+#define MESON6_AO_OFF		((MESON6_RSTC_N_REGS - 1) * BITS_PER_LONG + 3)
+
 static struct pll_conf dpll_conf = PLL_CONF2(750, 1512, PARM(0x00, 0, 9),
 							PARM(0x00, 9, 5));
 
@@ -129,6 +136,7 @@ static struct clk_conf meson_clk_confs[] __initdata = {
 static void __init meson_clkc_init(struct device_node *np)
 {
 	void __iomem *clk_base;
+	void __iomem *ao_base;
 	u32 xtal_rate;
 
 	/* XTAL */
@@ -156,5 +164,15 @@ static void __init meson_clkc_init(struct device_node *np)
 				clk_base);
 	meson_clk_register_pll_divs(meson_pll_divs, ARRAY_SIZE(meson_pll_divs),
 				    clk_base);
+
+	/* Reset controller */
+	ao_base = of_iomap(np, 2);
+	if (!ao_base) {
+		pr_warn("%s: Unable to map ao domain base for reset controller\n", __func__);
+		return;
+	}
+
+	meson_register_rstc(np, MESON6_RSTC_N_REGS, ao_base,
+			    clk_base + MESON6_REG_RSTC, MESON6_AO_OFF, 0);
 }
 CLK_OF_DECLARE(meson6_clock, "amlogic,meson6-clkc", meson_clkc_init);
diff --git a/drivers/clk/meson/rstc.c b/drivers/clk/meson/rstc.c
new file mode 100644
index 0000000..493f789
--- /dev/null
+++ b/drivers/clk/meson/rstc.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/io.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include "clkc.h"
+
+/*
+ * Modules and submodules within the chip can be reset by disabling the
+ * clock and enabling it again.
+ * The modules in the AO (Always-On) domain are controlled by a different
+ * register mapped in a different memory region accessed by the ao_base.
+ *
+ */
+
+struct meson_rstc {
+	struct reset_controller_dev	rcdev;
+	void __iomem			*ao_base;
+	void __iomem			*ot_base;
+	unsigned int			num_regs;
+	unsigned int			ao_off_id;
+	u8				flags;
+	spinlock_t			lock;
+};
+
+static int meson_rstc_assert(struct reset_controller_dev *rcdev,
+			     unsigned long id)
+{
+	struct meson_rstc *rstc = container_of(rcdev,
+					       struct meson_rstc,
+					       rcdev);
+	int bank = id / BITS_PER_LONG;
+	int offset;
+	void __iomem *rstc_mem;
+	unsigned long flags;
+	u32 reg;
+
+	/*
+	 * The higher IDs are used for the AO domain register
+	 */
+	if (id >= rstc->ao_off_id) {
+		offset = id - rstc->ao_off_id;
+		rstc_mem = rstc->ao_base;
+	} else {
+		offset = id % BITS_PER_LONG;
+		rstc_mem = rstc->ot_base + (bank << 2);
+	}
+
+	spin_lock_irqsave(&rstc->lock, flags);
+
+	reg = readl(rstc_mem);
+	writel(reg & ~BIT(offset), rstc_mem);
+
+	spin_unlock_irqrestore(&rstc->lock, flags);
+
+	return 0;
+}
+
+static int meson_rstc_deassert(struct reset_controller_dev *rcdev,
+			       unsigned long id)
+{
+	struct meson_rstc *rstc = container_of(rcdev,
+					       struct meson_rstc,
+					       rcdev);
+	int bank = id / BITS_PER_LONG;
+	int offset;
+	void __iomem *rstc_mem;
+	unsigned long flags;
+	u32 reg;
+
+	if (id >= rstc->ao_off_id) {
+		offset = id - rstc->ao_off_id;
+		rstc_mem = rstc->ao_base;
+	} else {
+		offset = id % BITS_PER_LONG;
+		rstc_mem = rstc->ot_base + (bank << 2);
+	}
+
+	spin_lock_irqsave(&rstc->lock, flags);
+
+	reg = readl(rstc_mem);
+	writel(reg | BIT(offset), rstc_mem);
+
+	spin_unlock_irqrestore(&rstc->lock, flags);
+
+	return 0;
+
+}
+
+static int meson_rstc_reset(struct reset_controller_dev *rcdev, unsigned long id)
+{
+	int err;
+
+	err = meson_rstc_assert(rcdev, id);
+	if (err)
+		return err;
+
+	return meson_rstc_deassert(rcdev, id);
+}
+
+static struct reset_control_ops meson_rstc_ops = {
+	.assert		= meson_rstc_assert,
+	.deassert	= meson_rstc_deassert,
+	.reset		= meson_rstc_reset,
+};
+
+void __init meson_register_rstc(struct device_node *np, unsigned int num_regs,
+				void __iomem *ao_base, void __iomem *ot_base,
+				unsigned int ao_off_id, u8 flags)
+{
+	struct meson_rstc *rstc;
+	int ret;
+
+	rstc = kzalloc(sizeof(*rstc), GFP_KERNEL);
+	if (!rstc)
+		return;
+
+	spin_lock_init(&rstc->lock);
+
+	rstc->ao_base = ao_base;
+	rstc->ot_base = ot_base;
+	rstc->num_regs = num_regs;
+	rstc->flags = flags;
+
+	rstc->rcdev.owner = THIS_MODULE;
+	rstc->rcdev.nr_resets = num_regs * BITS_PER_LONG;
+	rstc->rcdev.of_node = np;
+	rstc->rcdev.ops = &meson_rstc_ops;
+
+	ret = reset_controller_register(&rstc->rcdev);
+	if (ret) {
+		pr_err("%s: could not register reset controller: %d\n",
+		       __func__, ret);
+		kfree(rstc);
+	}
+}
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/4] ARM: meson: enable reset controller in kconfig
  2014-11-19 10:32 [PATCH 0/4] ARM: meson: add clock/reset controller Carlo Caione
  2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
  2014-11-19 10:32 ` [PATCH 2/4] ARM: meson: add reset controller Carlo Caione
@ 2014-11-19 10:32 ` Carlo Caione
  2014-11-19 10:32 ` [PATCH 4/4] ARM: meson: add documentation for reset/clock controller Carlo Caione
  3 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

Modify the arch Kconfig to enable the reset controller

Signed-off-by: Carlo Caione <carlo@caione.org>
---
 arch/arm/mach-meson/Kconfig | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm/mach-meson/Kconfig b/arch/arm/mach-meson/Kconfig
index 18301dc..af2ad93 100644
--- a/arch/arm/mach-meson/Kconfig
+++ b/arch/arm/mach-meson/Kconfig
@@ -10,6 +10,8 @@ config MACH_MESON6
 	bool "Amlogic Meson6 (8726MX) SoCs support"
 	default ARCH_MESON
 	select MESON6_TIMER
+	select ARCH_HAS_RESET_CONTROLLER
+	select RESET_CONTROLLER
 
 config MACH_MESON8
 	bool "Amlogic Meson8 SoCs support"
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 4/4] ARM: meson: add documentation for reset/clock controller
  2014-11-19 10:32 [PATCH 0/4] ARM: meson: add clock/reset controller Carlo Caione
                   ` (2 preceding siblings ...)
  2014-11-19 10:32 ` [PATCH 3/4] ARM: meson: enable reset controller in kconfig Carlo Caione
@ 2014-11-19 10:32 ` Carlo Caione
  3 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 10:32 UTC (permalink / raw)
  To: linux-arm-kernel

Add documentation for the reset and clock controller

Signed-off-by: Carlo Caione <carlo@caione.org>
---
 .../bindings/clock/amlogic,meson6-clkc.txt         | 45 ++++++++++++++++++++++
 1 file changed, 45 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/amlogic,meson6-clkc.txt

diff --git a/Documentation/devicetree/bindings/clock/amlogic,meson6-clkc.txt b/Documentation/devicetree/bindings/clock/amlogic,meson6-clkc.txt
new file mode 100644
index 0000000..b4a8813
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/amlogic,meson6-clkc.txt
@@ -0,0 +1,45 @@
+* Amlogic Meson6 Clock and Reset Unit
+
+The Amlogic Meson6 clock controller generates and supplies clock to various
+controllers within the SoC and also implements a reset controller for SoC
+peripherals.
+
+Required Properties:
+
+- compatible: should be "amlogic,meson6-clkc"
+- reg: it must be composed by three tuples:
+	0) physical base address of the xtal register and length of memory
+	   mapped region.
+	1) physical base address of the clock controller and length of memory
+	   mapped region.
+	2) physical base address of the AO domain reset register and length
+	   of memory mapped region.
+
+- #clock-cells: should be 1.
+- #reset-cells: should be 1.
+
+Each clock is assigned an identifier and client nodes can use this identifier
+to specify the clock which they consume. All available clocks are defined as
+preprocessor macros in the dt-bindings/clock/meson6-clkc.h header and can be
+used in device tree sources.
+
+Example: Clock controller node:
+
+	clkc: clock-controller at c1104000 {
+		#clock-cells = <1>;
+		#reset-cells = <1>;
+		compatible = "amlogic,meson6-clkc";
+		reg = <0xc1108000 0x4>, <0xc1104000 0x460>, <0xc8100040 0x4>;
+	};
+
+
+Example: UART controller node that consumes the clock generated by the clock
+  controller:
+
+	uart_AO: serial at c81004c0 {
+		compatible = "amlogic,meson-uart";
+		reg = <0xc81004c0 0x14>;
+		interrupts = <0 90 1>;
+		clocks = <&clkc CLKID_CLK81>;
+		status = "disabled";
+	};
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 1/4] ARM: meson: add basic infrastructure for clocks
  2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
@ 2014-11-19 22:30   ` Mike Turquette
  2014-11-19 22:48     ` Carlo Caione
  2014-11-19 22:42   ` Mike Turquette
  1 sibling, 1 reply; 9+ messages in thread
From: Mike Turquette @ 2014-11-19 22:30 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Carlo Caione (2014-11-19 02:32:20)
> This patchset adds the infrastructure for registering and managing the
> core clocks found on Amlogic MesonX SoCs and also adds the support for
> the basic Meson6 clocks.

Minor nitpick: typically the $SUBJECT starts with "arm:" if the patch
primarily deals with code under arch/arm. Since this code lives in
drivers/clk it might be better to use something like:

clk: meson: add basic infrastructure for clocks

<snip>

> +static void meson_clk_pll_get_parms(struct meson_clk_pll *pll,
> +                                   unsigned long *rate, unsigned long p_rate,
> +                                   u16 *best_n, u16 *best_m, u16 *best_od_fb)
> +{
> +       unsigned long rate_mhz, p_rate_mhz;
> +       unsigned long pll_vco_min_mhz, pll_vco_max_mhz;
> +       unsigned long cur_rate_mhz, best_rate_mhz;
> +       u16 m, m_min, m_max, m_mask;
> +       u16 n, n_min, n_max, n_mask, _n_min, _n_max;
> +       u16 od_fb, od_fb_max;
> +
> +       rate_mhz = *rate / 1000000;
> +       p_rate_mhz = p_rate / 1000000;
> +       pll_vco_min_mhz = pll->conf->vco_min_mhz;
> +       pll_vco_max_mhz = pll->conf->vco_max_mhz;
> +       best_rate_mhz = ULONG_MAX;
> +       *best_n = 0;
> +       *best_m = 0;
> +       *best_od_fb = 1;
> +
> +       m_mask = PMASK(pll->conf->m.width);
> +       n_mask = PMASK(pll->conf->n.width);
> +
> +       /* FREF = P_RATE / N */
> +       n_min = max_t(u16, DIV_ROUND_UP(p_rate_mhz, MESON_FREF_MAX_MHZ), 1);
> +       n_max = min_t(u16, p_rate_mhz / MESON_FREF_MIN_MHZ, n_mask);
> +
> +       od_fb_max = 1 << PMASK(pll->conf->od_fb.width);
> +
> +       for (od_fb = 1; od_fb <= od_fb_max; od_fb <<= 1) {
> +
> +               /* VCO = P_RATE * M * OD_FB / N */
> +               m_min = max_t(u16, DIV_ROUND_UP(pll_vco_min_mhz,
> +                             p_rate_mhz * od_fb) * n_min, 1);
> +               m_max = min_t(u16, (pll_vco_max_mhz * n_max) /
> +                             (p_rate_mhz * od_fb), m_mask);
> +
> +
> +               for (m = m_min; m <= m_max; m++) {
> +                       _n_min = max_t(u16, n_min,
> +                                      DIV_ROUND_UP(p_rate_mhz * m * od_fb,
> +                                      pll_vco_max_mhz));
> +                       _n_max = min_t(u16, n_max,
> +                                      p_rate_mhz * m * od_fb / pll_vco_min_mhz);
> +
> +                       for (n = _n_min; n <= _n_max; n++) {
> +                               cur_rate_mhz = p_rate_mhz * m * od_fb / n;
> +
> +                               if (abs(cur_rate_mhz - rate_mhz) <
> +                                  abs(best_rate_mhz - rate_mhz)) {
> +                                       best_rate_mhz = cur_rate_mhz;
> +                                       *best_n = n;
> +                                       *best_m = m;
> +                                       *best_od_fb = od_fb;
> +                                       if (best_rate_mhz == rate_mhz)
> +                                               goto done;
> +                               }
> +                       }
> +               }
> +       }

Nothing strictly wrong with the above, but is is n^3 complexity. If your
tables are large then you might be spending a non-trivial amount time
calculating the pll parameters.

<snip>

> +
> +done:
> +       *best_od_fb >>= 1;
> +       *rate = best_rate_mhz * 1000000;
> +       return;
> +
> +}
> +
> +static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
> +                                               unsigned long parent_rate)
> +{
> +       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
> +       struct parm *p_n, *p_m, *p_od_fb;
> +       unsigned long parent_rate_mhz = parent_rate / 1000000;
> +       unsigned long rate_mhz;
> +       u16 n, m, od_fb = 1;
> +       u32 reg_n, reg_m, reg_od_fb;
> +
> +       p_n = &pll->conf->n;
> +       p_m = &pll->conf->m;
> +       p_od_fb = &pll->conf->od_fb;
> +
> +       reg_n = readl(pll->base + p_n->reg_off);
> +       n = PARM_GET(p_n->width, p_n->shift, reg_n);
> +
> +       reg_m = readl(pll->base + p_m->reg_off);
> +       m = PARM_GET(p_m->width, p_m->shift, reg_m);
> +
> +       if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) {
> +               reg_od_fb = readl(pll->base + p_od_fb->reg_off);
> +               od_fb = PARM_GET(p_od_fb->width, p_od_fb->shift, reg_od_fb);
> +               od_fb = 1 << od_fb;
> +       }
> +
> +       rate_mhz = parent_rate_mhz * m * od_fb / n;
> +
> +       return rate_mhz * 1000000;
> +}
> +
> +static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
> +                                     unsigned long *parent_rate)
> +{
> +       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
> +       u16 m, n, od_fb;
> +
> +       meson_clk_pll_get_parms(pll, &rate, *parent_rate, &n, &m, &od_fb);
> +       if (m == 0 || n == 0)
> +               return -EINVAL;
> +
> +       return rate;
> +}

Can the clock signal input to your pll by multiplexed? Are there
multiple possible parents to a pll? If so you might want to use
.determine_rate over .round_rate.

<snip>

> diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h
> new file mode 100644
> index 0000000..6c0f611
> --- /dev/null
> +++ b/drivers/clk/meson/clkc.h
> @@ -0,0 +1,159 @@
> +/*
> + * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef __CLKC_H
> +#define __CLKC_H

<snip>

> +void meson_clk_init(struct device_node *np, unsigned long nr_clks);
> +void meson_clk_add_lookup(struct clk *clk, unsigned int id);
> +void meson_clk_register_pll_divs(struct pll_div_conf *pll_divs,
> +                                unsigned int nr_pll_divs,
> +                                void __iomem *reg_base);
> +void meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf,
> +                               void __iomem *reg_base, spinlock_t *lock);
> +void meson_clk_register_clks(struct clk_conf *clk_confs,
> +                            unsigned int nr_confs,
> +                            void __iomem *clk_base);

Missing #endif here causes compile failure.

Regards,
Mike

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] ARM: meson: add basic infrastructure for clocks
  2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
  2014-11-19 22:30   ` Mike Turquette
@ 2014-11-19 22:42   ` Mike Turquette
  2014-11-19 22:51     ` Carlo Caione
  1 sibling, 1 reply; 9+ messages in thread
From: Mike Turquette @ 2014-11-19 22:42 UTC (permalink / raw)
  To: linux-arm-kernel

Quoting Carlo Caione (2014-11-19 02:32:20)
> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c
> new file mode 100644
> index 0000000..5b6d064
> --- /dev/null
> +++ b/drivers/clk/meson/clk-pll.c
> @@ -0,0 +1,298 @@
> +/*
> + * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms and conditions of the GNU General Public License,
> + * version 2, as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope 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.
> + *
> + * You should have received a copy of the GNU General Public License along with
> + * this program.  If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clkdev.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>

All of the headers below are unnecessary. Copy/paste? I didn't bother to
check the ones above.

> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/slab.h>
> +#include <linux/string.h>

Regards,
Mike

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] ARM: meson: add basic infrastructure for clocks
  2014-11-19 22:30   ` Mike Turquette
@ 2014-11-19 22:48     ` Carlo Caione
  0 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 22:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 19, 2014 at 11:30 PM, Mike Turquette <mturquette@linaro.org> wrote:
> Quoting Carlo Caione (2014-11-19 02:32:20)
>> This patchset adds the infrastructure for registering and managing the
>> core clocks found on Amlogic MesonX SoCs and also adds the support for
>> the basic Meson6 clocks.
>
> Minor nitpick: typically the $SUBJECT starts with "arm:" if the patch
> primarily deals with code under arch/arm. Since this code lives in
> drivers/clk it might be better to use something like:
>
> clk: meson: add basic infrastructure for clocks

Good to know. Thanks.

>> +static void meson_clk_pll_get_parms(struct meson_clk_pll *pll,
>> +                                   unsigned long *rate, unsigned long p_rate,
>> +                                   u16 *best_n, u16 *best_m, u16 *best_od_fb)
>> +{
>> +       unsigned long rate_mhz, p_rate_mhz;
>> +       unsigned long pll_vco_min_mhz, pll_vco_max_mhz;
>> +       unsigned long cur_rate_mhz, best_rate_mhz;
>> +       u16 m, m_min, m_max, m_mask;
>> +       u16 n, n_min, n_max, n_mask, _n_min, _n_max;
>> +       u16 od_fb, od_fb_max;
>> +
>> +       rate_mhz = *rate / 1000000;
>> +       p_rate_mhz = p_rate / 1000000;
>> +       pll_vco_min_mhz = pll->conf->vco_min_mhz;
>> +       pll_vco_max_mhz = pll->conf->vco_max_mhz;
>> +       best_rate_mhz = ULONG_MAX;
>> +       *best_n = 0;
>> +       *best_m = 0;
>> +       *best_od_fb = 1;
>> +
>> +       m_mask = PMASK(pll->conf->m.width);
>> +       n_mask = PMASK(pll->conf->n.width);
>> +
>> +       /* FREF = P_RATE / N */
>> +       n_min = max_t(u16, DIV_ROUND_UP(p_rate_mhz, MESON_FREF_MAX_MHZ), 1);
>> +       n_max = min_t(u16, p_rate_mhz / MESON_FREF_MIN_MHZ, n_mask);
>> +
>> +       od_fb_max = 1 << PMASK(pll->conf->od_fb.width);
>> +
>> +       for (od_fb = 1; od_fb <= od_fb_max; od_fb <<= 1) {
>> +
>> +               /* VCO = P_RATE * M * OD_FB / N */
>> +               m_min = max_t(u16, DIV_ROUND_UP(pll_vco_min_mhz,
>> +                             p_rate_mhz * od_fb) * n_min, 1);
>> +               m_max = min_t(u16, (pll_vco_max_mhz * n_max) /
>> +                             (p_rate_mhz * od_fb), m_mask);
>> +
>> +
>> +               for (m = m_min; m <= m_max; m++) {
>> +                       _n_min = max_t(u16, n_min,
>> +                                      DIV_ROUND_UP(p_rate_mhz * m * od_fb,
>> +                                      pll_vco_max_mhz));
>> +                       _n_max = min_t(u16, n_max,
>> +                                      p_rate_mhz * m * od_fb / pll_vco_min_mhz);
>> +
>> +                       for (n = _n_min; n <= _n_max; n++) {
>> +                               cur_rate_mhz = p_rate_mhz * m * od_fb / n;
>> +
>> +                               if (abs(cur_rate_mhz - rate_mhz) <
>> +                                  abs(best_rate_mhz - rate_mhz)) {
>> +                                       best_rate_mhz = cur_rate_mhz;
>> +                                       *best_n = n;
>> +                                       *best_m = m;
>> +                                       *best_od_fb = od_fb;
>> +                                       if (best_rate_mhz == rate_mhz)
>> +                                               goto done;
>> +                               }
>> +                       }
>> +               }
>> +       }
>
> Nothing strictly wrong with the above, but is is n^3 complexity. If your
> tables are large then you might be spending a non-trivial amount time
> calculating the pll parameters.

Actually od_fb il always quite small (maximum /1, /2, /4, /8 maximum).
But I see the problem.
I'll investigate a bit to check if there is a way to make it simpler
(and shorter)

>> +
>> +done:
>> +       *best_od_fb >>= 1;
>> +       *rate = best_rate_mhz * 1000000;
>> +       return;
>> +
>> +}
>> +
>> +static unsigned long meson_clk_pll_recalc_rate(struct clk_hw *hw,
>> +                                               unsigned long parent_rate)
>> +{
>> +       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
>> +       struct parm *p_n, *p_m, *p_od_fb;
>> +       unsigned long parent_rate_mhz = parent_rate / 1000000;
>> +       unsigned long rate_mhz;
>> +       u16 n, m, od_fb = 1;
>> +       u32 reg_n, reg_m, reg_od_fb;
>> +
>> +       p_n = &pll->conf->n;
>> +       p_m = &pll->conf->m;
>> +       p_od_fb = &pll->conf->od_fb;
>> +
>> +       reg_n = readl(pll->base + p_n->reg_off);
>> +       n = PARM_GET(p_n->width, p_n->shift, reg_n);
>> +
>> +       reg_m = readl(pll->base + p_m->reg_off);
>> +       m = PARM_GET(p_m->width, p_m->shift, reg_m);
>> +
>> +       if (p_od_fb->width != MESON_PARM_NOT_APPLICABLE) {
>> +               reg_od_fb = readl(pll->base + p_od_fb->reg_off);
>> +               od_fb = PARM_GET(p_od_fb->width, p_od_fb->shift, reg_od_fb);
>> +               od_fb = 1 << od_fb;
>> +       }
>> +
>> +       rate_mhz = parent_rate_mhz * m * od_fb / n;
>> +
>> +       return rate_mhz * 1000000;
>> +}
>> +
>> +static long meson_clk_pll_round_rate(struct clk_hw *hw, unsigned long rate,
>> +                                     unsigned long *parent_rate)
>> +{
>> +       struct meson_clk_pll *pll = to_meson_clk_pll(hw);
>> +       u16 m, n, od_fb;
>> +
>> +       meson_clk_pll_get_parms(pll, &rate, *parent_rate, &n, &m, &od_fb);
>> +       if (m == 0 || n == 0)
>> +               return -EINVAL;
>> +
>> +       return rate;
>> +}
>
> Can the clock signal input to your pll by multiplexed? Are there
> multiple possible parents to a pll? If so you might want to use
> .determine_rate over .round_rate.

The input to the PLLs is always unique for both Meson6 and Meson8.

> <snip>
>
>> diff --git a/drivers/clk/meson/clkc.h b/drivers/clk/meson/clkc.h
>> new file mode 100644
>> index 0000000..6c0f611
>> --- /dev/null
>> +++ b/drivers/clk/meson/clkc.h
>> @@ -0,0 +1,159 @@
>> +/*
>> + * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope 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.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#ifndef __CLKC_H
>> +#define __CLKC_H
>
> <snip>
>
>> +void meson_clk_init(struct device_node *np, unsigned long nr_clks);
>> +void meson_clk_add_lookup(struct clk *clk, unsigned int id);
>> +void meson_clk_register_pll_divs(struct pll_div_conf *pll_divs,
>> +                                unsigned int nr_pll_divs,
>> +                                void __iomem *reg_base);
>> +void meson_clk_register_pll_div(struct pll_div_conf *pll_div_conf,
>> +                               void __iomem *reg_base, spinlock_t *lock);
>> +void meson_clk_register_clks(struct clk_conf *clk_confs,
>> +                            unsigned int nr_confs,
>> +                            void __iomem *clk_base);
>
> Missing #endif here causes compile failure.

Ah, then you will find a double #endif in the reset controller patch.
I screwed up separating the patches.

Thank you for the review.

-- 
Carlo Caione

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/4] ARM: meson: add basic infrastructure for clocks
  2014-11-19 22:42   ` Mike Turquette
@ 2014-11-19 22:51     ` Carlo Caione
  0 siblings, 0 replies; 9+ messages in thread
From: Carlo Caione @ 2014-11-19 22:51 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Nov 19, 2014 at 11:42 PM, Mike Turquette <mturquette@linaro.org> wrote:
> Quoting Carlo Caione (2014-11-19 02:32:20)
>> diff --git a/drivers/clk/meson/clk-pll.c b/drivers/clk/meson/clk-pll.c
>> new file mode 100644
>> index 0000000..5b6d064
>> --- /dev/null
>> +++ b/drivers/clk/meson/clk-pll.c
>> @@ -0,0 +1,298 @@
>> +/*
>> + * Copyright (c) 2014 Carlo Caione <carlo@caione.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify it
>> + * under the terms and conditions of the GNU General Public License,
>> + * version 2, as published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope 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.
>> + *
>> + * You should have received a copy of the GNU General Public License along with
>> + * this program.  If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/clkdev.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>
> All of the headers below are unnecessary. Copy/paste? I didn't bother to
> check the ones above.

No prob. I'll fix them also.

>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of_address.h>
>> +#include <linux/slab.h>
>> +#include <linux/string.h>
>
> Regards,
> Mike

Thanks,

-- 
Carlo Caione

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2014-11-19 22:51 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-11-19 10:32 [PATCH 0/4] ARM: meson: add clock/reset controller Carlo Caione
2014-11-19 10:32 ` [PATCH 1/4] ARM: meson: add basic infrastructure for clocks Carlo Caione
2014-11-19 22:30   ` Mike Turquette
2014-11-19 22:48     ` Carlo Caione
2014-11-19 22:42   ` Mike Turquette
2014-11-19 22:51     ` Carlo Caione
2014-11-19 10:32 ` [PATCH 2/4] ARM: meson: add reset controller Carlo Caione
2014-11-19 10:32 ` [PATCH 3/4] ARM: meson: enable reset controller in kconfig Carlo Caione
2014-11-19 10:32 ` [PATCH 4/4] ARM: meson: add documentation for reset/clock controller Carlo Caione

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).