[PATCH 1/3] clk: sunxi: Add a driver for the CCU

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 




Most of the clocks in the Allwinner's SoCs are configured in the CCU
(Clock Configuration Unit).

The PLL clocks are driven from the main clock. Their rates are controlled
by a set of multiply and divide factors, named from the Allwinner's
documentation:
- multipliers: 'n' and 'k'
- dividers: 'd1', 'd2', 'm' and 'p'

The peripheral clocks may receive their inputs from one or more parents,
thanks to a mux. Their rates are controlled by a set of divide factors
only, named 'm' and 'p'.

This driver also handles:
- fixed clocks,
- the phase delays for the MMCs,
- the clock gates,
- the bus gates,
- and the resets.

Signed-off-by: Jean-Francois Moine <moinejf@xxxxxxx>
---
 drivers/clk/sunxi/Makefile |   2 +
 drivers/clk/sunxi/ccu.c    | 980 +++++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/sunxi/ccu.h    | 153 +++++++
 3 files changed, 1135 insertions(+)
 create mode 100644 drivers/clk/sunxi/ccu.c
 create mode 100644 drivers/clk/sunxi/ccu.h

diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index 39d2044..b8ca3e2 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -26,3 +26,5 @@ obj-$(CONFIG_MACH_SUN9I) += clk-sun9i-cpus.o
 obj-$(CONFIG_MFD_SUN6I_PRCM) += \
 	clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \
 	clk-sun8i-apb0.o
+
+obj-y += ccu.o
diff --git a/drivers/clk/sunxi/ccu.c b/drivers/clk/sunxi/ccu.c
new file mode 100644
index 0000000..5749f9c
--- /dev/null
+++ b/drivers/clk/sunxi/ccu.c
@@ -0,0 +1,980 @@
+/*
+ * Allwinner system CCU
+ *
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@xxxxxxx>
+ * Rewrite from 'sunxi-ng':
+ * Copyright (C) 2016 Maxime Ripard <maxime.ripard@xxxxxxxxxxxxxxxxxx>
+ *
+ * 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.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/reset-controller.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+#include <linux/rational.h>
+#include <linux/of_address.h>
+
+#include "ccu.h"
+
+#define CCU_DEBUG 0
+
+#define CCU_MASK(shift, width) (((1 << width) - 1) << shift)
+
+/*
+ * factors:
+ *	n: multiplier (PLL)
+ *	d1, d2: boolean dividers by 2 (d2 is p with 1 bit width - PLL)
+ *	k: multiplier (PLL)
+ *	m: divider
+ *	p: divider by power of 2
+ */
+struct values {
+	int n, d1, k, m, p;
+};
+
+static DEFINE_SPINLOCK(ccu_lock);
+
+void ccu_set_clock(struct ccu *ccu, int reg, u32 mask, u32 val)
+{
+
+#if CCU_DEBUG
+	pr_info("** ccu %s set %03x %08x\n",
+		clk_hw_get_name(&ccu->hw), reg,
+		(readl(ccu->base + reg) & ~mask) | val);
+#endif
+	spin_lock(&ccu_lock);
+	writel((readl(ccu->base + reg) & ~mask) | val, ccu->base + reg);
+	spin_unlock(&ccu_lock);
+}
+
+/* --- prepare / enable --- */
+int ccu_prepare(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (!ccu->reset_reg && !ccu->bus_reg)
+		return 0;
+
+#if CCU_DEBUG
+	pr_info("** ccu %s prepare\n", clk_hw_get_name(&ccu->hw));
+#endif
+	spin_lock(&ccu_lock);
+	if (ccu->reset_reg)
+		writel(readl(ccu->base + ccu->reset_reg) |
+							BIT(ccu->reset_bit),
+				ccu->base + ccu->reset_reg);
+	if (ccu->bus_reg)
+		writel(readl(ccu->base + ccu->bus_reg) | BIT(ccu->bus_bit),
+				ccu->base + ccu->bus_reg);
+	spin_unlock(&ccu_lock);
+
+	return 0;
+}
+
+void ccu_unprepare(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (!ccu->reset_reg && !ccu->bus_reg)
+		return;
+
+#if CCU_DEBUG
+	pr_info("** ccu %s unprepare\n", clk_hw_get_name(&ccu->hw));
+#endif
+	spin_lock(&ccu_lock);
+	if (ccu->bus_reg)
+		writel(readl(ccu->base + ccu->bus_reg) & ~BIT(ccu->bus_bit),
+				ccu->base + ccu->bus_reg);
+	if (ccu->reset_reg)
+		writel(readl(ccu->base + ccu->reset_reg) &
+							~BIT(ccu->reset_bit),
+				ccu->base + ccu->reset_reg);
+	spin_unlock(&ccu_lock);
+}
+
+int ccu_enable(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (!ccu->has_gate)
+		return 0;
+
+#if CCU_DEBUG
+	pr_info("** ccu %s enable\n", clk_hw_get_name(&ccu->hw));
+#endif
+	spin_lock(&ccu_lock);
+	writel(readl(ccu->base + ccu->reg) | BIT(ccu->gate_bit),
+				ccu->base + ccu->reg);
+	spin_unlock(&ccu_lock);
+
+	return 0;
+}
+
+void ccu_disable(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (!ccu->has_gate)
+		return;
+
+#if CCU_DEBUG
+	pr_info("** ccu %s disable\n", clk_hw_get_name(&ccu->hw));
+#endif
+	spin_lock(&ccu_lock);
+	writel(readl(ccu->base + ccu->reg) & ~BIT(ccu->gate_bit),
+				ccu->base + ccu->reg);
+	spin_unlock(&ccu_lock);
+}
+
+/* --- PLL --- */
+static int ccu_pll_find_best(struct ccu *ccu,
+				unsigned long rate,
+				unsigned long parent_rate,
+				struct values *p_v)
+{
+	int max_mul, max_div, mul, div, t;
+	int n = 1, d1 = 1, k = 1, m = 1, p = 0;
+	int max_n = 1 << ccu->n_width;
+	int max_d1 = 1 << ccu->d1_width;
+	int max_k = 1 << ccu->k_width;
+	int max_m = 1 << ccu->m_width;
+	int max_p = 1 << ccu->p_width;
+
+	if (ccu->features & CCU_FEATURE_N0)
+		max_n--;
+
+	/* compute n */
+	if (max_n > 1) {
+		max_mul = max_n * max_k;
+		if (rate > parent_rate * max_mul) {
+			pr_err("%s: Clock rate too high %ld > %ld * %d * %d\n",
+				clk_hw_get_name(&ccu->hw),
+				rate, parent_rate, max_n, max_k);
+			return -EINVAL;
+		}
+		max_div = max_m * max_d1 << max_p;
+		if (max_div > 1) {
+			unsigned long lmul, ldiv;
+
+			rational_best_approximation(rate, parent_rate,
+						max_mul - 1,
+						max_div - 1,
+						&lmul, &ldiv);
+			mul = lmul;
+			div = ldiv;
+			if (ccu->n_min && mul < ccu->n_min) {
+				t = (ccu->n_min + mul - 1) / mul;
+				mul *= t;
+				div *= t;
+			}
+		} else {
+			mul = (rate + parent_rate - 1) / parent_rate;
+			div = 1;
+		}
+
+		/* compute k (present only when 'n' is present) */
+		if (max_k > 1) {
+			int k_min, k_opt, delta_opt = 100, delta;
+
+			k = (mul + max_n - 1) / max_n;
+			k_opt = k_min = k;
+			for (k = max_k; k > k_min; k--) {
+				n = (mul + k - 1) / k;
+				t = n * k;
+				delta = t - mul;
+				if (delta == 0) {
+					k_opt = k;
+					break;
+				}
+				if (delta < 0)
+					delta = -delta;
+				if (delta < delta_opt) {
+					delta_opt = delta;
+					k_opt = k;
+				}
+			}
+			k = k_opt;
+			n = (mul + k - 1) / k;
+		} else {
+			n = mul;
+		}
+	} else {
+		div = (parent_rate + rate - 1) / rate;
+	}
+
+	/* compute d1 (value is only 1 or 2) */
+	if (max_d1 > 1) {
+		if (div % 2 == 0) {
+			d1 = 2;
+			div /= 2;
+		}
+	}
+
+	/* compute p */
+/*	p = 0; */
+	while (div % 2 == 0 && p <= max_p) {
+		p++;
+		div /= 2;
+	}
+
+	/* compute m */
+	if (max_m > 1) {
+		if (div <= max_m)
+			m = div;
+		else
+			m = max_m;
+		div /= m;
+	}
+
+	/* adjust n */
+	n = DIV_ROUND_CLOSEST((rate << p) * m * d1, parent_rate);
+	n = DIV_ROUND_CLOSEST(n, k);
+
+	p_v->n = n;
+	p_v->d1 = d1;
+	p_v->k = k;
+	p_v->m = m;
+	p_v->p = p;
+
+	return 0;
+}
+
+static unsigned long ccu_pll_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	const struct ccu_extra *extra = ccu->extra;
+	unsigned long rate;
+	int i, n, d1, m, k, p;
+	u32 reg;
+
+	reg = readl(ccu->base + ccu->reg);
+
+	if (extra) {
+		for (i = 0; i < extra->num_frac - 1; i++) {
+			if ((reg & extra->frac[i].mask) == extra->frac[i].val)
+				return rate = extra->frac[i].rate;
+		}
+	}
+
+	rate = parent_rate;
+
+	if (ccu->d1_width) {
+		d1 = reg >> ccu->d1_shift;
+		d1 &= (1 << ccu->d1_width) - 1;
+		rate /= (d1 + 1);
+	}
+
+	if (ccu->n_width) {
+		n = reg >> ccu->n_shift;
+		n &= (1 << ccu->n_width) - 1;
+		if (!(ccu->features & CCU_FEATURE_N0))
+			n++;
+		rate *= n;
+	}
+
+	if (ccu->m_width) {
+		m = reg >> ccu->m_shift;
+		m &= (1 << ccu->m_width) - 1;
+		rate /= (m + 1);
+	}
+
+	if (ccu->k_width) {
+		k = reg >> ccu->k_shift;
+		k &= (1 << ccu->k_width) - 1;
+		rate *= (k + 1);
+	}
+
+	if (ccu->p_width) {
+		p = reg >> ccu->p_shift;
+		p &= (1 << ccu->p_width) - 1;
+		rate >>= p;
+	}
+
+	if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+		rate /= extra->fixed_div[0];
+
+	return rate;
+}
+
+static long ccu_pll_round_rate(struct clk_hw *hw,
+				unsigned long rate,
+				unsigned long *parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	const struct ccu_extra *extra = ccu->extra;
+	struct values v;
+	int i, ret;
+
+	if (extra) {
+		for (i = 0; i < extra->num_frac - 1; i++) {
+			if (extra->frac[i].rate == rate)
+				return rate;
+		}
+
+		if (ccu->features & CCU_FEATURE_FIXED_POSTDIV)
+			rate *= extra->fixed_div[0];
+	}
+
+	ret = ccu_pll_find_best(ccu, rate, *parent_rate, &v);
+	if (ret)
+		return ret;
+
+	rate = *parent_rate / v.d1 * v.n / v.m * v.k >> v.p;
+
+	if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+		rate /= extra->fixed_div[0];
+
+	return rate;
+}
+
+static void ccu_pll_set_flat_factors(struct ccu *ccu, u32 mask, u32 val)
+{
+	u32 reg, m_val, p_val;
+	u32 m_mask = (1 << ccu->m_width) - 1;
+	u32 p_mask = (1 << ccu->p_width) - 1;
+
+	reg = readl(ccu->base + ccu->reg);
+	m_val = reg & m_mask;
+	p_val = reg & p_mask;
+
+	spin_lock(&ccu_lock);
+
+	/* increase p, then m */
+	if (ccu->p_width && p_val < (val & p_mask)) {
+		reg &= ~p_mask;
+		reg |= val & p_mask;
+		writel(reg, ccu->base + ccu->reg);
+		udelay(10);
+	}
+	if (ccu->m_width && m_val < (val & m_mask)) {
+		reg &= ~m_mask;
+		reg |= val & m_mask;
+		writel(reg, ccu->base + ccu->reg);
+		udelay(10);
+	}
+
+	/* set other factors */
+	reg &= ~(mask & ~(p_mask | m_mask));
+	reg |= val & ~(p_mask | m_mask);
+	writel(reg, ccu->base + ccu->reg);
+
+	/* decrease m */
+	if (ccu->m_width && m_val > (val & m_mask)) {
+		reg &= ~m_mask;
+		reg |= val & m_mask;
+		writel(reg, ccu->base + ccu->reg);
+		udelay(10);
+	}
+
+	/* wait for PLL stable */
+	if (ccu->lock_reg) {
+		u32 lock;
+
+		lock = BIT(ccu->lock_bit);
+		WARN_ON(readl_relaxed_poll_timeout(ccu->base + ccu->lock_reg,
+							reg, !(reg & lock),
+							100, 70000));
+	}
+
+	/* decrease p */
+	if (ccu->p_width && p_val > (val & p_mask)) {
+		reg &= ~p_mask;
+		reg |= val & p_mask;
+		writel(reg, ccu->base + ccu->reg);
+		udelay(10);
+	}
+
+	spin_unlock(&ccu_lock);
+}
+
+static int ccu_pll_set_rate(struct clk_hw *hw,
+				unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	const struct ccu_extra *extra = ccu->extra;
+	struct values v;
+	u32 mask, val;
+	int ret;
+
+	mask =  CCU_MASK(ccu->n_shift, ccu->n_width) |
+		CCU_MASK(ccu->d1_shift, ccu->d1_width) |
+		CCU_MASK(ccu->k_shift, ccu->k_width) |
+		CCU_MASK(ccu->m_shift, ccu->m_width) |
+		CCU_MASK(ccu->p_shift, ccu->p_width);
+	val = 0;
+
+	if (extra && extra->num_frac) {
+		int i;
+
+		for (i = 0; i < extra->num_frac - 1; i++) {
+			if (extra->frac[i].rate == rate) {
+				ccu_set_clock(ccu, ccu->reg,
+						extra->frac[i].mask,
+						extra->frac[i].val);
+				return 0;
+			}
+		}
+		mask |= extra->frac[i].mask;
+		val |= extra->frac[i].val;
+	}
+
+	if (extra && (ccu->features & CCU_FEATURE_FIXED_POSTDIV))
+		rate *= extra->fixed_div[0];
+
+
+	ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+	if (ret)
+		return ret;
+
+	if (!(ccu->features & CCU_FEATURE_N0))
+		v.n--;
+
+	val |=  (v.n << ccu->n_shift) |
+		((v.d1 - 1) << ccu->d1_shift) |
+		((v.k - 1) << ccu->k_shift) |
+		((v.m - 1) << ccu->m_shift) |
+		(v.p << ccu->p_shift);
+
+	if (ccu->upd_bit)				/* cannot be 0 */
+		val |= BIT(ccu->upd_bit);
+
+	if (!(ccu->features & CCU_FEATURE_FLAT_FACTORS))
+		ccu_set_clock(ccu, ccu->reg, mask, val);
+	else
+		ccu_pll_set_flat_factors(ccu, mask, val);
+
+	/* wait for PLL stable */
+	if (ccu->lock_reg) {
+		u32 lock, reg;
+
+		lock = BIT(ccu->lock_bit);
+		WARN_ON(readl_relaxed_poll_timeout(ccu->base + ccu->lock_reg,
+							reg, !(reg & lock),
+							100, 70000));
+	}
+
+	return 0;
+}
+
+const struct clk_ops ccu_pll_ops = {
+	.prepare	= ccu_prepare,
+	.unprepare	= ccu_unprepare,
+	.enable		= ccu_enable,
+	.disable	= ccu_disable,
+/*	.is_enabled	= NULL;	(don't disable the clocks at startup time) */
+
+	.recalc_rate	= ccu_pll_recalc_rate,
+	.round_rate	= ccu_pll_round_rate,
+	.set_rate	= ccu_pll_set_rate,
+};
+
+/* --- mux parent --- */
+u8 ccu_get_parent(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (!ccu->mux_width)
+		return 0;
+
+	return (readl(ccu->base + ccu->reg) >> ccu->mux_shift) &
+				((1 << ccu->mux_width) - 1);
+}
+
+int ccu_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	u32 mask;
+
+	if (!ccu->mux_width)
+		return 0;
+
+	mask = CCU_MASK(ccu->mux_shift, ccu->mux_width);
+
+	ccu_set_clock(ccu, ccu->reg, mask, index << ccu->mux_shift);
+
+	return 0;
+}
+
+/* --- mux --- */
+static void ccu_mux_adjust_parent_for_prediv(struct ccu *ccu,
+					     int parent_index,
+					     unsigned long *parent_rate)
+{
+	const struct ccu_extra *extra = ccu->extra;
+	int prediv = 1;
+	u32 reg;
+
+	if (!(extra &&
+	      (ccu->features & (CCU_FEATURE_MUX_FIXED_PREDIV |
+				CCU_FEATURE_MUX_VARIABLE_PREDIV))))
+		return;
+
+	reg = readl(ccu->base + ccu->reg);
+	if (parent_index < 0)
+		parent_index = (reg >> ccu->mux_shift) &
+					((1 << ccu->mux_width) - 1);
+
+	if (ccu->features & CCU_FEATURE_MUX_FIXED_PREDIV)
+		prediv = extra->fixed_div[parent_index];
+
+	if (ccu->features & CCU_FEATURE_MUX_VARIABLE_PREDIV)
+		if (parent_index == extra->variable_prediv.index) {
+			u8 div;
+
+			div = reg >> extra->variable_prediv.shift;
+			div &= (1 << extra->variable_prediv.width) - 1;
+			prediv = div + 1;
+		}
+
+	*parent_rate /= prediv;
+}
+
+/* --- periph --- */
+static unsigned long ccu_m_round_rate(struct ccu *ccu,
+					unsigned long rate,
+					unsigned long parent_rate)
+{
+	int m;
+
+	/*
+	 * We can't use divider_round_rate that assumes that there's
+	 * several parents, while we might be called to evaluate
+	 * several different parents.
+	 */
+	m = divider_get_val(rate, parent_rate,
+			ccu->div_table, ccu->m_width, ccu->div_flags);
+
+	return divider_recalc_rate(&ccu->hw, parent_rate, m,
+				   ccu->div_table, ccu->div_flags);
+}
+
+static unsigned long ccu_mp_round_rate(struct ccu *ccu,
+				unsigned long rate,
+				unsigned long parent_rate)
+{
+	struct values v;
+	int ret;
+
+	ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+	if (ret)
+		return 0;
+
+	return parent_rate / v.m >> v.p;
+}
+
+unsigned long ccu_periph_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	int m, p;
+	u32 reg;
+
+	ccu_mux_adjust_parent_for_prediv(ccu, -1, &parent_rate);
+
+	if (!ccu->m_width && !ccu->p_width)
+		return parent_rate;
+
+	reg = readl(ccu->base + ccu->reg);
+	m = (reg >> ccu->m_shift) & ((1 << ccu->m_width) - 1);
+
+	if (ccu->p_width) {
+		reg = readl(ccu->base + ccu->reg);
+		p = (reg >> ccu->p_shift) & ((1 << ccu->p_width) - 1);
+
+		return parent_rate / (m + 1) >> p;
+	}
+
+	return divider_recalc_rate(hw, parent_rate, m,
+				ccu->div_table, ccu->div_flags);
+}
+
+int ccu_periph_determine_rate(struct clk_hw *hw,
+				struct clk_rate_request *req)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	unsigned long best_parent_rate = 0, best_rate = 0;
+	struct clk_hw *best_parent;
+	unsigned int i;
+	unsigned long (*round)(struct ccu *,
+				unsigned long,
+				unsigned long);
+
+	if (ccu->p_width)
+		round = ccu_mp_round_rate;
+	else if (ccu->m_width)
+		round = ccu_m_round_rate;
+	else
+		return __clk_mux_determine_rate(hw, req);
+
+	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+		unsigned long new_rate, parent_rate;
+		struct clk_hw *parent;
+
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		parent_rate = clk_hw_get_rate(parent);
+		ccu_mux_adjust_parent_for_prediv(ccu, i, &parent_rate);
+		new_rate = round(ccu, req->rate, parent_rate);
+
+		if (new_rate == req->rate) {
+			best_parent = parent;
+			best_parent_rate = parent_rate;
+			best_rate = new_rate;
+			goto out;
+		}
+
+		if ((req->rate - new_rate) < (req->rate - best_rate)) {
+			best_rate = new_rate;
+			best_parent_rate = parent_rate;
+			best_parent = parent;
+		}
+	}
+
+	if (best_rate == 0)
+		return -EINVAL;
+
+out:
+	req->best_parent_hw = best_parent;
+	req->best_parent_rate = best_parent_rate;
+	req->rate = best_rate;
+
+	return 0;
+}
+
+int ccu_periph_set_rate(struct clk_hw *hw, unsigned long rate,
+			unsigned long parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	const struct ccu_extra *extra = ccu->extra;
+	struct values v;
+	u32 mask;
+	int ret;
+
+	if (!ccu->m_width && !ccu->p_width)
+		return 0;
+
+	ccu_mux_adjust_parent_for_prediv(ccu, -1, &parent_rate);
+
+	if (extra && (ccu->features & CCU_FEATURE_MODE_SELECT)) {
+		/* fixme: should use new mode */
+		if (rate == extra->mode_select.rate)
+			rate /= 2;
+	}
+
+	if (ccu->p_width) {				/* m and p */
+		ret = ccu_pll_find_best(ccu, rate, parent_rate, &v);
+		if (ret)
+			return ret;
+	} else {					/* m alone */
+		v.m = divider_get_val(rate, parent_rate,
+				ccu->div_table, ccu->m_width, ccu->div_flags);
+		v.p = 0;
+		return 0;
+	}
+
+	mask = CCU_MASK(ccu->m_shift, ccu->m_width) |
+		CCU_MASK(ccu->p_shift, ccu->p_width);
+
+	if (ccu->features & CCU_FEATURE_SET_RATE_UNGATE)
+		ccu_disable(hw);
+	ccu_set_clock(ccu, ccu->reg, mask, ((v.m - 1) << ccu->m_shift) |
+					    (v.p << ccu->p_shift));
+	if (ccu->features & CCU_FEATURE_SET_RATE_UNGATE)
+		ccu_enable(hw);
+
+	return 0;
+}
+
+const struct clk_ops ccu_periph_ops = {
+	.prepare	= ccu_prepare,
+	.unprepare	= ccu_unprepare,
+	.enable		= ccu_enable,
+	.disable	= ccu_disable,
+/*	.is_enabled	= NULL;	(don't disable the clocks at startup time) */
+
+	.get_parent	= ccu_get_parent,
+	.set_parent	= ccu_set_parent,
+
+	.determine_rate	= ccu_periph_determine_rate,
+	.recalc_rate	= ccu_periph_recalc_rate,
+	.set_rate	= ccu_periph_set_rate,
+};
+
+/* --- fixed factor --- */
+/* mul is n_width - div is m_width */
+unsigned long ccu_fixed_factor_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	return parent_rate / ccu->m_width * ccu->n_width;
+}
+
+long ccu_fixed_factor_round_rate(struct clk_hw *hw,
+				unsigned long rate,
+				unsigned long *parent_rate)
+{
+	struct ccu *ccu = hw2ccu(hw);
+
+	if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+		unsigned long best_parent;
+
+		best_parent = (rate / ccu->n_width) * ccu->m_width;
+		*parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
+						 best_parent);
+	}
+
+	return *parent_rate / ccu->m_width * ccu->n_width;
+}
+
+int ccu_fixed_factor_set_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long parent_rate)
+{
+	return 0;
+}
+
+const struct clk_ops ccu_fixed_factor_ops = {
+	.disable	= ccu_disable,
+	.enable		= ccu_enable,
+/*	.is_enabled	= NULL, */
+
+	.recalc_rate	= ccu_fixed_factor_recalc_rate,
+	.round_rate	= ccu_fixed_factor_round_rate,
+	.set_rate	= ccu_fixed_factor_set_rate,
+};
+
+/* --- phase --- */
+static int ccu_phase_get_phase(struct clk_hw *hw)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	struct clk_hw *parent, *grandparent;
+	unsigned int parent_rate, grandparent_rate;
+	u16 step, parent_div;
+	u32 reg;
+	u8 delay;
+
+	reg = readl(ccu->base + ccu->reg);
+	delay = (reg >> ccu->p_shift);
+	delay &= (1 << ccu->p_width) - 1;
+
+	if (!delay)
+		return 180;
+
+	/* Get our parent clock, it's the one that can adjust its rate */
+	parent = clk_hw_get_parent(hw);
+	if (!parent)
+		return -EINVAL;
+
+	/* And its rate */
+	parent_rate = clk_hw_get_rate(parent);
+	if (!parent_rate)
+		return -EINVAL;
+
+	/* Now, get our parent's parent (most likely some PLL) */
+	grandparent = clk_hw_get_parent(parent);
+	if (!grandparent)
+		return -EINVAL;
+
+	/* And its rate */
+	grandparent_rate = clk_hw_get_rate(grandparent);
+	if (!grandparent_rate)
+		return -EINVAL;
+
+	/* Get our parent clock divider */
+	parent_div = grandparent_rate / parent_rate;
+
+	step = DIV_ROUND_CLOSEST(360, parent_div);
+	return delay * step;
+}
+
+static int ccu_phase_set_phase(struct clk_hw *hw, int degrees)
+{
+	struct ccu *ccu = hw2ccu(hw);
+	struct clk_hw *parent, *grandparent;
+	unsigned int parent_rate, grandparent_rate;
+	u32 mask;
+	u8 delay = 0;
+	u16 step, parent_div;
+
+	if (degrees == 180)
+		goto set_phase;
+
+	/* Get our parent clock, it's the one that can adjust its rate */
+	parent = clk_hw_get_parent(hw);
+	if (!parent)
+		return -EINVAL;
+
+	/* And its rate */
+	parent_rate = clk_hw_get_rate(parent);
+	if (!parent_rate)
+		return -EINVAL;
+
+	/* Now, get our parent's parent (most likely some PLL) */
+	grandparent = clk_hw_get_parent(parent);
+	if (!grandparent)
+		return -EINVAL;
+
+	/* And its rate */
+	grandparent_rate = clk_hw_get_rate(grandparent);
+	if (!grandparent_rate)
+		return -EINVAL;
+
+	/* Get our parent divider */
+	parent_div = grandparent_rate / parent_rate;
+
+	/*
+	 * We can only outphase the clocks by multiple of the
+	 * PLL's period.
+	 *
+	 * Since our parent clock is only a divider, and the
+	 * formula to get the outphasing in degrees is deg =
+	 * 360 * delta / period
+	 *
+	 * If we simplify this formula, we can see that the
+	 * only thing that we're concerned about is the number
+	 * of period we want to outphase our clock from, and
+	 * the divider set by our parent clock.
+	 */
+	step = DIV_ROUND_CLOSEST(360, parent_div);
+	delay = DIV_ROUND_CLOSEST(degrees, step);
+
+set_phase:
+	mask = CCU_MASK(ccu->p_shift, ccu->p_width);
+	ccu_set_clock(ccu, ccu->reg, mask, delay << ccu->p_shift);
+
+	return 0;
+}
+
+const struct clk_ops ccu_phase_ops = {
+	.get_phase	= ccu_phase_get_phase,
+	.set_phase	= ccu_phase_set_phase,
+};
+
+/* --- reset --- */
+static inline
+struct ccu_reset *rcdev_to_ccu_reset(struct reset_controller_dev *rcdev)
+{
+	return container_of(rcdev, struct ccu_reset, rcdev);
+}
+
+static void ccu_set_reset_clock(struct ccu_reset *ccu_reset,
+				int reg, int bit, int enable)
+{
+	u32 mask;
+
+	if (!reg)			/* compatibility */
+		return;
+
+#if CCU_DEBUG
+	pr_info("** ccu reset %03x %d %sassert\n",
+		reg, bit, enable ? "de-" : "");
+#endif
+	mask = BIT(bit);
+
+	spin_lock(&ccu_lock);
+	if (enable)
+		writel(readl(ccu_reset->base + reg) | mask,
+			ccu_reset->base + reg);
+	else
+		writel(readl(ccu_reset->base + reg) & ~mask,
+			ccu_reset->base + reg);
+	spin_unlock(&ccu_lock);
+}
+
+static int ccu_reset_assert(struct reset_controller_dev *rcdev,
+			    unsigned long id)
+{
+	struct ccu_reset *ccu_reset = rcdev_to_ccu_reset(rcdev);
+	const struct ccu_reset_map *map = &ccu_reset->reset_map[id];
+
+	ccu_set_reset_clock(ccu_reset, map->reg, map->bit, 0);
+
+	return 0;
+}
+
+static int ccu_reset_deassert(struct reset_controller_dev *rcdev,
+			      unsigned long id)
+{
+	struct ccu_reset *ccu_reset = rcdev_to_ccu_reset(rcdev);
+	const struct ccu_reset_map *map = &ccu_reset->reset_map[id];
+
+	ccu_set_reset_clock(ccu_reset, map->reg, map->bit, 1);
+
+	return 0;
+}
+
+const struct reset_control_ops ccu_reset_ops = {
+	.assert		= ccu_reset_assert,
+	.deassert	= ccu_reset_deassert,
+};
+
+/* --- init --- */
+int __init ccu_probe(struct device_node *node,
+			struct clk_hw_onecell_data *data,
+			struct ccu_reset *resets)
+{
+	struct clk_hw *hw;
+	struct ccu *ccu;
+	void __iomem *reg;
+	int i, ret;
+
+	reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+	if (IS_ERR(reg)) {
+		pr_err("%s: Clock mapping failed %d\n",
+			of_node_full_name(node), (int) PTR_ERR(reg));
+		return PTR_ERR(reg);
+	}
+
+	/* register the clocks */
+	for (i = 0; i < data->num; i++) {
+		hw = data->hws[i];
+#if CCU_DEBUG
+		if (!hw) {
+			pr_err("%s: Bad number of clocks %d != %d\n",
+				of_node_full_name(node),
+				i + 1, data->num);
+			data->num = i;
+			break;
+		}
+#endif
+		ccu = hw2ccu(hw);
+		ccu->base = reg;
+		ret = clk_hw_register(NULL, hw);
+		if (ret < 0) {
+			pr_err("%s: Register clock %s failed %d\n",
+				of_node_full_name(node),
+				clk_hw_get_name(hw), ret);
+			data->num = i;
+			break;
+		}
+	}
+	ret = of_clk_add_hw_provider(node, of_clk_hw_onecell_get, data);
+	if (ret < 0)
+		goto err;
+
+	/* register the resets */
+	resets->rcdev.of_node = node;
+	resets->base = reg;
+
+	ret = reset_controller_register(&resets->rcdev);
+	if (ret) {
+		pr_err("%s: Reset register failed %d\n",
+			of_node_full_name(node), ret);
+		goto err;
+	}
+
+	return ret;
+
+err:
+	/* don't do anything, otherwise no uart anymore */
+	return ret;
+}
diff --git a/drivers/clk/sunxi/ccu.h b/drivers/clk/sunxi/ccu.h
new file mode 100644
index 0000000..5597681
--- /dev/null
+++ b/drivers/clk/sunxi/ccu.h
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 Jean-Francois Moine <moinejf@xxxxxxx>
+ *
+ * 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.
+ */
+
+#ifndef _CCU_H_
+#define _CCU_H_
+
+struct device_node;
+
+#define CCU_HW(_name, _parent, _ops, _flags)			\
+	.hw.init = &(struct clk_init_data) {			\
+		.flags		= _flags,			\
+		.name		= _name,			\
+		.parent_names	= (const char *[]) { _parent },	\
+		.num_parents	= 1,				\
+		.ops		= _ops,				\
+	}
+
+#define CCU_HW_PARENTS(_name, _parents, _ops, _flags)		\
+	.hw.init = &(struct clk_init_data) {			\
+		.flags		= _flags,			\
+		.name		= _name,			\
+		.parent_names	= _parents,			\
+		.num_parents	= ARRAY_SIZE(_parents),		\
+		.ops		= _ops,				\
+	}
+
+#define CCU_REG(_reg) .reg = _reg
+#define CCU_RESET(_reg, _bit) .reset_reg = _reg, .reset_bit = _bit
+#define CCU_BUS(_reg, _bit) .bus_reg = _reg, .bus_bit = _bit
+#define CCU_GATE(_bit) .has_gate = 1, .gate_bit = _bit
+#define CCU_LOCK(_reg, _bit) .lock_reg = _reg, .lock_bit = _bit
+#define CCU_MUX(_shift, _width) .mux_shift = _shift, .mux_width = _width
+#define CCU_N(_shift, _width) .n_shift = _shift, .n_width = _width
+#define CCU_D1(_shift, _width) .d1_shift = _shift, .d1_width = _width
+#define CCU_D2(_shift, _width) .p_shift = _shift, .p_width = _width
+#define CCU_K(_shift, _width) .k_shift = _shift, .k_width = _width
+#define CCU_M(_shift, _width) .m_shift = _shift, .m_width = _width
+#define CCU_P(_shift, _width) .p_shift = _shift, .p_width = _width
+#define CCU_UPD(_bit) .upd_bit = _bit
+/* with ccu_fixed_factor_ops */
+#define CCU_FIXED(_mul, _div) .n_width = _mul, .m_width = _div
+/* with ccu_phase_ops */
+#define CCU_PHASE(_shift, _width) .p_shift = _shift, .p_width = _width
+
+#define CCU_FEATURE_FRACTIONAL		BIT(0)
+#define CCU_FEATURE_MUX_VARIABLE_PREDIV	BIT(1)
+#define CCU_FEATURE_MUX_FIXED_PREDIV	BIT(2)
+#define CCU_FEATURE_FIXED_POSTDIV	BIT(3)
+#define CCU_FEATURE_N0			BIT(4)
+#define CCU_FEATURE_MODE_SELECT		BIT(5)
+#define CCU_FEATURE_FLAT_FACTORS	BIT(6)
+#define CCU_FEATURE_SET_RATE_UNGATE	BIT(7)
+
+/* extra */
+#define CCU_EXTRA_FRAC(_frac) .frac = _frac, .num_frac = ARRAY_SIZE(_frac)
+#define CCU_EXTRA_POST_DIV(_div) .fixed_div[0] = _div
+
+/* fractional values */
+struct frac {
+	unsigned long rate;
+	u32 mask;
+	u32 val;
+};
+
+/* extra features */
+struct ccu_extra {
+	const struct frac *frac; /* array - last is the fractional mask/value */
+	u8 num_frac;
+
+	u8 fixed_div[4];
+
+	struct {
+		u8 index;
+		u8 shift;
+		u8 width;
+	} variable_prediv;
+
+	struct {
+		unsigned long rate;
+		u8 bit;
+	} mode_select;
+};
+
+struct ccu {
+	struct clk_hw	hw;
+
+	void __iomem *base;
+	u16 reg;
+
+	u16 reset_reg, bus_reg, lock_reg;
+	u8  reset_bit, bus_bit, lock_bit;
+	u8 has_gate, gate_bit;
+
+	u8 mux_shift, mux_width;
+	u8 n_shift, n_width, n_min;
+	u8 d1_shift, d1_width;
+	u8 k_shift, k_width;
+	u8 m_shift, m_width;
+	u8 p_shift, p_width;
+
+	u8 upd_bit;
+
+	u8 features;
+
+	const struct clk_div_table *div_table;
+	u32 div_flags;
+
+	const struct ccu_extra *extra;
+};
+
+struct ccu_reset_map {
+	u16	reg;
+	u16	bit;
+};
+
+struct ccu_reset {
+	void __iomem			*base;
+	const struct ccu_reset_map	*reset_map;
+	struct reset_controller_dev	rcdev;
+};
+
+extern const struct clk_ops ccu_fixed_factor_ops;
+extern const struct clk_ops ccu_periph_ops;
+extern const struct clk_ops ccu_pll_ops;
+extern const struct clk_ops ccu_phase_ops;
+extern const struct reset_control_ops ccu_reset_ops;
+
+static inline struct ccu *hw2ccu(struct clk_hw *hw)
+{
+	return container_of(hw, struct ccu, hw);
+}
+
+int ccu_probe(struct device_node *node,
+		struct clk_hw_onecell_data *data,
+		struct ccu_reset *resets);
+
+/* functions exported for specific features */
+void ccu_set_clock(struct ccu *ccu, int reg, u32 mask, u32 val);
+unsigned long ccu_fixed_factor_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate);
+long ccu_fixed_factor_round_rate(struct clk_hw *hw,
+				unsigned long rate,
+				unsigned long *parent_rate);
+int ccu_fixed_factor_set_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long parent_rate);
+
+#endif /* _CCU_H_ */
-- 
2.9.0

--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]
  Powered by Linux