[U-Boot] [PATCH v1 06/11] sunxi: add clock driver (UCLASS_CLK) support for sunxi

Philipp Tomsich philipp.tomsich at theobroma-systems.com
Fri Feb 17 17:52:43 UTC 2017


When CONFIG_CLK is defined, we now provide support for the basic
clock configuration of peripherals on sunxi:
 * clk-sunxi-mod.c implements support for module clocks, which
     performs parent selection (determined via the device-tree)
     and determines/configures a pre-divider and divider when
     setting a clock-rate
 * clk-sunxi-pll.c implements read-only (i.e. getting the rate
     is implemented, but setting the rate is not) access to
     PLLs.  At this time, the necessary platform data (number
     of bits and position for the factors P, M, N and K) for
     the peripheral PLLs ('allwinner,sun6i-a31-pll6-clk') are
     included
 * clk-sunxi-gate.c: implements an clk-gate to gate individual
     modules (i.e. 'allwinner,sunxi-multi-bus-gates-clk')

Signed-off-by: Philipp Tomsich <philipp.tomsich at theobroma-systems.com>
---
 drivers/clk/Makefile               |   1 +
 drivers/clk/sunxi/Makefile         |   7 ++
 drivers/clk/sunxi/clk-sunxi-gate.c |  92 ++++++++++++++
 drivers/clk/sunxi/clk-sunxi-mod.c  | 241 +++++++++++++++++++++++++++++++++++++
 drivers/clk/sunxi/clk-sunxi-pll.c  | 123 +++++++++++++++++++
 5 files changed, 464 insertions(+)
 create mode 100644 drivers/clk/sunxi/Makefile
 create mode 100644 drivers/clk/sunxi/clk-sunxi-gate.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-mod.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-pll.c

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 884c21c..7ae8029 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -7,6 +7,7 @@
 
 obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o
 obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
+obj-$(CONFIG_ARCH_SUNXI) += sunxi/
 obj-$(CONFIG_SANDBOX) += clk_sandbox.o
 obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o
 obj-$(CONFIG_MACH_PIC32) += clk_pic32.o
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
new file mode 100644
index 0000000..e37768cdd
--- /dev/null
+++ b/drivers/clk/sunxi/Makefile
@@ -0,0 +1,7 @@
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+obj-y += clk-sunxi-pll.o
+obj-y += clk-sunxi-mod.o
+obj-y += clk-sunxi-gate.o
\ No newline at end of file
diff --git a/drivers/clk/sunxi/clk-sunxi-gate.c b/drivers/clk/sunxi/clk-sunxi-gate.c
new file mode 100644
index 0000000..956ba8e
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-gate.c
@@ -0,0 +1,92 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <wait_bit.h>
+#include <dm/lists.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_priv {
+	void   *base;
+	size_t  size;
+};
+
+static int sunxi_gate_update(struct clk *clk, bool enable)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	unsigned long id = clk->id;
+	uint32_t offset = id / 32;
+	uint32_t bit = id % 32;
+
+	debug("%s (%s): id %ld base %p offset %x bit %d enable %d\n",
+	      clk->dev->name, __func__, id, priv->base, offset,
+	      bit, enable);
+
+	if (enable)
+		setbits_le32(priv->base + offset, BIT(bit));
+	else
+		clrbits_le32(priv->base + offset, BIT(bit));
+
+	return -EINVAL;
+}
+
+static int sunxi_gate_enable(struct clk *clk)
+{
+	return sunxi_gate_update(clk, true);
+}
+
+static int sunxi_gate_disable(struct clk *clk)
+{
+	return sunxi_gate_update(clk, false);
+}
+
+static struct clk_ops sunxi_clk_gate_ops = {
+	.enable = sunxi_gate_enable,
+	.disable = sunxi_gate_disable,
+};
+
+static int sunxi_clk_gate_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+
+	debug("%s: %s\n", dev->name, __func__);
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: could not get addr\n", dev->name);
+		return -EINVAL;
+	}
+
+	priv->base = (void *)addr;
+	priv->size = size;
+
+	return 0;
+}
+
+static const struct udevice_id sunxi_clk_gate_ids[] = {
+	{ .compatible = "allwinner,sunxi-multi-bus-gates-clk" },
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_gate) = {
+	.name		= "sunxi_clk_gate",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_gate_ids,
+	.ops		= &sunxi_clk_gate_ops,
+	.probe		= sunxi_clk_gate_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
+
+
diff --git a/drivers/clk/sunxi/clk-sunxi-mod.c b/drivers/clk/sunxi/clk-sunxi-mod.c
new file mode 100644
index 0000000..4e70cc9
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-mod.c
@@ -0,0 +1,241 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * With sun4i_a10_get_mod0_factors(...) adapted from
+ *    linux/drivers/clk/sunxi/clk-mod0.c
+ * which is
+ *    Copyright 2013 Emilio López
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <wait_bit.h>
+#include <dm/lists.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_priv {
+	void         *reg;
+	int           num_parents;
+	struct clk    parent[4];
+};
+
+#define SRCSHIFT    (24)
+#define SRCMASK     (0x3 << SRCSHIFT)
+#define SRC(n)      (n << SRCSHIFT)
+#define PREDIVMASK  (0x3 << 16)
+#define PREDIV(n)   (n << 16)
+#define DIVMASK     (0xf << 0)
+#define DIV(n)      (n)
+
+static ulong sunxi_mod_get_rate(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	u32 active_parent;
+	ulong rate = -EINVAL;
+	u32 regval = readl(priv->reg);
+
+	/* if not enabled, return 0 */
+	if (regval & BIT(31))
+		return 0;
+
+	active_parent = (readl(priv->reg) >> 24) & 0x3;
+	if (active_parent < priv->num_parents)
+		rate = clk_get_rate(&priv->parent[active_parent]);
+
+	return rate;
+}
+
+/**
+ * sun4i_a10_get_mod0_factors()
+ *  - calculates m, n factors for MOD0-style clocks
+ *
+ * MOD0 rate is calculated as follows:
+ *    rate = (parent_rate >> p) / (m + 1);
+ */
+
+struct factors_request {
+	unsigned long rate;
+	unsigned long parent_rate;
+	u8 parent_index;
+	u8 n;
+	u8 k;
+	u8 m;
+	u8 p;
+};
+
+static void sun4i_a10_get_mod0_factors(struct factors_request *req)
+{
+	u8 div, calcm, calcp;
+
+	/* These clocks can only divide, so we will never be able to
+	 * achieve frequencies higher than the parent frequency */
+	if (req->rate >= req->parent_rate) {
+		req->rate = req->parent_rate;
+		req->m = 0;
+		req->p = 0;
+	}
+
+	div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+	if (div < 16)
+		calcp = 0;
+	else if (div / 2 < 16)
+		calcp = 1;
+	else if (div / 4 < 16)
+		calcp = 2;
+	else
+		calcp = 3;
+
+	calcm = DIV_ROUND_UP(div, 1 << calcp);
+	/* clamp calcm to 16, as that is the largest possible divider */
+	if (calcm > 16)
+		calcm = 16;
+
+	req->rate = (req->parent_rate >> calcp) / calcm;
+	req->m = calcm - 1;
+	req->p = calcp;
+}
+
+static ulong sunxi_mod_set_rate(struct clk *clk, ulong rate)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	ulong best_rate = 0;
+	int i;
+
+	debug("%s (%s): id %ld rate %ld base %p\n",
+	      clk->dev->name, __func__, clk->id, rate, priv->reg);
+
+	/* check if the current rate is already the target rate */
+	if (sunxi_mod_get_rate(clk) == rate)
+		return rate;
+
+	/* find the parent (iterate through) which allows us to have:
+	 *     fastest rate <= rate
+	 */
+	for (i = 0; i < priv->num_parents; ++i) {
+		ulong parent_rate = clk_get_rate(&priv->parent[i]);
+		struct factors_request  req = {
+			.rate = rate,
+			.parent_rate = parent_rate,
+			.parent_index = i
+		};
+
+		debug("%s (%s): parent %d rate %ld\n",
+		      clk->dev->name, __func__, i, parent_rate);
+
+		if (parent_rate == -ENOSYS) {
+			debug("%s: parent %d does not support get_rate\n",
+			      clk->dev->name, i);
+			continue;
+		}
+
+		if (parent_rate == 0) {
+			debug("%s: parent %d seems disabled (rate == 0)\n",
+			      clk->dev->name, i);
+			continue;
+		}
+
+		/* We recalculate the dividers, even if the parent's
+		 * rate is less than the requested rate
+		 */
+		sun4i_a10_get_mod0_factors(&req);
+
+		if (req.rate > rate) {
+			debug("%s: rate %ld for parent %i exceeds rate\n",
+			      clk->dev->name, req.rate, i);
+			continue;
+		}
+
+		if (req.rate > best_rate) {
+			debug("%s: new best => parent %d P %d M %d rate %ld\n",
+			      clk->dev->name, i, req.p, req.m, req.rate);
+
+			clrsetbits_le32(priv->reg,
+					SRCMASK | PREDIVMASK | DIVMASK,
+					SRC(i) | PREDIV(req.p) | DIV(req.m));
+			best_rate = req.rate;
+
+			/* don't continue, if this is the requested rate */
+			if (best_rate == rate)
+				break;
+		}
+	}
+
+	return best_rate;
+}
+
+static int sunxi_mod_enable(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+
+	setbits_le32(priv->reg, BIT(31));
+	return 0;
+}
+
+static int sunxi_mod_disable(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+
+	clrbits_le32(priv->reg, BIT(31));
+	return 0;
+}
+
+static struct clk_ops sunxi_clk_mod_ops = {
+	.set_rate = sunxi_mod_set_rate,
+	.get_rate = sunxi_mod_get_rate,
+	.enable = sunxi_mod_enable,
+	.disable = sunxi_mod_disable,
+};
+
+static int sunxi_clk_mod_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+	int i;
+
+	debug("%s: %s\n", dev->name, __func__);
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: could not get addr\n", dev->name);
+		return -EINVAL;
+	}
+
+	priv->reg = (void *)addr;
+
+	for (i = 0; i < 4; ++i) {
+		int ret = clk_get_by_index(dev, i, &priv->parent[i]);
+		if (ret != 0)
+			break;
+	};
+	priv->num_parents = i;
+
+	debug("%s: reg %p num-parents %d\n",
+	      dev->name, priv->reg, priv->num_parents);
+	return 0;
+}
+
+static const struct udevice_id sunxi_clk_mod_ids[] = {
+	{ .compatible = "allwinner,sun4i-a10-mod0-clk" },
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_mod) = {
+	.name		= "sunxi_clk_mod",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_mod_ids,
+	.ops		= &sunxi_clk_mod_ops,
+	.probe		= sunxi_clk_mod_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
+
+
diff --git a/drivers/clk/sunxi/clk-sunxi-pll.c b/drivers/clk/sunxi/clk-sunxi-pll.c
new file mode 100644
index 0000000..7c82ccf
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-pll.c
@@ -0,0 +1,123 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <wait_bit.h>
+#include <dm/lists.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_pll_data {
+	u8 nshift;
+	u8 nwidth;
+	u8 kshift;
+	u8 kwidth;
+	u8 mshift;
+	u8 mwidth;
+	u8 pshift;
+	u8 pwidth;
+	u8 n_start;
+};
+
+struct sunxi_clk_priv {
+	void *reg;
+};
+
+static inline uint32_t bitmask(uint8_t width, uint8_t shift)
+{
+	return ((1U << width) - 1) << shift;
+}
+
+static inline uint32_t factor_extract(uint32_t regval,
+				      uint8_t width,
+				      uint8_t shift)
+{
+	return ((regval & bitmask(width, shift)) >> shift) + 1;
+}
+
+static ulong sunxi_pll_get_rate(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	struct sunxi_clk_pll_data *data =
+		(struct sunxi_clk_pll_data *)dev_get_driver_data(clk->dev);
+	uint32_t regval = readl(priv->reg);
+	int n = factor_extract(regval, data->nwidth, data->nshift);
+	int k = factor_extract(regval, data->kwidth, data->kshift);
+	int m = factor_extract(regval, data->mwidth, data->mshift);
+	ulong rate = (24000000 * n * k) / m;
+
+	debug("%s (%s): id %ld base %p\n",
+	      clk->dev->name, __func__, clk->id, priv->reg);
+
+	/* Check if the PLL is enabled... */
+	if (!(regval & BIT(31)))
+		return 0;
+
+	debug("%s: n %d k %d m %d\n", clk->dev->name, n, k, m);
+	debug("%s: rate %ld\n", clk->dev->name, rate);
+
+	if (clk->id == 1)
+		return 2 * rate;
+
+	return rate;
+}
+
+static struct clk_ops sunxi_clk_pll_ops = {
+	/* For now, we'll let the arch/board-specific code setup the
+	   PLLs through the legacy implementation (some of this will
+	   happen in SPL, which may not have device model capability)
+	   and we only read the PLL rates. */
+	.get_rate = sunxi_pll_get_rate,
+};
+
+static int sunxi_clk_pll_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	priv->reg = (void *)addr;
+	if (!priv->reg)
+		return -EINVAL;
+
+	debug("%s: reg %p\n", dev->name, priv->reg);
+
+	return 0;
+}
+
+static struct sunxi_clk_pll_data pll6_data = {
+	.nwidth = 5,
+	.nshift = 8,
+	.kwidth = 2,
+	.kshift = 4,
+	.mwidth = 2,
+	.mshift = 0,
+};
+
+static const struct udevice_id sunxi_clk_pll_ids[] = {
+	{ .compatible = "allwinner,sun6i-a31-pll6-clk",
+	  .data = (uintptr_t)&pll6_data },
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_pll) = {
+	.name		= "sunxi_clk_pll",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_pll_ids,
+	.ops		= &sunxi_clk_pll_ops,
+	.probe		= sunxi_clk_pll_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
-- 
1.9.1



More information about the U-Boot mailing list