[PATCH v1 06/14] clk: msm: Add support for root clock generators (RCGs)

Stephen Boyd sboyd at codeaurora.org
Wed Jul 24 20:43:34 EDT 2013


Cc: devicetree at vger.kernel.org
Signed-off-by: Stephen Boyd <sboyd at codeaurora.org>
---
 Documentation/devicetree/bindings/clock/msm.txt |  31 +
 drivers/clk/msm/Makefile                        |   2 +
 drivers/clk/msm/clk-rcg.c                       | 754 ++++++++++++++++++++++++
 drivers/clk/msm/clk-rcg.h                       | 114 ++++
 drivers/clk/msm/clk-rcg2.c                      | 320 ++++++++++
 5 files changed, 1221 insertions(+)
 create mode 100644 drivers/clk/msm/clk-rcg.c
 create mode 100644 drivers/clk/msm/clk-rcg.h
 create mode 100644 drivers/clk/msm/clk-rcg2.c

diff --git a/Documentation/devicetree/bindings/clock/msm.txt b/Documentation/devicetree/bindings/clock/msm.txt
index 2192621..f4595fa 100644
--- a/Documentation/devicetree/bindings/clock/msm.txt
+++ b/Documentation/devicetree/bindings/clock/msm.txt
@@ -38,3 +38,34 @@ Example:
 		clocks = <&pll8>;
 	};
 
+M/N:D Binding
+-------------
+
+Required properties:
+- compatible : shall be one of "qcom,p2-mn16-clock" or "qcom,p2-mn8-clock"
+- #clock-cells : from common clock binding; shall be set to 0
+- clocks : from common clock binding; shall be set of muxed input sources
+
+Example:
+	gsbi5_uart_rcg: gsbi5_uart_rcg {
+		#clock-cells = <0>;
+		compatible = "qcom,p2-mn16-clock";
+		clocks = <&pxo>, <&pll8>;
+	};
+
+M/N:D Dynamic Binding
+---------------------
+
+Required properties:
+- compatible : shall be one of "qcom,mn4-dyn-clock", "qcom,mn8-dyn-clock" or
+	       "qcom,p4-dyn-clock".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : from common clock binding; shall be set of muxed input sources
+
+Example:
+	gfx2d_rcg : gfx2d_rcg {
+		#clock-cells = <0>;
+		compatible = "qcom,mn4-dyn-clock";
+		clocks = <&pxo>, <&pll2>, <&pll8>;
+	};
+
diff --git a/drivers/clk/msm/Makefile b/drivers/clk/msm/Makefile
index 16b750f..fb78ac9 100644
--- a/drivers/clk/msm/Makefile
+++ b/drivers/clk/msm/Makefile
@@ -1,3 +1,5 @@
 obj-$(CONFIG_COMMON_CLK_MSM) += clk-msm.o
 
 clk-msm-$(CONFIG_COMMON_CLK_MSM) += clk-pll.o
+clk-msm-$(CONFIG_COMMON_CLK_MSM) += clk-rcg.o
+clk-msm-$(CONFIG_COMMON_CLK_MSM) += clk-rcg2.o
diff --git a/drivers/clk/msm/clk-rcg.c b/drivers/clk/msm/clk-rcg.c
new file mode 100644
index 0000000..3400fee
--- /dev/null
+++ b/drivers/clk/msm/clk-rcg.c
@@ -0,0 +1,754 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+
+#include <asm/div64.h>
+
+#include "clk-rcg.h"
+
+/**
+ * struct mn - M/N:D counter
+ * @mnctr_en_bit: bit to enable mn counter
+ * @mnctr_reset_bit: bit to assert mn counter reset
+ * @mnctr_mode_shift: lowest bit of mn counter mode field
+ * @n_val_shift: lowest bit of n value field
+ * @m_val_shift: lowest bit of m value field
+ * @width: number of bits in m/n/d values
+ */
+struct mn {
+	u8		mnctr_en_bit;
+	u8		mnctr_reset_bit;
+	u8		mnctr_mode_shift;
+#define MNCTR_MODE_DUAL 0x2
+#define MNCTR_MODE_MASK 0x3
+	u8		n_val_shift;
+	u8		m_val_shift;
+	u8		width;
+};
+
+/**
+ * struct pre_div - pre-divider
+ * @pre_div_shift: lowest bit of pre divider field
+ * @pre_div_width: number of bits in predivider
+ */
+struct pre_div {
+	u8		pre_div_shift;
+	u8		pre_div_width;
+};
+
+/**
+ * struct src_sel - source selector
+ * @src_sel_shift: lowest bit of source selection field
+ * @parent_map: map from software's parent index to hardware's src_sel field
+ */
+struct src_sel {
+	u8		src_sel_shift;
+#define SRC_SEL_MASK	0x7
+	u8		*parent_map;
+};
+
+/**
+ * struct clk_rcg - root clock generator
+ *
+ * @ctl_reg: clock control register
+ * @ctl_bit: ORed with @ctl_reg to enable the clock
+ * @ns_reg: NS register
+ * @md_reg: MD register
+ * @mn: mn counter
+ * @p: pre divider
+ * @s: source selector
+ * @freq_tbl: Frequency table
+ * @hw: handle between common and hardware-specific interfaces
+ * @lock: register lock
+ *
+ */
+struct clk_rcg {
+	void __iomem	*ctl_reg;
+	void __iomem	*ns_reg;
+	void __iomem	*md_reg;
+
+	u8		ctl_bit;
+	struct mn	mn;
+	struct pre_div	p;
+	struct src_sel	s;
+
+	const struct freq_tbl	*freq_tbl;
+
+	struct clk_hw	hw;
+	spinlock_t	*lock;
+};
+
+#define to_clk_rcg(_hw) container_of(_hw, struct clk_rcg, hw)
+
+/**
+ * struct clk_dyn_rcg - root clock generator with glitch free mux
+ *
+ * @ctl_reg: clock control register
+ * @ctl_bit: ORed with @ctl_reg to enable the clock
+ * @mux_sel_bit: Bit to switch glitch free mux
+ * @ns_reg: NS register
+ * @md_reg: MD0 and MD1 register
+ * @mn: mn counter (banked)
+ * @s: source selector (banked)
+ * @freq_tbl: Frequency table
+ * @hw: handle between common and hardware-specific interfaces
+ * @lock: register lock
+ *
+ */
+struct clk_dyn_rcg {
+	void __iomem	*ctl_reg;
+	void __iomem	*ns_reg;
+	void __iomem	*md_reg[2];
+
+	u8		ctl_bit;
+	u8		mux_sel_bit;
+	struct mn	mn[2];
+	struct pre_div	p[2];
+	struct src_sel	s[2];
+
+	const struct freq_tbl	*freq_tbl;
+
+	struct clk_hw	hw;
+	spinlock_t	*lock;
+};
+
+#define to_clk_dyn_rcg(_hw) container_of(_hw, struct clk_dyn_rcg, hw)
+
+static int clk_rcg_toggle(void __iomem *ctl_reg, u8 ctl_bit,
+		spinlock_t *lock, bool en)
+{
+	unsigned long flags;
+	u32 val;
+
+	spin_lock_irqsave(lock, flags);
+
+	val = readl_relaxed(ctl_reg);
+	if (en)
+		val |= BIT(ctl_bit);
+	else
+		val &= ~BIT(ctl_bit);
+	writel(val, ctl_reg);
+
+	spin_unlock_irqrestore(lock, flags);
+
+	return 0;
+}
+
+static int clk_rcg_enable(struct clk_hw *hw)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+
+	return clk_rcg_toggle(rcg->ctl_reg, rcg->ctl_bit, rcg->lock, true);
+}
+
+static void clk_rcg_disable(struct clk_hw *hw)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+
+	clk_rcg_toggle(rcg->ctl_reg, rcg->ctl_bit, rcg->lock, false);
+}
+
+static int clk_dyn_rcg_enable(struct clk_hw *hw)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+
+	return clk_rcg_toggle(rcg->ctl_reg, rcg->ctl_bit, rcg->lock, true);
+}
+
+static void clk_dyn_rcg_disable(struct clk_hw *hw)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+
+	clk_rcg_toggle(rcg->ctl_reg, rcg->ctl_bit, rcg->lock, false);
+}
+
+static int clk_dyn_rcg_is_enabled(struct clk_hw *hw)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+	u32 val;
+
+	val = readl_relaxed(rcg->ctl_reg);
+	val &= BIT(rcg->ctl_bit);
+
+	return val ? 1 : 0;
+}
+
+static u32 ns_to_src(struct src_sel *s, u32 ns)
+{
+	ns >>= s->src_sel_shift;
+	ns &= SRC_SEL_MASK;
+	return ns;
+}
+
+static u32 src_to_ns(struct src_sel *s, u8 src, u32 ns)
+{
+	u32 mask;
+
+	mask = SRC_SEL_MASK;
+	mask <<= s->src_sel_shift;
+	ns &= ~mask;
+
+	ns |= src << s->src_sel_shift;
+	return ns;
+}
+
+static u8 clk_rcg_get_parent(struct clk_hw *hw)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+	int num_parents = __clk_get_num_parents(hw->clk);
+	u32 ns;
+	int i;
+
+	ns = readl_relaxed(rcg->ns_reg);
+	ns = ns_to_src(&rcg->s, ns);
+
+	for (i = 0; i < num_parents; i++)
+		if (ns == rcg->s.parent_map[i])
+			return i;
+
+	return -EINVAL;
+}
+
+static int reg_to_bank(struct clk_dyn_rcg *rcg, u32 bank)
+{
+	bank &= BIT(rcg->mux_sel_bit);
+	return !!bank;
+}
+
+static u8 clk_dyn_rcg_get_parent(struct clk_hw *hw)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+	int num_parents = __clk_get_num_parents(hw->clk);
+	u32 ns, ctl;
+	int bank;
+	int i;
+	struct src_sel *s;
+
+	ctl = readl_relaxed(rcg->ctl_reg);
+	bank = reg_to_bank(rcg, ctl);
+	s = &rcg->s[bank];
+
+	ns = readl_relaxed(rcg->ns_reg);
+	ns = ns_to_src(s, ns);
+
+	for (i = 0; i < num_parents; i++)
+		if (ns == s->parent_map[i])
+			return i;
+
+	return -EINVAL;
+}
+
+static int clk_rcg_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+	unsigned long flags;
+	u32 ns;
+
+	spin_lock_irqsave(rcg->lock, flags);
+
+	ns = readl_relaxed(rcg->ns_reg);
+	ns = src_to_ns(&rcg->s, rcg->s.parent_map[index], ns);
+	writel(ns, rcg->ns_reg);
+
+	spin_unlock_irqrestore(rcg->lock, flags);
+
+	return 0;
+}
+
+static u32 md_to_m(struct mn *mn, u32 md)
+{
+	md >>= mn->m_val_shift;
+	md &= BIT(mn->width) - 1;
+	return md;
+}
+
+static u32 ns_to_pre_div(struct pre_div *p, u32 ns)
+{
+	ns >>= p->pre_div_shift;
+	ns &= BIT(p->pre_div_width) - 1;
+	return ns;
+}
+
+static u32 pre_div_to_ns(struct pre_div *p, u8 pre_div, u32 ns)
+{
+	u32 mask;
+
+	mask = BIT(p->pre_div_width) - 1;
+	mask <<= p->pre_div_shift;
+	ns &= ~mask;
+
+	ns |= pre_div << p->pre_div_shift;
+	return ns;
+}
+
+static u32 mn_to_md(struct mn *mn, u32 m, u32 n, u32 md)
+{
+	u32 mask, mask_w;
+
+	mask_w = BIT(mn->width) - 1;
+	mask = (mask_w << mn->m_val_shift) | mask_w;
+	md &= ~mask;
+
+	if (n) {
+		m <<= mn->m_val_shift;
+		md |= m;
+		md |= ~n & mask_w;
+	}
+
+	return md;
+}
+
+static u32 ns_m_to_n(struct mn *mn, u32 ns, u32 m)
+{
+	ns = ~ns >> mn->n_val_shift;
+	ns &= BIT(mn->width) - 1;
+	return ns + m;
+}
+
+static u32 reg_to_mnctr_mode(struct mn *mn, u32 val)
+{
+	val >>= mn->mnctr_mode_shift;
+	val &= MNCTR_MODE_MASK;
+	return val;
+}
+
+static u32 mn_to_ns(struct mn *mn, u32 m, u32 n, u32 ns)
+{
+	u32 mask;
+
+	mask = BIT(mn->width) - 1;
+	mask <<= mn->n_val_shift;
+	ns &= ~mask;
+
+	if (n) {
+		n = n - m;
+		n = ~n;
+		n &= BIT(mn->width) - 1;
+		n <<= mn->n_val_shift;
+		ns |= n;
+	}
+
+	return ns;
+}
+
+static u32 mn_to_reg(struct mn *mn, u32 m, u32 n, u32 val)
+{
+	u32 mask;
+
+	mask = MNCTR_MODE_MASK << mn->mnctr_mode_shift;
+	mask |= BIT(mn->mnctr_en_bit);
+	val &= ~mask;
+
+	if (n) {
+		val |= BIT(mn->mnctr_en_bit);
+		val |= MNCTR_MODE_DUAL << mn->mnctr_mode_shift;
+	}
+
+	return val;
+}
+
+static void configure_bank(struct clk_dyn_rcg *rcg, const struct freq_tbl *f)
+{
+	unsigned long flags;
+	u32 ns, md, ctl, *regp;
+	int bank, new_bank;
+	struct mn *mn;
+	struct pre_div *p;
+	struct src_sel *s;
+	bool enabled;
+	void __iomem *md_reg;
+	void __iomem *bank_reg;
+	bool banked_mn = !!rcg->md_reg[0];
+
+	spin_lock_irqsave(rcg->lock, flags);
+
+	enabled = __clk_is_enabled(rcg->hw.clk);
+
+	ns = readl_relaxed(rcg->ns_reg);
+	ctl = readl_relaxed(rcg->ctl_reg);
+
+	if (banked_mn) {
+		regp = &ctl;
+		bank_reg = rcg->ctl_reg;
+	} else {
+		regp = &ns;
+		bank_reg = rcg->ns_reg;
+	}
+
+	bank = reg_to_bank(rcg, *regp);
+	new_bank = enabled ? !bank : bank;
+
+	if (banked_mn) {
+		mn = &rcg->mn[new_bank];
+		md_reg = rcg->md_reg[new_bank];
+
+		ns |= BIT(mn->mnctr_reset_bit);
+		writel_relaxed(ns, rcg->ns_reg);
+
+		md = readl_relaxed(md_reg);
+		md = mn_to_md(mn, f->m, f->n, md);
+		writel_relaxed(md, md_reg);
+
+		ns = mn_to_ns(mn, f->m, f->n, ns);
+		writel_relaxed(ns, rcg->ns_reg);
+
+		ctl = mn_to_reg(mn, f->m, f->n, ctl);
+		writel_relaxed(ctl, rcg->ctl_reg);
+
+		ns &= ~BIT(mn->mnctr_reset_bit);
+		writel_relaxed(ns, rcg->ns_reg);
+	} else {
+		p = &rcg->p[new_bank];
+		ns = pre_div_to_ns(p, f->pre_div - 1, ns);
+	}
+
+	s = &rcg->s[new_bank];
+	ns = src_to_ns(s, s->parent_map[f->src], ns);
+	writel_relaxed(ns, rcg->ns_reg);
+
+	if (enabled) {
+		*regp ^= BIT(rcg->mux_sel_bit);
+		writel_relaxed(*regp, bank_reg);
+	}
+
+	/* Ensure parent switch is completed */
+	mb();
+	spin_unlock_irqrestore(rcg->lock, flags);
+}
+
+static int clk_dyn_rcg_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+	u32 ns, ctl, md, reg;
+	int bank;
+	struct freq_tbl f = { 0 };
+	bool banked_mn = !!rcg->md_reg[0];
+
+	ctl = readl_relaxed(rcg->ctl_reg);
+	ns = readl_relaxed(rcg->ns_reg);
+	reg = banked_mn ? ctl : ns;
+
+	bank = reg_to_bank(rcg, reg);
+
+	if (banked_mn) {
+		md = readl_relaxed(rcg->md_reg[bank]);
+		f.m = md_to_m(&rcg->mn[bank], md);
+		f.n = ns_m_to_n(&rcg->mn[bank], ns, f.m);
+	} else {
+		f.pre_div = ns_to_pre_div(&rcg->p[bank], ns) + 1;
+	}
+	f.src = index;
+
+	configure_bank(rcg, &f);
+
+	return 0;
+}
+
+/*
+ * Calculate m/n:d rate
+ *
+ *          parent_rate     m
+ *   rate = ----------- x  ---
+ *            pre_div       n
+ */
+static unsigned long
+calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 pre_div)
+{
+	if (pre_div)
+		rate /= pre_div + 1;
+
+	if (mode) {
+		u64 tmp = rate;
+		tmp *= m;
+		do_div(tmp, n);
+		rate = tmp;
+	}
+
+	return rate;
+}
+
+static unsigned long
+clk_rcg_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+	u32 pre_div, m, n, ns, md, mode;
+	struct mn *mn = &rcg->mn;
+
+	ns = readl_relaxed(rcg->ns_reg);
+	md = readl_relaxed(rcg->md_reg);
+
+	pre_div = ns_to_pre_div(&rcg->p, ns);
+	m = md_to_m(mn, md);
+	n = ns_m_to_n(mn, ns, m);
+	/* MN counter mode is in ctl_reg sometimes */
+	if (rcg->ctl_reg != rcg->ns_reg)
+		mode = readl_relaxed(rcg->ctl_reg);
+	else
+		mode = ns;
+	mode = reg_to_mnctr_mode(mn, mode);
+
+	return calc_rate(parent_rate, m, n, mode, pre_div);
+}
+
+static unsigned long
+clk_dyn_rcg_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+	u32 m, n, pre_div, ns, md, mode, reg;
+	int bank;
+	struct mn *mn;
+	bool banked_mn = !!rcg->md_reg[0];
+
+	ns = readl_relaxed(rcg->ns_reg);
+
+	if (banked_mn)
+		reg = readl_relaxed(rcg->ctl_reg);
+	else
+		reg = ns;
+
+	bank = reg_to_bank(rcg, reg);
+
+	if (banked_mn) {
+		mn = &rcg->mn[bank];
+		md = readl_relaxed(rcg->md_reg[bank]);
+		m = md_to_m(mn, md);
+		n = ns_m_to_n(mn, ns, m);
+		mode = reg_to_mnctr_mode(mn, reg);
+		return calc_rate(parent_rate, m, n, mode, 0);
+	} else {
+		pre_div = ns_to_pre_div(&rcg->p[bank], ns);
+		return calc_rate(parent_rate, 0, 0, 0, pre_div);
+	}
+}
+
+static const
+struct freq_tbl *find_freq(const struct freq_tbl *f, unsigned long rate)
+{
+	for (; f->freq; f++)
+		if (rate <= f->freq)
+			return f;
+
+	return NULL;
+}
+
+static long _freq_tbl_determine_rate(struct clk_hw *hw,
+		const struct freq_tbl *f, unsigned long rate,
+		unsigned long *p_rate, struct clk **p)
+{
+	f = find_freq(f, rate);
+	if (!f)
+		return -EINVAL;
+
+	*p = clk_get_parent_by_index(hw->clk, f->src);
+	*p_rate =  __clk_get_rate(*p);
+
+	return f->freq;
+}
+
+static long clk_rcg_determine_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long *p_rate, struct clk **p)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+
+	return _freq_tbl_determine_rate(hw, rcg->freq_tbl, rate, p_rate, p);
+}
+
+static long clk_dyn_rcg_determine_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long *p_rate, struct clk **p)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+
+	return _freq_tbl_determine_rate(hw, rcg->freq_tbl, rate, p_rate, p);
+}
+
+static int clk_rcg_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	struct clk_rcg *rcg = to_clk_rcg(hw);
+	unsigned long flags;
+	const struct freq_tbl *f;
+	u32 ns, md, ctl;
+	struct mn *mn = &rcg->mn;
+
+	f = find_freq(rcg->freq_tbl, rate);
+	if (!f)
+		return -EINVAL;
+
+	spin_lock_irqsave(rcg->lock, flags);
+
+	ns = readl_relaxed(rcg->ns_reg);
+	ns |= BIT(mn->mnctr_reset_bit);
+	writel_relaxed(ns, rcg->ns_reg);
+
+	md = readl_relaxed(rcg->md_reg);
+	md = mn_to_md(mn, f->m, f->n, md);
+	writel_relaxed(md, rcg->md_reg);
+
+	/* MN counter mode is in ctl_reg sometimes */
+	if (rcg->ctl_reg != rcg->ns_reg) {
+		ctl = readl_relaxed(rcg->ctl_reg);
+		ctl = mn_to_reg(mn, f->m, f->n, ctl);
+		writel_relaxed(ctl, rcg->ctl_reg);
+	} else {
+		ns = mn_to_reg(mn, f->m, f->n, ns);
+	}
+	ns = mn_to_ns(mn, f->m, f->n, ns);
+	ns = pre_div_to_ns(&rcg->p, f->pre_div - 1, ns);
+	writel_relaxed(ns, rcg->ns_reg);
+
+	ns &= ~BIT(mn->mnctr_reset_bit);
+	writel(ns, rcg->ns_reg);
+
+	spin_unlock_irqrestore(rcg->lock, flags);
+
+	return 0;
+}
+
+static int __clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate)
+{
+	struct clk_dyn_rcg *rcg = to_clk_dyn_rcg(hw);
+	const struct freq_tbl *f;
+
+	f = find_freq(rcg->freq_tbl, rate);
+	if (!f)
+		return -EINVAL;
+
+	configure_bank(rcg, f);
+
+	return 0;
+}
+
+static int clk_dyn_rcg_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	return __clk_dyn_rcg_set_rate(hw, rate);
+}
+
+static int clk_dyn_rcg_set_rate_and_parent(struct clk_hw *hw,
+		unsigned long rate, unsigned long parent_rate, u8 index)
+{
+	return __clk_dyn_rcg_set_rate(hw, rate);
+}
+
+static const struct clk_ops clk_rcg_ops = {
+	.enable = clk_rcg_enable,
+	.disable = clk_rcg_disable,
+	.get_parent = clk_rcg_get_parent,
+	.set_parent = clk_rcg_set_parent,
+	.recalc_rate = clk_rcg_recalc_rate,
+	.determine_rate = clk_rcg_determine_rate,
+	.set_rate = clk_rcg_set_rate,
+};
+
+struct clk *rcg_clk_register(struct device *dev, struct rcg_desc *desc,
+		spinlock_t *lock, struct clk_init_data *init,
+		u8 pre_div_width, u8 mnd_width)
+{
+	struct clk_rcg *r;
+
+	r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+	if (!r)
+		return ERR_PTR(-ENOMEM);
+
+	r->ctl_reg = desc->base + desc->ctl_reg;
+	r->ns_reg = desc->base + desc->ns_reg;
+	r->md_reg = desc->base + desc->md_reg;
+	r->ctl_bit = desc->ctl_bit;
+	r->mn.mnctr_en_bit = desc->mnctr_en_bit;
+	r->mn.mnctr_reset_bit = desc->mnctr_reset_bit;
+	r->mn.mnctr_mode_shift = desc->mnctr_mode_shift;
+	r->mn.n_val_shift = desc->n_val_shift;
+	r->mn.m_val_shift = desc->m_val_shift;
+	r->p.pre_div_shift = desc->pre_div_shift;
+	r->p.pre_div_width = pre_div_width;
+	r->s.src_sel_shift = desc->src_sel_shift;
+	r->s.parent_map = desc->parent_map;
+	r->freq_tbl = desc->freq_tbl;
+	r->lock = lock;
+	r->mn.width = mnd_width;
+
+	init->ops = &clk_rcg_ops;
+	init->flags |= CLK_SET_RATE_GATE;
+	r->hw.init = init;
+
+	return devm_clk_register(dev, &r->hw);
+}
+
+static const struct clk_ops clk_dyn_rcg_ops = {
+	.enable = clk_dyn_rcg_enable,
+	.is_enabled = clk_dyn_rcg_is_enabled,
+	.disable = clk_dyn_rcg_disable,
+	.get_parent = clk_dyn_rcg_get_parent,
+	.set_parent = clk_dyn_rcg_set_parent,
+	.recalc_rate = clk_dyn_rcg_recalc_rate,
+	.determine_rate = clk_dyn_rcg_determine_rate,
+	.set_rate = clk_dyn_rcg_set_rate,
+	.set_rate_and_parent = clk_dyn_rcg_set_rate_and_parent,
+};
+
+struct clk *
+rcg_dyn_clk_register(struct device *dev, struct rcg_dyn_desc *desc,
+		spinlock_t *lock, struct clk_init_data *init, u8 pre_div_width,
+		u8 mnd_width)
+{
+	struct clk_dyn_rcg *r;
+
+	r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+	if (!r)
+		return ERR_PTR(-ENOMEM);
+
+	r->ctl_reg = desc->base + desc->ctl_reg;
+	r->ns_reg = desc->base + desc->ns_reg;
+	r->ctl_bit = desc->ctl_bit;
+	if (mnd_width) {
+		r->md_reg[0] = desc->base + desc->md0_reg;
+		r->md_reg[1] = desc->base + desc->md1_reg;
+		r->mn[0].mnctr_en_bit = desc->mnctr0_en_bit;
+		r->mn[0].mnctr_reset_bit = desc->mnctr0_reset_bit;
+		r->mn[0].mnctr_mode_shift = desc->mnctr0_mode_shift;
+		r->mn[0].n_val_shift = desc->n0_val_shift;
+		r->mn[0].m_val_shift = desc->m0_val_shift;
+		r->mn[0].width = mnd_width;
+		r->mn[1].mnctr_en_bit = desc->mnctr1_en_bit;
+		r->mn[1].mnctr_reset_bit = desc->mnctr1_reset_bit;
+		r->mn[1].mnctr_mode_shift = desc->mnctr1_mode_shift;
+		r->mn[1].n_val_shift = desc->n1_val_shift;
+		r->mn[1].m_val_shift = desc->m1_val_shift;
+		r->mn[1].width = mnd_width;
+	}
+	if (pre_div_width) {
+		r->p[0].pre_div_shift = desc->pre_div0_shift;
+		r->p[0].pre_div_width = pre_div_width;
+		r->p[1].pre_div_shift = desc->pre_div1_shift;
+		r->p[1].pre_div_width = pre_div_width;
+	}
+	r->s[0].src_sel_shift = desc->src0_sel_shift;
+	r->s[0].parent_map = desc->parent_map;
+	r->s[1].src_sel_shift = desc->src1_sel_shift;
+	r->s[1].parent_map = desc->parent_map;
+	r->mux_sel_bit = desc->mux_sel_bit;
+	r->freq_tbl = desc->freq_tbl;
+	r->lock = lock;
+
+	init->ops = &clk_dyn_rcg_ops;
+	r->hw.init = init;
+
+	return devm_clk_register(dev, &r->hw);
+}
diff --git a/drivers/clk/msm/clk-rcg.h b/drivers/clk/msm/clk-rcg.h
new file mode 100644
index 0000000..9cc572d
--- /dev/null
+++ b/drivers/clk/msm/clk-rcg.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#ifndef __MSM_CLK_RCG_H__
+#define __MSM_CLK_RCG_H__
+
+struct device;
+struct clk;
+struct clk_init_data;
+
+struct freq_tbl {
+	unsigned long freq;
+	u8 src;
+	u8 pre_div;
+	u16 m;
+	u16 n;
+};
+
+struct rcg_desc {
+	void __iomem *base;
+	u32 ctl_reg;
+	u32 ns_reg;
+	u32 md_reg;
+
+	u8 ctl_bit;
+	u8 mnctr_en_bit;
+	u8 mnctr_reset_bit;
+	u8 mnctr_mode_shift;
+	u8 pre_div_shift;
+	u8 src_sel_shift;
+	u8 n_val_shift;
+	u8 m_val_shift;
+
+	u8 *parent_map;
+	struct freq_tbl *freq_tbl;
+};
+
+extern struct clk *rcg_clk_register(struct device *dev, struct rcg_desc *desc,
+		spinlock_t *lock, struct clk_init_data *init,
+		u8 pre_div_width, u8 mnd_width);
+
+#define rcg_p2mn8_clk_register(dev, desc, lock, init) \
+	rcg_clk_register(dev, desc, lock, init, 2, 8)
+#define rcg_p2mn16_clk_register(dev, desc, lock, init) \
+	rcg_clk_register(dev, desc, lock, init, 2, 16)
+
+struct rcg_dyn_desc {
+	void __iomem *base;
+	u32 ctl_reg;
+	u32 ns_reg;
+	u32 md0_reg;
+	u32 md1_reg;
+
+	u8 ctl_bit;
+	u8 mnctr0_en_bit;
+	u8 mnctr1_en_bit;
+	u8 mnctr0_reset_bit;
+	u8 mnctr1_reset_bit;
+	u8 mnctr0_mode_shift;
+	u8 mnctr1_mode_shift;
+	u8 pre_div0_shift;
+	u8 pre_div1_shift;
+	u8 src0_sel_shift;
+	u8 src1_sel_shift;
+	u8 n0_val_shift;
+	u8 n1_val_shift;
+	u8 m0_val_shift;
+	u8 m1_val_shift;
+	u8 mux_sel_bit;
+
+	u8 *parent_map;
+	struct freq_tbl *freq_tbl;
+};
+
+struct clk *rcg_dyn_clk_register(struct device *dev,
+		struct rcg_dyn_desc *desc, spinlock_t *lock,
+		struct clk_init_data *init, u8 pre_div_width, u8 mnd_width);
+
+#define rcg_mn4_dyn_clk_register(dev, desc, lock, init) \
+	rcg_dyn_clk_register(dev, desc, lock, init, 0, 4)
+#define rcg_mn8_dyn_clk_register(dev, desc, lock, init) \
+	rcg_dyn_clk_register(dev, desc, lock, init, 0, 8)
+#define rcg_p4_dyn_clk_register(dev, desc, lock, init) \
+	rcg_dyn_clk_register(dev, desc, lock, init, 4, 0)
+
+struct rcg2_desc {
+	void __iomem *base;
+	u32 cmd_rcgr;
+	u8 *parent_map;
+	struct freq_tbl *freq_tbl;
+};
+
+extern struct clk *rcg2_clk_register(struct device *dev, struct rcg2_desc *desc,
+		spinlock_t *lock, struct clk_init_data *init,
+		u8 hid_width, u8 mnd_width);
+
+#define rcg_h5_clk_register(dev, desc, lock, init) \
+	rcg2_clk_register(dev, desc, lock, init, 5, 0)
+#define rcg_h5mn8_clk_register(dev, desc, lock, init) \
+	rcg2_clk_register(dev, desc, lock, init, 5, 8)
+#define rcg_h5mn16_clk_register(dev, desc, lock, init) \
+	rcg2_clk_register(dev, desc, lock, init, 5, 16)
+
+#endif
diff --git a/drivers/clk/msm/clk-rcg2.c b/drivers/clk/msm/clk-rcg2.c
new file mode 100644
index 0000000..b8daf87
--- /dev/null
+++ b/drivers/clk/msm/clk-rcg2.c
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/clk-provider.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+
+#include <asm/div64.h>
+
+#include "clk-rcg.h"
+
+#define CMD_REG			0x0
+#define CMD_UPDATE		BIT(0)
+#define CMD_ROOT_EN		BIT(1)
+#define CMD_DIRTY_CFG		BIT(4)
+#define CMD_DIRTY_N		BIT(5)
+#define CMD_DIRTY_M		BIT(6)
+#define CMD_DIRTY_D		BIT(7)
+#define CMD_ROOT_OFF		BIT(31)
+
+#define CFG_REG			0x4
+#define CFG_SRC_DIV_SHIFT	0
+#define CFG_SRC_SEL_SHIFT	8
+#define CFG_SRC_SEL_MASK	(0x7 << CFG_SRC_SEL_SHIFT)
+#define CFG_MODE_SHIFT		12
+#define CFG_MODE_MASK		(0x3 << CFG_MODE_SHIFT)
+#define CFG_MODE_DUAL_EDGE	(0x2 << CFG_MODE_SHIFT)
+
+#define M_REG			0x8
+#define N_REG			0xc
+#define D_REG			0x10
+
+/**
+ * struct clk_rcg2 - root clock generator
+ *
+ * @base: corresponds to *_CMD_RCGR
+ * @mnd_width: number of bits in m/n/d values
+ * @hid_width: number of bits in half integer divider
+ * @parent_map: map from software's parent index to hardware's src_sel field
+ * @freq_tbl: Frequency table
+ * @hw: handle between common and hardware-specific interfaces
+ * @lock: register lock
+ *
+ */
+struct clk_rcg2 {
+	void __iomem		*base;
+	u8			mnd_width;
+	u8			hid_width;
+	u8			*parent_map;
+	const struct freq_tbl	*freq_tbl;
+	struct clk_hw		hw;
+	spinlock_t		*lock;
+};
+
+#define to_clk_rcg2(_hw) container_of(_hw, struct clk_rcg2, hw)
+
+static int clk_rcg2_is_enabled(struct clk_hw *hw)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+	u32 cmd;
+
+	cmd = readl_relaxed(rcg->base + CMD_REG);
+	cmd &= CMD_ROOT_OFF;
+
+	return cmd ? 0 : 1;
+}
+
+static u8 clk_rcg2_get_parent(struct clk_hw *hw)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+	int num_parents = __clk_get_num_parents(hw->clk);
+	u32 cfg;
+	int i;
+
+	cfg = readl_relaxed(rcg->base + CFG_REG);
+	cfg &= CFG_SRC_SEL_MASK;
+	cfg >>= CFG_SRC_SEL_SHIFT;
+
+	for (i = 0; i < num_parents; i++)
+		if (cfg == rcg->parent_map[i])
+			return i;
+
+	return -EINVAL;
+}
+
+static void update_config(struct clk_rcg2 *rcg)
+{
+	int count;
+	u32 cmd;
+	const char *name = __clk_get_name(rcg->hw.clk);
+
+	cmd = readl_relaxed(rcg->base + CMD_REG);
+	cmd |= CMD_UPDATE;
+	writel_relaxed(cmd, rcg->base + CMD_REG);
+
+	/* Wait for update to take effect */
+	for (count = 500; count > 0; count--) {
+		if (!(readl_relaxed(rcg->base + CMD_REG) & CMD_UPDATE))
+			return;
+		udelay(1);
+	}
+
+	WARN(1, "%s: rcg didn't update its configuration.", name);
+}
+
+static int clk_rcg2_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+	unsigned long flags;
+	u32 cfg;
+
+	spin_lock_irqsave(rcg->lock, flags);
+
+	cfg = readl_relaxed(rcg->base + CFG_REG);
+	cfg &= ~CFG_SRC_SEL_MASK;
+	cfg |= rcg->parent_map[index] << CFG_SRC_SEL_SHIFT;
+	writel(cfg, rcg->base + CFG_REG);
+
+	update_config(rcg);
+
+	spin_unlock_irqrestore(rcg->lock, flags);
+
+	return 0;
+}
+
+/*
+ * Calculate m/n:d rate
+ *
+ *          parent_rate     m
+ *   rate = ----------- x  ---
+ *            hid_div       n
+ */
+static unsigned long
+calc_rate(unsigned long rate, u32 m, u32 n, u32 mode, u32 hid_div)
+{
+	if (hid_div) {
+		rate *= 2;
+		rate /= hid_div + 1;
+	}
+
+	if (mode) {
+		u64 tmp = rate;
+		tmp *= m;
+		do_div(tmp, n);
+		rate = tmp;
+	}
+
+	return rate;
+}
+
+static unsigned long
+clk_rcg2_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+	u32 cfg, hid_div, m = 0, n = 0, mode = 0, mask;
+
+	cfg = readl_relaxed(rcg->base + CFG_REG);
+
+	if (rcg->mnd_width) {
+		mask = BIT(rcg->mnd_width) - 1;
+		m = readl_relaxed(rcg->base + M_REG);
+		m &= mask;
+		n = readl_relaxed(rcg->base + N_REG);
+		n =  ~n;
+		n &= mask;
+		n += m;
+		mode = cfg & CFG_MODE_MASK;
+		mode >>= CFG_MODE_SHIFT;
+	}
+
+	mask = BIT(rcg->hid_width) - 1;
+	hid_div = cfg >> CFG_SRC_DIV_SHIFT;
+	hid_div &= mask;
+
+	return calc_rate(parent_rate, m, n, mode, hid_div);
+}
+
+static const
+struct freq_tbl *find_freq(const struct freq_tbl *f, unsigned long rate)
+{
+	for (; f->freq; f++)
+		if (rate <= f->freq)
+			return f;
+
+	return NULL;
+}
+
+static long _freq_tbl_determine_rate(struct clk_hw *hw,
+		const struct freq_tbl *f, unsigned long rate,
+		unsigned long *p_rate, struct clk **p)
+{
+	f = find_freq(f, rate);
+	if (!f)
+		return -EINVAL;
+
+	*p = clk_get_parent_by_index(hw->clk, f->src);
+	*p_rate =  __clk_get_rate(*p);
+
+	return f->freq;
+}
+
+static long clk_rcg2_determine_rate(struct clk_hw *hw, unsigned long rate,
+		unsigned long *p_rate, struct clk **p)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+
+	return _freq_tbl_determine_rate(hw, rcg->freq_tbl, rate, p_rate, p);
+}
+
+static int __clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate)
+{
+	struct clk_rcg2 *rcg = to_clk_rcg2(hw);
+	unsigned long flags;
+	const struct freq_tbl *f;
+	u32 cfg, m, n, d, mask, val;
+
+	f = find_freq(rcg->freq_tbl, rate);
+	if (!f)
+		return -EINVAL;
+
+	spin_lock_irqsave(rcg->lock, flags);
+
+	cfg = readl_relaxed(rcg->base + CFG_REG);
+	mask = BIT(rcg->hid_width) - 1;
+	mask |= CFG_SRC_SEL_MASK | CFG_MODE_MASK;
+	cfg &= ~mask;
+
+	if (rcg->mnd_width && f->n) {
+		mask = BIT(rcg->mnd_width) - 1;
+		m = readl_relaxed(rcg->base + M_REG);
+		m &= ~mask;
+		m |= f->m;
+		writel_relaxed(m, rcg->base + M_REG);
+
+		val = readl_relaxed(rcg->base + M_REG);
+		val &= ~mask;
+		n = f->n - f->m;
+		n = ~n;
+		n &= mask;
+		val |= n;
+		writel_relaxed(val, rcg->base + N_REG);
+
+		d = readl_relaxed(rcg->base + D_REG);
+		d &= ~mask;
+		d |= ~f->n & mask;
+		writel_relaxed(d, rcg->base + D_REG);
+
+		cfg |= CFG_MODE_DUAL_EDGE;
+	}
+
+	cfg |= f->pre_div << CFG_SRC_DIV_SHIFT;
+	cfg |= rcg->parent_map[f->src] << CFG_SRC_SEL_SHIFT;
+	writel_relaxed(cfg, rcg->base + CFG_REG);
+
+	update_config(rcg);
+
+	spin_unlock_irqrestore(rcg->lock, flags);
+
+	return 0;
+}
+
+static int clk_rcg2_set_rate(struct clk_hw *hw, unsigned long rate,
+			    unsigned long parent_rate)
+{
+	return __clk_rcg2_set_rate(hw, rate);
+}
+
+static int clk_rcg2_set_rate_and_parent(struct clk_hw *hw,
+		unsigned long rate, unsigned long parent_rate, u8 index)
+{
+	return __clk_rcg2_set_rate(hw, rate);
+}
+
+static const struct clk_ops clk_rcg2_ops = {
+	.is_enabled = clk_rcg2_is_enabled,
+	.get_parent = clk_rcg2_get_parent,
+	.set_parent = clk_rcg2_set_parent,
+	.recalc_rate = clk_rcg2_recalc_rate,
+	.determine_rate = clk_rcg2_determine_rate,
+	.set_rate = clk_rcg2_set_rate,
+	.set_rate_and_parent = clk_rcg2_set_rate_and_parent,
+};
+
+struct clk *rcg2_clk_register(struct device *dev, struct rcg2_desc *desc,
+		spinlock_t *lock, struct clk_init_data *init,
+		u8 hid_width, u8 mnd_width)
+{
+	struct clk_rcg2 *r;
+
+	r = devm_kzalloc(dev, sizeof(*r), GFP_KERNEL);
+	if (!r)
+		return ERR_PTR(-ENOMEM);
+
+	r->base = desc->base + desc->cmd_rcgr;
+	r->parent_map = desc->parent_map;
+	r->freq_tbl = desc->freq_tbl;
+	r->lock = lock;
+	r->mnd_width = mnd_width;
+	r->hid_width = hid_width;
+
+	init->ops = &clk_rcg2_ops;
+	r->hw.init = init;
+
+	return devm_clk_register(dev, &r->hw);
+}
-- 
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
hosted by The Linux Foundation




More information about the linux-arm-kernel mailing list