All of lore.kernel.org
 help / color / mirror / Atom feed
From: emilio@elopez.com.ar (Emilio López)
To: linux-arm-kernel@lists.infradead.org
Subject: [RFC 1/8] clk: sunxi: PLL2 support for sun4i, sun5i and sun7i
Date: Mon, 28 Jul 2014 00:49:39 -0300	[thread overview]
Message-ID: <1406519386-4902-2-git-send-email-emilio@elopez.com.ar> (raw)
In-Reply-To: <1406519386-4902-1-git-send-email-emilio@elopez.com.ar>

This patch adds support for PLL2 and derivates on A10 revision B and
higher, as well as on sun5i and sun7i SoCs. As this PLL is only used for
audio and requires good accuracy, we only support two known good rates.

Signed-off-by: Emilio L?pez <emilio@elopez.com.ar>
---
 drivers/clk/sunxi/Makefile       |   1 +
 drivers/clk/sunxi/clk-a10-pll2.c | 249 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 250 insertions(+)
 create mode 100644 drivers/clk/sunxi/clk-a10-pll2.c

diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index 6850cba..dcd5709 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -4,6 +4,7 @@
 
 obj-y += clk-sunxi.o clk-factors.o
 obj-y += clk-a10-hosc.o
+obj-y += clk-a10-pll2.o
 obj-y += clk-a20-gmac.o
 
 obj-$(CONFIG_MFD_SUN6I_PRCM) += \
diff --git a/drivers/clk/sunxi/clk-a10-pll2.c b/drivers/clk/sunxi/clk-a10-pll2.c
new file mode 100644
index 0000000..bdbf1e9
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-pll2.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2013 Emilio L?pez
+ *
+ * Emilio L?pez <emilio@elopez.com.ar>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#define SUN4I_PLL2_ENABLE		31
+#define SUN4I_PLL2_POST_DIV		26
+#define SUN4I_PLL2_POST_DIV_MASK	0xF
+#define SUN4I_PLL2_N			8
+#define SUN4I_PLL2_N_MASK		0x7F
+#define SUN4I_PLL2_PRE_DIV		0
+#define SUN4I_PLL2_PRE_DIV_MASK		0x1F
+
+#define SUN4I_PLL2_OUTPUTS		4
+
+struct sun4i_pll2_clk {
+	struct clk_hw hw;
+	void __iomem *reg;
+};
+
+static inline struct sun4i_pll2_clk *to_sun4i_pll2_clk(struct clk_hw *hw)
+{
+	return container_of(hw, struct sun4i_pll2_clk, hw);
+}
+
+static unsigned long sun4i_pll2_1x_recalc_rate(struct clk_hw *hw,
+					    unsigned long parent_rate)
+{
+	struct sun4i_pll2_clk *clk = to_sun4i_pll2_clk(hw);
+	int n, prediv, postdiv;
+
+	u32 val = readl(clk->reg);
+	n = (val >> SUN4I_PLL2_N) & SUN4I_PLL2_N_MASK;
+	prediv = (val >> SUN4I_PLL2_PRE_DIV) & SUN4I_PLL2_PRE_DIV_MASK;
+	postdiv = (val >> SUN4I_PLL2_POST_DIV) & SUN4I_PLL2_POST_DIV_MASK;
+
+	/* 0 is a special case and means 1 */
+	if (n == 0)
+		n = 1;
+	if (prediv == 0)
+		prediv = 1;
+	if (postdiv == 0)
+		postdiv = 1;
+
+	return ((parent_rate * n) / prediv) / postdiv;
+}
+
+static unsigned long sun4i_pll2_8x_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	struct sun4i_pll2_clk *clk = to_sun4i_pll2_clk(hw);
+	int n, prediv;
+
+	u32 val = readl(clk->reg);
+	n = (val >> SUN4I_PLL2_N) & SUN4I_PLL2_N_MASK;
+	prediv = (val >> SUN4I_PLL2_PRE_DIV) & SUN4I_PLL2_PRE_DIV_MASK;
+
+	/* 0 is a special case and means 1 */
+	if (n == 0)
+		n = 1;
+	if (prediv == 0)
+		prediv = 1;
+
+	return ((parent_rate * 2 * n) / prediv);
+}
+
+static unsigned long sun4i_pll2_4x_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	return sun4i_pll2_8x_recalc_rate(hw, parent_rate / 2);
+}
+
+static unsigned long sun4i_pll2_2x_recalc_rate(struct clk_hw *hw,
+					       unsigned long parent_rate)
+{
+	return sun4i_pll2_8x_recalc_rate(hw, parent_rate / 4);
+}
+
+static long sun4i_pll2_1x_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	/*
+	 * There is only two interesting rates for the audio PLL, the
+	 * rest isn't really usable due to accuracy concerns. Therefore,
+	 * we specifically round to those rates here
+	 */
+	if (rate < 22579200)
+		return -EINVAL;
+
+	if (rate >= 22579200 && rate < 24576000)
+		return 22579200;
+
+	return 24576000;
+}
+
+static long sun4i_pll2_8x_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	/*
+	 * We should account for the postdiv that we're undoing on PLL2x8,
+	 * which is always 4 in the usable configurations. The division
+	 * by two is done because PLL2x8 also doubles the rate
+	 */
+	*parent_rate = (rate * 4) / 2;
+
+	return rate;
+}
+
+static long sun4i_pll2_4x_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	/* PLL2x4 * 2 = PLL2x8 */
+	return sun4i_pll2_8x_round_rate(hw, rate * 2, parent_rate);
+}
+
+static long sun4i_pll2_2x_round_rate(struct clk_hw *hw, unsigned long rate,
+				     unsigned long *parent_rate)
+{
+	/* PLL2x2 * 4 = PLL2x8 */
+	return sun4i_pll2_8x_round_rate(hw, rate * 4, parent_rate);
+}
+
+static int sun4i_pll2_set_rate(struct clk_hw *hw, unsigned long rate,
+			       unsigned long parent_rate)
+{
+	struct sun4i_pll2_clk *clk = to_sun4i_pll2_clk(hw);
+	u32 val = readl(clk->reg);
+
+	val &= ~(SUN4I_PLL2_N_MASK << SUN4I_PLL2_N);
+	val &= ~(SUN4I_PLL2_PRE_DIV_MASK << SUN4I_PLL2_PRE_DIV);
+	val &= ~(SUN4I_PLL2_POST_DIV_MASK << SUN4I_PLL2_POST_DIV);
+
+	val |= (21 << SUN4I_PLL2_PRE_DIV) | (4 << SUN4I_PLL2_POST_DIV);
+
+	if (rate == 22579200)
+		val |= (79 << SUN4I_PLL2_N);
+	else if (rate == 24576000)
+		val |= (86 << SUN4I_PLL2_N);
+	else
+		return -EINVAL;
+
+	writel(val, clk->reg);
+
+	return 0;
+}
+
+static struct clk_ops sun4i_pll2_ops_1x = {
+	.recalc_rate = sun4i_pll2_1x_recalc_rate,
+	.round_rate = sun4i_pll2_1x_round_rate,
+	.set_rate = sun4i_pll2_set_rate,
+};
+
+static struct clk_ops sun4i_pll2_ops_2x = {
+	.recalc_rate = sun4i_pll2_2x_recalc_rate,
+	.round_rate = sun4i_pll2_2x_round_rate,
+};
+
+static struct clk_ops sun4i_pll2_ops_4x = {
+	.recalc_rate = sun4i_pll2_4x_recalc_rate,
+	.round_rate = sun4i_pll2_4x_round_rate,
+};
+
+static struct clk_ops sun4i_pll2_ops_8x = {
+	.recalc_rate = sun4i_pll2_8x_recalc_rate,
+	.round_rate = sun4i_pll2_8x_round_rate,
+};
+
+static void __init sun4i_pll2_setup(struct device_node *np)
+{
+	const char *clk_name = np->name, *parent;
+	struct clk_onecell_data *clk_data;
+	struct sun4i_pll2_clk *pll2;
+	struct clk_gate *gate;
+	struct clk **clks;
+	void __iomem *reg;
+
+	pll2 = kzalloc(sizeof(*pll2), GFP_KERNEL);
+	gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+	clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+	clks = kcalloc(SUN4I_PLL2_OUTPUTS, sizeof(struct clk *), GFP_KERNEL);
+	if (!pll2 || !gate || !clk_data || !clks)
+		goto free_mem;
+
+	reg = of_iomap(np, 0);
+	parent = of_clk_get_parent_name(np, 0);
+	of_property_read_string_index(np, "clock-output-names", 0, &clk_name);
+
+	pll2->reg = reg;
+	gate->reg = reg;
+	gate->bit_idx = SUN4I_PLL2_ENABLE;
+
+	/* PLL2, also known as PLL2x1 */
+	of_property_read_string_index(np, "clock-output-names", 0, &clk_name);
+	clks[0] = clk_register_composite(NULL, clk_name, &parent, 1, NULL, NULL,
+					 &pll2->hw, &sun4i_pll2_ops_1x,
+					 &gate->hw, &clk_gate_ops, 0);
+	WARN_ON(IS_ERR(clks[0]));
+	parent = clk_name;
+
+	/* PLL2x2, 1/4 the rate of PLL2x8 */
+	of_property_read_string_index(np, "clock-output-names", 1, &clk_name);
+	clks[1] = clk_register_composite(NULL, clk_name, &parent, 1, NULL, NULL,
+					 &pll2->hw, &sun4i_pll2_ops_2x,
+					 NULL, NULL, CLK_SET_RATE_PARENT);
+	WARN_ON(IS_ERR(clks[1]));
+
+	/* PLL2x4, 1/2 the rate of PLL2x8 */
+	of_property_read_string_index(np, "clock-output-names", 2, &clk_name);
+	clks[2] = clk_register_composite(NULL, clk_name, &parent, 1, NULL, NULL,
+					 &pll2->hw, &sun4i_pll2_ops_4x,
+					 NULL, NULL, CLK_SET_RATE_PARENT);
+	WARN_ON(IS_ERR(clks[2]));
+
+	/* PLL2x8, double of PLL2 without the post divisor */
+	of_property_read_string_index(np, "clock-output-names", 3, &clk_name);
+	clks[3] = clk_register_composite(NULL, clk_name, &parent, 1, NULL, NULL,
+					 &pll2->hw, &sun4i_pll2_ops_8x,
+					 NULL, NULL, CLK_SET_RATE_PARENT);
+	WARN_ON(IS_ERR(clks[3]));
+
+	clk_data->clks = clks;
+	clk_data->clk_num = SUN4I_PLL2_OUTPUTS;
+	of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
+
+	return;
+
+free_mem:
+	kfree(pll2);
+	kfree(gate);
+	kfree(clk_data);
+	kfree(clks);
+}
+CLK_OF_DECLARE(sun4i_pll2, "allwinner,sun4i-a10-b-pll2-clk", sun4i_pll2_setup);
-- 
2.0.3

  reply	other threads:[~2014-07-28  3:49 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2014-07-28  3:49 [RFC 0/8] Audio clocks for sun[457]i, SoC revision detection Emilio López
2014-07-28  3:49 ` Emilio López [this message]
2014-07-28  3:49 ` [RFC 2/8] clk: sunxi: codec clock support Emilio López
2014-07-28 13:19   ` Maxime Ripard
2014-07-28  3:49 ` [RFC 3/8] clk: sunxi: mod1 " Emilio López
2014-07-28  3:49 ` [RFC 4/8] clk: sunxi: add __iomem markings to MMIO pointers Emilio López
2014-07-28 13:21   ` Maxime Ripard
2014-07-28 22:42     ` Mike Turquette
2014-07-28  3:49 ` [RFC 5/8] clk: sunxi: staticize structures and arrays Emilio López
2014-07-28 13:21   ` Maxime Ripard
2014-07-28  3:49 ` [RFC 6/8] ARM: sunxi: Add PLL2 support Emilio López
2014-07-28 13:22   ` Maxime Ripard
2014-07-28  3:49 ` [RFC 7/8] ARM: sunxi: Add codec clock support Emilio López
2014-07-28  3:49 ` [RFC 8/8] ARM: sun7i: Add mod1 clock nodes Emilio López
2014-07-28  4:28   ` Chen-Yu Tsai
2014-07-28 14:51     ` jonsmirl at gmail.com
2014-07-28 13:25   ` Maxime Ripard
2014-07-28 14:47     ` jonsmirl at gmail.com
2014-07-31 10:19       ` Maxime Ripard
2014-07-28 12:42 ` [RFC 0/8] Audio clocks for sun[457]i, SoC revision detection Maxime Ripard
2014-07-28 14:40   ` jonsmirl at gmail.com
2014-07-31 10:27     ` Maxime Ripard

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1406519386-4902-2-git-send-email-emilio@elopez.com.ar \
    --to=emilio@elopez.com.ar \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.