All of lore.kernel.org
 help / color / mirror / Atom feed
From: Dario Binacchi <dariobin@libero.it>
To: u-boot@lists.denx.de
Subject: [PATCH v7 09/28] clk: ti: add divider clock driver
Date: Thu, 24 Dec 2020 08:25:32 +0100	[thread overview]
Message-ID: <20201224072552.15848-10-dariobin@libero.it> (raw)
In-Reply-To: <20201224072552.15848-1-dariobin@libero.it>

The patch adds support for TI divider clock binding. The driver uses
routines provided by the common clock framework (ccf).

The code is based on the drivers/clk/ti/divider.c driver of the Linux
kernel version 5.9-rc7.
For DT binding details see:
- Documentation/devicetree/bindings/clock/ti/divider.txt

Signed-off-by: Dario Binacchi <dariobin@libero.it>

---

(no changes since v5)

Changes in v5:
- Move the clk-ti.c file to drivers/clk/ti with the name clk.c.
- Move the clk-ti.h file to drivers/clk/ti with the name clk.h.
- Move the clk-ti-divider.c file to drivers/clk/ti with the name
  clk-divider.c.

Changes in v4:
- Include device_compat.h header for dev_xxx macros.
- Fix compilation errors on the dev parameter of the dev_xx macros.

Changes in v3:
- Remove doc/device-tree-bindings/clock/ti,autoidle.txt.
- Remove doc/device-tree-bindings/clock/ti,divider.txt.
- Add to commit message the references to linux kernel dt binding
  documentation.

Changes in v2:
- Merged to patch [09/31] clk: ti: refactor mux and divider clock
  drivers.

 drivers/clk/ti/Kconfig       |   6 +
 drivers/clk/ti/Makefile      |   3 +
 drivers/clk/ti/clk-divider.c | 381 +++++++++++++++++++++++++++++++++++
 drivers/clk/ti/clk-mux.c     |  27 +--
 drivers/clk/ti/clk.c         |  34 ++++
 drivers/clk/ti/clk.h         |  13 ++
 6 files changed, 439 insertions(+), 25 deletions(-)
 create mode 100644 drivers/clk/ti/clk-divider.c
 create mode 100644 drivers/clk/ti/clk.c
 create mode 100644 drivers/clk/ti/clk.h

diff --git a/drivers/clk/ti/Kconfig b/drivers/clk/ti/Kconfig
index c430dd9b8a..87eea86c6f 100644
--- a/drivers/clk/ti/Kconfig
+++ b/drivers/clk/ti/Kconfig
@@ -10,6 +10,12 @@ config CLK_TI_AM3_DPLL
 	  This enables the DPLL clock drivers support on AM33XX SoCs. The DPLL
 	  provides all interface clocks and functional clocks to the processor.
 
+config CLK_TI_DIVIDER
+	bool "TI divider clock driver"
+	depends on CLK && OF_CONTROL && CLK_CCF
+	help
+	  This enables the divider clock driver support on TI's SoCs.
+
 config CLK_TI_MUX
 	bool "TI mux clock driver"
 	depends on CLK && OF_CONTROL && CLK_CCF
diff --git a/drivers/clk/ti/Makefile b/drivers/clk/ti/Makefile
index 9e14b83cfe..fd7094cff0 100644
--- a/drivers/clk/ti/Makefile
+++ b/drivers/clk/ti/Makefile
@@ -3,5 +3,8 @@
 # Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
 #
 
+obj-$(CONFIG_ARCH_OMAP2PLUS) += clk.o
+
 obj-$(CONFIG_CLK_TI_AM3_DPLL) += clk-am3-dpll.o clk-am3-dpll-x2.o
+obj-$(CONFIG_CLK_TI_DIVIDER) += clk-divider.o
 obj-$(CONFIG_CLK_TI_MUX) += clk-mux.o
diff --git a/drivers/clk/ti/clk-divider.c b/drivers/clk/ti/clk-divider.c
new file mode 100644
index 0000000000..d448197b1f
--- /dev/null
+++ b/drivers/clk/ti/clk-divider.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * TI divider clock support
+ *
+ * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
+ *
+ * Loosely based on Linux kernel drivers/clk/ti/divider.c
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <clk-uclass.h>
+#include <div64.h>
+#include <dm.h>
+#include <dm/device_compat.h>
+#include <asm/io.h>
+#include <linux/clk-provider.h>
+#include <linux/kernel.h>
+#include <linux/log2.h>
+#include "clk.h"
+
+/*
+ * The reverse of DIV_ROUND_UP: The maximum number which
+ * divided by m is r
+ */
+#define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1)
+
+struct clk_ti_divider_priv {
+	struct clk parent;
+	fdt_addr_t reg;
+	const struct clk_div_table *table;
+	u8 shift;
+	u8 flags;
+	u8 div_flags;
+	s8 latch;
+	u16 min;
+	u16 max;
+	u16 mask;
+};
+
+static unsigned int _get_div(const struct clk_div_table *table, ulong flags,
+			     unsigned int val)
+{
+	if (flags & CLK_DIVIDER_ONE_BASED)
+		return val;
+
+	if (flags & CLK_DIVIDER_POWER_OF_TWO)
+		return 1 << val;
+
+	if (table)
+		return clk_divider_get_table_div(table, val);
+
+	return val + 1;
+}
+
+static unsigned int _get_val(const struct clk_div_table *table, ulong flags,
+			     unsigned int div)
+{
+	if (flags & CLK_DIVIDER_ONE_BASED)
+		return div;
+
+	if (flags & CLK_DIVIDER_POWER_OF_TWO)
+		return __ffs(div);
+
+	if (table)
+		return clk_divider_get_table_val(table, div);
+
+	return div - 1;
+}
+
+static int _div_round_up(const struct clk_div_table *table, ulong parent_rate,
+			 ulong rate)
+{
+	const struct clk_div_table *clkt;
+	int up = INT_MAX;
+	int div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+
+	for (clkt = table; clkt->div; clkt++) {
+		if (clkt->div == div)
+			return clkt->div;
+		else if (clkt->div < div)
+			continue;
+
+		if ((clkt->div - div) < (up - div))
+			up = clkt->div;
+	}
+
+	return up;
+}
+
+static int _div_round(const struct clk_div_table *table, ulong parent_rate,
+		      ulong rate)
+{
+	if (table)
+		return _div_round_up(table, parent_rate, rate);
+
+	return DIV_ROUND_UP(parent_rate, rate);
+}
+
+static int clk_ti_divider_best_div(struct clk *clk, ulong rate,
+				   ulong *best_parent_rate)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev);
+	ulong parent_rate, parent_round_rate, max_div;
+	ulong best_rate, r;
+	int i, best_div = 0;
+
+	parent_rate = clk_get_rate(&priv->parent);
+	if (IS_ERR_VALUE(parent_rate))
+		return parent_rate;
+
+	if (!rate)
+		rate = 1;
+
+	if (!(clk->flags & CLK_SET_RATE_PARENT)) {
+		best_div = _div_round(priv->table, parent_rate, rate);
+		if (best_div == 0)
+			best_div = 1;
+
+		if (best_div > priv->max)
+			best_div = priv->max;
+
+		*best_parent_rate = parent_rate;
+		return best_div;
+	}
+
+	max_div = min(ULONG_MAX / rate, (ulong)priv->max);
+	for (best_rate = 0, i = 1; i <= max_div; i++) {
+		if (!clk_divider_is_valid_div(priv->table, priv->div_flags, i))
+			continue;
+
+		/*
+		 * It's the most ideal case if the requested rate can be
+		 * divided from parent clock without needing to change
+		 * parent rate, so return the divider immediately.
+		 */
+		if ((rate * i) == parent_rate) {
+			*best_parent_rate = parent_rate;
+			dev_dbg(clk->dev, "rate=%ld, best_rate=%ld, div=%d\n",
+				rate, rate, i);
+			return i;
+		}
+
+		parent_round_rate = clk_round_rate(&priv->parent,
+						   MULT_ROUND_UP(rate, i));
+		if (IS_ERR_VALUE(parent_round_rate))
+			continue;
+
+		r = DIV_ROUND_UP(parent_round_rate, i);
+		if (r <= rate && r > best_rate) {
+			best_div = i;
+			best_rate = r;
+			*best_parent_rate = parent_round_rate;
+			if (best_rate == rate)
+				break;
+		}
+	}
+
+	if (best_div == 0) {
+		best_div = priv->max;
+		parent_round_rate = clk_round_rate(&priv->parent, 1);
+		if (IS_ERR_VALUE(parent_round_rate))
+			return parent_round_rate;
+	}
+
+	dev_dbg(clk->dev, "rate=%ld, best_rate=%ld, div=%d\n", rate, best_rate,
+		best_div);
+
+	return best_div;
+}
+
+static ulong clk_ti_divider_round_rate(struct clk *clk, ulong rate)
+{
+	ulong parent_rate;
+	int div;
+
+	div = clk_ti_divider_best_div(clk, rate, &parent_rate);
+	if (div < 0)
+		return div;
+
+	return DIV_ROUND_UP(parent_rate, div);
+}
+
+static ulong clk_ti_divider_set_rate(struct clk *clk, ulong rate)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev);
+	ulong parent_rate;
+	int div;
+	u32 val, v;
+
+	div = clk_ti_divider_best_div(clk, rate, &parent_rate);
+	if (div < 0)
+		return div;
+
+	if (clk->flags & CLK_SET_RATE_PARENT) {
+		parent_rate = clk_set_rate(&priv->parent, parent_rate);
+		if (IS_ERR_VALUE(parent_rate))
+			return parent_rate;
+	}
+
+	val = _get_val(priv->table, priv->div_flags, div);
+
+	v = readl(priv->reg);
+	v &= ~(priv->mask << priv->shift);
+	v |= val << priv->shift;
+	writel(v, priv->reg);
+	clk_ti_latch(priv->reg, priv->latch);
+
+	return clk_get_rate(clk);
+}
+
+static ulong clk_ti_divider_get_rate(struct clk *clk)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev);
+	ulong rate, parent_rate;
+	unsigned int div;
+	u32 v;
+
+	parent_rate = clk_get_rate(&priv->parent);
+	if (IS_ERR_VALUE(parent_rate))
+		return parent_rate;
+
+	v = readl(priv->reg) >> priv->shift;
+	v &= priv->mask;
+
+	div = _get_div(priv->table, priv->div_flags, v);
+	if (!div) {
+		if (!(priv->div_flags & CLK_DIVIDER_ALLOW_ZERO))
+			dev_warn(clk->dev,
+				 "zero divisor and CLK_DIVIDER_ALLOW_ZERO not set\n");
+		return parent_rate;
+	}
+
+	rate = DIV_ROUND_UP(parent_rate, div);
+	dev_dbg(clk->dev, "rate=%ld\n", rate);
+	return rate;
+}
+
+static int clk_ti_divider_request(struct clk *clk)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(clk->dev);
+
+	clk->flags = priv->flags;
+	return 0;
+}
+
+const struct clk_ops clk_ti_divider_ops = {
+	.request = clk_ti_divider_request,
+	.round_rate = clk_ti_divider_round_rate,
+	.get_rate = clk_ti_divider_get_rate,
+	.set_rate = clk_ti_divider_set_rate
+};
+
+static int clk_ti_divider_remove(struct udevice *dev)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(dev);
+	int err;
+
+	err = clk_release_all(&priv->parent, 1);
+	if (err) {
+		dev_err(dev, "failed to release parent clock\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static int clk_ti_divider_probe(struct udevice *dev)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(dev);
+	int err;
+
+	err = clk_get_by_index(dev, 0, &priv->parent);
+	if (err) {
+		dev_err(dev, "failed to get parent clock\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static int clk_ti_divider_ofdata_to_platdata(struct udevice *dev)
+{
+	struct clk_ti_divider_priv *priv = dev_get_priv(dev);
+	struct clk_div_table *table = NULL;
+	u32 val, valid_div;
+	u32 min_div = 0;
+	u32 max_val, max_div = 0;
+	u16 mask;
+	int i, div_num;
+
+	priv->reg = dev_read_addr(dev);
+	dev_dbg(dev, "reg=0x%08lx\n", priv->reg);
+	priv->shift = dev_read_u32_default(dev, "ti,bit-shift", 0);
+	priv->latch = dev_read_s32_default(dev, "ti,latch-bit", -EINVAL);
+	if (dev_read_bool(dev, "ti,index-starts-at-one"))
+		priv->div_flags |= CLK_DIVIDER_ONE_BASED;
+
+	if (dev_read_bool(dev, "ti,index-power-of-two"))
+		priv->div_flags |= CLK_DIVIDER_POWER_OF_TWO;
+
+	if (dev_read_bool(dev, "ti,set-rate-parent"))
+		priv->flags |= CLK_SET_RATE_PARENT;
+
+	if (dev_read_prop(dev, "ti,dividers", &div_num)) {
+		div_num /= sizeof(u32);
+
+		/* Determine required size for divider table */
+		for (i = 0, valid_div = 0; i < div_num; i++) {
+			dev_read_u32_index(dev, "ti,dividers", i, &val);
+			if (val)
+				valid_div++;
+		}
+
+		if (!valid_div) {
+			dev_err(dev, "no valid dividers\n");
+			return -EINVAL;
+		}
+
+		table = calloc(valid_div + 1, sizeof(*table));
+		if (!table)
+			return -ENOMEM;
+
+		for (i = 0, valid_div = 0; i < div_num; i++) {
+			dev_read_u32_index(dev, "ti,dividers", i, &val);
+			if (!val)
+				continue;
+
+			table[valid_div].div = val;
+			table[valid_div].val = i;
+			valid_div++;
+			if (val > max_div)
+				max_div = val;
+
+			if (!min_div || val < min_div)
+				min_div = val;
+		}
+
+		max_val = max_div;
+	} else {
+		/* Divider table not provided, determine min/max divs */
+		min_div = dev_read_u32_default(dev, "ti,min-div", 1);
+		if (dev_read_u32(dev, "ti,max-div", &max_div)) {
+			dev_err(dev, "missing 'max-div' property\n");
+			return -EFAULT;
+		}
+
+		max_val = max_div;
+		if (!(priv->div_flags & CLK_DIVIDER_ONE_BASED) &&
+		    !(priv->div_flags & CLK_DIVIDER_POWER_OF_TWO))
+			max_val--;
+	}
+
+	priv->table = table;
+	priv->min = min_div;
+	priv->max = max_div;
+
+	if (priv->div_flags & CLK_DIVIDER_POWER_OF_TWO)
+		mask = fls(max_val) - 1;
+	else
+		mask = max_val;
+
+	priv->mask = (1 << fls(mask)) - 1;
+	return 0;
+}
+
+static const struct udevice_id clk_ti_divider_of_match[] = {
+	{.compatible = "ti,divider-clock"},
+	{}
+};
+
+U_BOOT_DRIVER(clk_ti_divider) = {
+	.name = "ti_divider_clock",
+	.id = UCLASS_CLK,
+	.of_match = clk_ti_divider_of_match,
+	.ofdata_to_platdata = clk_ti_divider_ofdata_to_platdata,
+	.probe = clk_ti_divider_probe,
+	.remove = clk_ti_divider_remove,
+	.priv_auto_alloc_size = sizeof(struct clk_ti_divider_priv),
+	.ops = &clk_ti_divider_ops,
+};
diff --git a/drivers/clk/ti/clk-mux.c b/drivers/clk/ti/clk-mux.c
index 9720c84513..35470255af 100644
--- a/drivers/clk/ti/clk-mux.c
+++ b/drivers/clk/ti/clk-mux.c
@@ -13,6 +13,7 @@
 #include <clk-uclass.h>
 #include <asm/io.h>
 #include <linux/clk-provider.h>
+#include "clk.h"
 
 struct clk_ti_mux_priv {
 	struct clk_bulk parents;
@@ -24,30 +25,6 @@ struct clk_ti_mux_priv {
 	s32 latch;
 };
 
-static void clk_ti_mux_rmw(u32 val, u32 mask, fdt_addr_t reg)
-{
-	u32 v;
-
-	v = readl(reg);
-	v &= ~mask;
-	v |= val;
-	writel(v, reg);
-}
-
-static void clk_ti_mux_latch(fdt_addr_t reg, s8 shift)
-{
-	u32 latch;
-
-	if (shift < 0)
-		return;
-
-	latch = 1 << shift;
-
-	clk_ti_mux_rmw(latch, latch, reg);
-	clk_ti_mux_rmw(0, latch, reg);
-	readl(reg);		/* OCP barrier */
-}
-
 static struct clk *clk_ti_mux_get_parent_by_index(struct clk_bulk *parents,
 						  int index)
 {
@@ -120,7 +97,7 @@ static int clk_ti_mux_set_parent(struct clk *clk, struct clk *parent)
 
 	val |= index << priv->shift;
 	writel(val, priv->reg);
-	clk_ti_mux_latch(priv->reg, priv->latch);
+	clk_ti_latch(priv->reg, priv->latch);
 	return 0;
 }
 
diff --git a/drivers/clk/ti/clk.c b/drivers/clk/ti/clk.c
new file mode 100644
index 0000000000..e44b90ad6a
--- /dev/null
+++ b/drivers/clk/ti/clk.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * TI clock utilities
+ *
+ * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include "clk.h"
+
+static void clk_ti_rmw(u32 val, u32 mask, fdt_addr_t reg)
+{
+	u32 v;
+
+	v = readl(reg);
+	v &= ~mask;
+	v |= val;
+	writel(v, reg);
+}
+
+void clk_ti_latch(fdt_addr_t reg, s8 shift)
+{
+	u32 latch;
+
+	if (shift < 0)
+		return;
+
+	latch = 1 << shift;
+
+	clk_ti_rmw(latch, latch, reg);
+	clk_ti_rmw(0, latch, reg);
+	readl(reg);		/* OCP barrier */
+}
diff --git a/drivers/clk/ti/clk.h b/drivers/clk/ti/clk.h
new file mode 100644
index 0000000000..601c3823f7
--- /dev/null
+++ b/drivers/clk/ti/clk.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * TI clock utilities header
+ *
+ * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
+ */
+
+#ifndef _CLK_TI_H
+#define _CLK_TI_H
+
+void clk_ti_latch(fdt_addr_t reg, s8 shift);
+
+#endif /* #ifndef _CLK_TI_H */
-- 
2.17.1

  parent reply	other threads:[~2020-12-24  7:25 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-24  7:25 [PATCH v7 00/28] Add DM support for omap PWM backlight Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 01/28] clk: export generic routines Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 02/28] dt-bindings: bus: ti-sysc: resync with Linux 5.9-rc7 Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 03/28] bus: ti: add minimal sysc interconnect target driver Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 04/28] arm: dts: sync am33xx with Linux 5.9-rc7 Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 05/28] clk: add clk_round_rate() Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 06/28] clk: ti: add mux clock driver Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 07/28] arm: ti: am33xx: add DPLL_EN_FAST_RELOCK_BYPASS macro Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 08/28] clk: ti: am33xx: add DPLL clock drivers Dario Binacchi
2020-12-24  7:25 ` Dario Binacchi [this message]
2020-12-24  7:25 ` [PATCH v7 10/28] clk: ti: add gate clock driver Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 11/28] arm: dts: am335x: include am33xx-u-boot.dtsi Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 12/28] ti: am33xx: fix do_enable_clocks() to accept NULL parameters Dario Binacchi
2020-12-24  7:25 ` [PATCH v7 13/28] clk: ti: add support for clkctrl clocks Dario Binacchi

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=20201224072552.15848-10-dariobin@libero.it \
    --to=dariobin@libero.it \
    --cc=u-boot@lists.denx.de \
    /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.