diff mbox

[3/7] clk: sunxi: unify sun6i AHB1 clock with proper PLL6 pre-divider

Message ID 1410000448-9999-4-git-send-email-wens@csie.org
State Superseded, archived
Headers show

Commit Message

Chen-Yu Tsai Sept. 6, 2014, 10:47 a.m. UTC
This patch unifies the sun6i AHB1 clock, originally supported
with separate mux and divider clks. It also adds support for
the pre-divider on the PLL6 input, thus allowing the clock to
be muxed to PLL6 with proper clock rate calculation.

Signed-off-by: Chen-Yu Tsai <wens@csie.org>
---
 Documentation/devicetree/bindings/clock/sunxi.txt |   2 +-
 drivers/clk/sunxi/clk-sunxi.c                     | 209 ++++++++++++++++++++++
 2 files changed, 210 insertions(+), 1 deletion(-)

Comments

Maxime Ripard Sept. 11, 2014, 9:02 p.m. UTC | #1
Hi,

On Sat, Sep 06, 2014 at 06:47:24PM +0800, Chen-Yu Tsai wrote:
> This patch unifies the sun6i AHB1 clock, originally supported
> with separate mux and divider clks. It also adds support for
> the pre-divider on the PLL6 input, thus allowing the clock to
> be muxed to PLL6 with proper clock rate calculation.
> 
> Signed-off-by: Chen-Yu Tsai <wens@csie.org>

It looks fine, but I'd rather see this in a separate file, especially
since we don't seem to have any order dependency.

Thanks!
Maxime
Chen-Yu Tsai Sept. 12, 2014, 3:16 a.m. UTC | #2
Hi,

On Fri, Sep 12, 2014 at 5:02 AM, Maxime Ripard
<maxime.ripard@free-electrons.com> wrote:
> Hi,
>
> On Sat, Sep 06, 2014 at 06:47:24PM +0800, Chen-Yu Tsai wrote:
>> This patch unifies the sun6i AHB1 clock, originally supported
>> with separate mux and divider clks. It also adds support for
>> the pre-divider on the PLL6 input, thus allowing the clock to
>> be muxed to PLL6 with proper clock rate calculation.
>>
>> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
>
> It looks fine, but I'd rather see this in a separate file, especially
> since we don't seem to have any order dependency.

Sorry, just to be clear, separate file under clk/sunxi?

This cannot be in a separate file, as it shares a spinlock with apb1
divider. They share the same register.

We could move apb1 out though. But i would prefer to do that when
we split out all the clocks into individual OF_CLK_DECLAREs.

ChenYu
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maxime Ripard Sept. 13, 2014, 10:26 a.m. UTC | #3
On Fri, Sep 12, 2014 at 11:16:26AM +0800, Chen-Yu Tsai wrote:
> Hi,
> 
> On Fri, Sep 12, 2014 at 5:02 AM, Maxime Ripard
> <maxime.ripard@free-electrons.com> wrote:
> > Hi,
> >
> > On Sat, Sep 06, 2014 at 06:47:24PM +0800, Chen-Yu Tsai wrote:
> >> This patch unifies the sun6i AHB1 clock, originally supported
> >> with separate mux and divider clks. It also adds support for
> >> the pre-divider on the PLL6 input, thus allowing the clock to
> >> be muxed to PLL6 with proper clock rate calculation.
> >>
> >> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> >
> > It looks fine, but I'd rather see this in a separate file, especially
> > since we don't seem to have any order dependency.
> 
> Sorry, just to be clear, separate file under clk/sunxi?

Yes

> This cannot be in a separate file, as it shares a spinlock with apb1
> divider. They share the same register.
> 
> We could move apb1 out though. But i would prefer to do that when
> we split out all the clocks into individual OF_CLK_DECLAREs.

Ah right, my bad :)

My plan on the long term is to kill clk-sunxi as a place where all the
clocks are defined, and only leave the "policy" there, for example the
clock protection code (even if that should probably be removed too,
together with clkdev), the various rates / parenting enforcements,
etc.

Maxime
Mike Turquette Sept. 25, 2014, 11:03 p.m. UTC | #4
Quoting Maxime Ripard (2014-09-13 03:26:03)
> On Fri, Sep 12, 2014 at 11:16:26AM +0800, Chen-Yu Tsai wrote:
> > Hi,
> > 
> > On Fri, Sep 12, 2014 at 5:02 AM, Maxime Ripard
> > <maxime.ripard@free-electrons.com> wrote:
> > > Hi,
> > >
> > > On Sat, Sep 06, 2014 at 06:47:24PM +0800, Chen-Yu Tsai wrote:
> > >> This patch unifies the sun6i AHB1 clock, originally supported
> > >> with separate mux and divider clks. It also adds support for
> > >> the pre-divider on the PLL6 input, thus allowing the clock to
> > >> be muxed to PLL6 with proper clock rate calculation.
> > >>
> > >> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> > >
> > > It looks fine, but I'd rather see this in a separate file, especially
> > > since we don't seem to have any order dependency.
> > 
> > Sorry, just to be clear, separate file under clk/sunxi?
> 
> Yes
> 
> > This cannot be in a separate file, as it shares a spinlock with apb1
> > divider. They share the same register.
> > 
> > We could move apb1 out though. But i would prefer to do that when
> > we split out all the clocks into individual OF_CLK_DECLAREs.
> 
> Ah right, my bad :)
> 
> My plan on the long term is to kill clk-sunxi as a place where all the
> clocks are defined, and only leave the "policy" there, for example the
> clock protection code (even if that should probably be removed too,
> together with clkdev), the various rates / parenting enforcements,
> etc.

Interesting! Where are you planning to store the clock data?

Regards,
Mike

> 
> Maxime
> 
> -- 
> Maxime Ripard, Free Electrons
> Embedded Linux, Kernel and Android engineering
> http://free-electrons.com
--
To unsubscribe from this list: send the line "unsubscribe devicetree" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Maxime Ripard Sept. 26, 2014, 8:28 a.m. UTC | #5
On Thu, Sep 25, 2014 at 04:03:40PM -0700, Mike Turquette wrote:
> Quoting Maxime Ripard (2014-09-13 03:26:03)
> > On Fri, Sep 12, 2014 at 11:16:26AM +0800, Chen-Yu Tsai wrote:
> > > Hi,
> > > 
> > > On Fri, Sep 12, 2014 at 5:02 AM, Maxime Ripard
> > > <maxime.ripard@free-electrons.com> wrote:
> > > > Hi,
> > > >
> > > > On Sat, Sep 06, 2014 at 06:47:24PM +0800, Chen-Yu Tsai wrote:
> > > >> This patch unifies the sun6i AHB1 clock, originally supported
> > > >> with separate mux and divider clks. It also adds support for
> > > >> the pre-divider on the PLL6 input, thus allowing the clock to
> > > >> be muxed to PLL6 with proper clock rate calculation.
> > > >>
> > > >> Signed-off-by: Chen-Yu Tsai <wens@csie.org>
> > > >
> > > > It looks fine, but I'd rather see this in a separate file, especially
> > > > since we don't seem to have any order dependency.
> > > 
> > > Sorry, just to be clear, separate file under clk/sunxi?
> > 
> > Yes
> > 
> > > This cannot be in a separate file, as it shares a spinlock with apb1
> > > divider. They share the same register.
> > > 
> > > We could move apb1 out though. But i would prefer to do that when
> > > we split out all the clocks into individual OF_CLK_DECLAREs.
> > 
> > Ah right, my bad :)
> > 
> > My plan on the long term is to kill clk-sunxi as a place where all the
> > clocks are defined, and only leave the "policy" there, for example the
> > clock protection code (even if that should probably be removed too,
> > together with clkdev), the various rates / parenting enforcements,
> > etc.
> 
> Interesting! Where are you planning to store the clock data?

Which data? 

I guess, for the rate boundaries, the DT would be the right place,
wether a clock should be protected can be derived from its
compatible. And for the rate to enforce, maybe a clock-frequency
property in the DT too, or directly in the driver, I haven't really
thought about that part at the moment to be honest :)

Maxime
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/clock/sunxi.txt b/Documentation/devicetree/bindings/clock/sunxi.txt
index d3a5c3c..3d531d6 100644
--- a/Documentation/devicetree/bindings/clock/sunxi.txt
+++ b/Documentation/devicetree/bindings/clock/sunxi.txt
@@ -23,7 +23,7 @@  Required properties:
 	"allwinner,sun5i-a10s-ahb-gates-clk" - for the AHB gates on A10s
 	"allwinner,sun7i-a20-ahb-gates-clk" - for the AHB gates on A20
 	"allwinner,sun6i-a31-ar100-clk" - for the AR100 on A31
-	"allwinner,sun6i-a31-ahb1-mux-clk" - for the AHB1 multiplexer on A31
+	"allwinner,sun6i-a31-ahb1-clk" - for the AHB1 clock on A31
 	"allwinner,sun6i-a31-ahb1-gates-clk" - for the AHB1 gates on A31
 	"allwinner,sun8i-a23-ahb1-gates-clk" - for the AHB1 gates on A23
 	"allwinner,sun4i-a10-apb0-clk" - for the APB0 clock
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index be9ac07..87b7740 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -19,6 +19,7 @@ 
 #include <linux/of.h>
 #include <linux/of_address.h>
 #include <linux/reset-controller.h>
+#include <linux/log2.h>
 
 #include "clk-factors.h"
 
@@ -1339,3 +1340,211 @@  static void __init sun6i_init_clocks(struct device_node *node)
 }
 CLK_OF_DECLARE(sun6i_a31_clk_init, "allwinner,sun6i-a31", sun6i_init_clocks);
 CLK_OF_DECLARE(sun8i_a23_clk_init, "allwinner,sun8i-a23", sun6i_init_clocks);
+
+
+/**
+ * sun6i_a31_ahb1_clk_setup() - Setup function for a31 ahb1 composite clk
+ */
+
+#define SUN6I_AHB1_MAX_PARENTS		4
+#define SUN6I_AHB1_MUX_PARENT_PLL6	3
+#define SUN6I_AHB1_MUX_SHIFT		12
+#define SUN6I_AHB1_MUX_MASK		0x3
+#define SUN6I_AHB1_MUX_GET_PARENT(reg)	((reg >> SUN6I_AHB1_MUX_SHIFT) & \
+					 SUN6I_AHB1_MUX_MASK)
+#define SUN6I_AHB1_DIV_SHIFT		4
+#define SUN6I_AHB1_DIV_MASK		0x3
+#define SUN6I_AHB1_DIV_GET(reg)		((reg >> SUN6I_AHB1_DIV_SHIFT) & \
+					 SUN6I_AHB1_DIV_MASK)
+#define SUN6I_AHB1_DIV_SET(reg, div)	((reg & ~(SUN6I_AHB1_DIV_MASK << \
+						  SUN6I_AHB1_DIV_SHIFT)) | \
+					 (div << SUN6I_AHB1_DIV_SHIFT))
+#define SUN6I_AHB1_PLL6_DIV_SHIFT	6
+#define SUN6I_AHB1_PLL6_DIV_MASK	0x3
+#define SUN6I_AHB1_PLL6_DIV_GET(reg)	((reg >> SUN6I_AHB1_PLL6_DIV_SHIFT) & \
+					 SUN6I_AHB1_PLL6_DIV_MASK)
+#define SUN6I_AHB1_PLL6_DIV_SET(reg, div) ((reg & \
+					    ~(SUN6I_AHB1_PLL6_DIV_MASK << \
+					      SUN6I_AHB1_PLL6_DIV_SHIFT)) | \
+					   (div << SUN6I_AHB1_PLL6_DIV_SHIFT))
+
+struct sun6i_ahb1_clk {
+	struct clk_hw hw;
+	void __iomem *reg;
+};
+
+#define to_sun6i_ahb1_clk(_hw) container_of(_hw, struct sun6i_ahb1_clk, hw)
+
+static unsigned long sun6i_ahb1_clk_recalc_rate(struct clk_hw *hw,
+						unsigned long parent_rate)
+{
+	struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw);
+	unsigned long rate;
+	u32 reg;
+
+	/* Fetch the register value */
+	reg = readl(ahb1->reg);
+
+	/* apply pre-divider first if parent is pll6 */
+	if (SUN6I_AHB1_MUX_GET_PARENT(reg) == SUN6I_AHB1_MUX_PARENT_PLL6)
+		parent_rate /= SUN6I_AHB1_PLL6_DIV_GET(reg) + 1;
+
+	/* clk divider */
+	rate = parent_rate >> SUN6I_AHB1_DIV_GET(reg);
+
+	return rate;
+}
+
+static long sun6i_ahb1_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
+				 u8 parent, unsigned long parent_rate)
+{
+	u8 div, calcp, calcm = 1;
+
+	/* clock can only divide, so we will never be able to achieve
+	 * frequencies higher than the parent frequency */
+	if (parent_rate && rate > parent_rate)
+		rate = parent_rate;
+
+	div = DIV_ROUND_UP(parent_rate, rate);
+
+	/* calculate pre-divider if parent is pll6 */
+	if (parent == SUN6I_AHB1_MUX_PARENT_PLL6) {
+		if (div < 4)
+			calcp = 0;
+		else if (div / 2 < 4)
+			calcp = 1;
+		else if (div / 4 < 4)
+			calcp = 2;
+		else
+			calcp = 3;
+
+		calcm = DIV_ROUND_UP(div, 1 << calcp);
+	} else {
+		calcp = __roundup_pow_of_two(div);
+		calcp = calcp > 3 ? 3 : calcp;
+	}
+
+	if (divp) {
+		*divp = calcp;
+		*pre_divp = calcm - 1;
+	}
+
+	return (parent_rate / calcm) >> calcp;
+}
+
+static long sun6i_ahb1_clk_determine_rate(struct clk_hw *hw, unsigned long rate,
+					  unsigned long *best_parent_rate,
+					  struct clk **best_parent_clk)
+{
+	struct clk *clk = hw->clk, *parent, *best_parent = NULL;
+	int i, num_parents;
+	unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
+
+	/* find the parent that can help provide the fastest rate <= rate */
+	num_parents = __clk_get_num_parents(clk);
+	for (i = 0; i < num_parents; i++) {
+		parent = clk_get_parent_by_index(clk, i);
+		if (!parent)
+			continue;
+		if (__clk_get_flags(clk) & CLK_SET_RATE_PARENT)
+			parent_rate = __clk_round_rate(parent, rate);
+		else
+			parent_rate = __clk_get_rate(parent);
+
+		child_rate = sun6i_ahb1_clk_round(rate, NULL, NULL, i,
+						  parent_rate);
+
+		if (child_rate <= rate && child_rate > best_child_rate) {
+			best_parent = parent;
+			best = parent_rate;
+			best_child_rate = child_rate;
+		}
+	}
+
+	if (best_parent)
+		*best_parent_clk = best_parent;
+	*best_parent_rate = best;
+
+	return best_child_rate;
+}
+
+static int sun6i_ahb1_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				   unsigned long parent_rate)
+{
+	struct sun6i_ahb1_clk *ahb1 = to_sun6i_ahb1_clk(hw);
+	unsigned long flags;
+	u8 div, pre_div, parent;
+	u32 reg;
+
+	spin_lock_irqsave(&clk_lock, flags);
+
+	reg = readl(ahb1->reg);
+
+	/* need to know which parent is used to apply pre-divider */
+	parent = SUN6I_AHB1_MUX_GET_PARENT(reg);
+	sun6i_ahb1_clk_round(rate, &div, &pre_div, parent, parent_rate);
+
+	reg = SUN6I_AHB1_DIV_SET(reg, div);
+	reg = SUN6I_AHB1_PLL6_DIV_SET(reg, pre_div);
+	writel(reg, ahb1->reg);
+
+	spin_unlock_irqrestore(&clk_lock, flags);
+
+	return 0;
+}
+
+static const struct clk_ops sun6i_ahb1_clk_ops = {
+	.determine_rate	= sun6i_ahb1_clk_determine_rate,
+	.recalc_rate	= sun6i_ahb1_clk_recalc_rate,
+	.set_rate	= sun6i_ahb1_clk_set_rate,
+};
+
+static void __init sun6i_ahb1_clk_setup(struct device_node *node)
+{
+	struct clk *clk;
+	struct sun6i_ahb1_clk *ahb1;
+	struct clk_mux *mux;
+	const char *clk_name = node->name;
+	const char *parents[SUN6I_AHB1_MAX_PARENTS];
+	void __iomem *reg;
+	int i = 0;
+
+	reg = of_iomap(node, 0);
+
+	/* we have a mux, we will have >1 parents */
+	while (i < SUN6I_AHB1_MAX_PARENTS &&
+	       (parents[i] = of_clk_get_parent_name(node, i)) != NULL)
+		i++;
+
+	of_property_read_string(node, "clock-output-names", &clk_name);
+
+	ahb1 = kzalloc(sizeof(struct sun6i_ahb1_clk), GFP_KERNEL);
+	if (!ahb1)
+		return;
+
+	mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
+	if (!mux) {
+		kfree(ahb1);
+		return;
+	}
+
+	/* set up clock properties */
+	mux->reg = reg;
+	mux->shift = SUN6I_AHB1_MUX_SHIFT;
+	mux->mask = SUN6I_AHB1_MUX_MASK;
+	mux->lock = &clk_lock;
+	ahb1->reg = reg;
+
+	clk = clk_register_composite(NULL, clk_name, parents, i,
+				     &mux->hw, &clk_mux_ops,
+				     &ahb1->hw, &sun6i_ahb1_clk_ops,
+				     NULL, NULL, 0);
+
+	if (!IS_ERR(clk)) {
+		of_clk_add_provider(node, of_clk_src_simple_get, clk);
+		clk_register_clkdev(clk, clk_name, NULL);
+	}
+}
+
+CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-clk",
+		sun6i_ahb1_clk_setup);