diff mbox series

[13/22] clk: at91: clk-master: add driver compatible with ccf

Message ID 1596034301-5428-14-git-send-email-claudiu.beznea@microchip.com
State Changes Requested
Delegated to: Eugen Hristev
Headers show
Series clk: at91: add sama7g5 support | expand

Commit Message

Claudiu Beznea July 29, 2020, 2:51 p.m. UTC
Add clk-master driver compatible with common clock framework.

Signed-off-by: Claudiu Beznea <claudiu.beznea@microchip.com>
---
 drivers/clk/at91/Makefile     |   2 +-
 drivers/clk/at91/clk-master.c | 156 ++++++++++++++++++++++++++++++++++++++++++
 drivers/clk/at91/pmc.h        |  21 ++++++
 3 files changed, 178 insertions(+), 1 deletion(-)
 create mode 100644 drivers/clk/at91/clk-master.c
diff mbox series

Patch

diff --git a/drivers/clk/at91/Makefile b/drivers/clk/at91/Makefile
index 338582b88aee..a4e397066e1e 100644
--- a/drivers/clk/at91/Makefile
+++ b/drivers/clk/at91/Makefile
@@ -3,7 +3,7 @@ 
 #
 
 ifdef CONFIG_CLK_CCF
-obj-y += pmc.o sckc.o clk-main.o
+obj-y += pmc.o sckc.o clk-main.o clk-master.o
 obj-$(CONFIG_AT91_SAM9X60_PLL)	+= clk-sam9x60-pll.o
 else
 obj-y += compat.o
diff --git a/drivers/clk/at91/clk-master.c b/drivers/clk/at91/clk-master.c
new file mode 100644
index 000000000000..1d388b6b2519
--- /dev/null
+++ b/drivers/clk/at91/clk-master.c
@@ -0,0 +1,156 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Master clock support for AT91 architectures.
+ *
+ * Copyright (C) 2020 Microchip Technology Inc. and its subsidiaries
+ *
+ * Author: Claudiu Beznea <claudiu.beznea@microchip.com>
+ *
+ * Based on drivers/clk/at91/clk-master.c from Linux.
+ */
+
+#include <asm/processor.h>
+#include <clk-uclass.h>
+#include <common.h>
+#include <dm.h>
+#include <linux/clk-provider.h>
+#include <linux/clk/at91_pmc.h>
+
+#include "pmc.h"
+
+#define UBOOT_DM_CLK_AT91_MASTER		"at91-master-clk"
+
+#define MASTER_PRES_MASK	0x7
+#define MASTER_PRES_MAX		MASTER_PRES_MASK
+#define MASTER_DIV_SHIFT	8
+#define MASTER_DIV_MASK		0x3
+
+struct clk_master {
+	void __iomem *base;
+	const struct clk_master_layout *layout;
+	const struct clk_master_characteristics *characteristics;
+	const u32 *mux_table;
+	const u32 *clk_mux_table;
+	u32 num_parents;
+	struct clk clk;
+	u8 id;
+};
+
+#define to_clk_master(_clk) container_of(_clk, struct clk_master, clk)
+
+static inline bool clk_master_ready(struct clk_master *master)
+{
+	unsigned int status;
+
+	pmc_read(master->base, AT91_PMC_SR, &status);
+
+	return !!(status & AT91_PMC_MCKRDY);
+}
+
+static int clk_master_enable(struct clk *clk)
+{
+	struct clk_master *master = to_clk_master(clk);
+
+	while (!clk_master_ready(master)) {
+		debug("waiting for mck %d\n", master->id);
+		cpu_relax();
+	}
+
+	return 0;
+}
+
+static ulong clk_master_get_rate(struct clk *clk)
+{
+	struct clk_master *master = to_clk_master(clk);
+	const struct clk_master_layout *layout = master->layout;
+	const struct clk_master_characteristics *characteristics =
+						master->characteristics;
+	ulong rate = clk_get_parent_rate(clk);
+	unsigned int mckr;
+	u8 pres, div;
+
+	if (!rate)
+		return 0;
+
+	pmc_read(master->base, master->layout->offset, &mckr);
+	mckr &= layout->mask;
+
+	pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
+	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
+
+	if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
+		rate /= 3;
+	else
+		rate >>= pres;
+
+	rate /= characteristics->divisors[div];
+
+	if (rate < characteristics->output.min)
+		pr_warn("master clk is underclocked");
+	else if (rate > characteristics->output.max)
+		pr_warn("master clk is overclocked");
+
+	return rate;
+}
+
+static const struct clk_ops master_ops = {
+	.enable = clk_master_enable,
+	.get_rate = clk_master_get_rate,
+};
+
+struct clk *at91_clk_register_master(void __iomem *base,
+		const char *name, const char * const *parent_names,
+		int num_parents, const struct clk_master_layout *layout,
+		const struct clk_master_characteristics *characteristics,
+		const u32 *mux_table)
+{
+	struct clk_master *master;
+	struct clk *clk;
+	unsigned int val;
+	int ret;
+
+	if (!base || !name || !num_parents || !parent_names ||
+	    !layout || !characteristics || !mux_table)
+		return ERR_PTR(-EINVAL);
+
+	master = kzalloc(sizeof(*master), GFP_KERNEL);
+	if (!master)
+		return ERR_PTR(-ENOMEM);
+
+	master->layout = layout;
+	master->characteristics = characteristics;
+	master->base = base;
+	master->num_parents = num_parents;
+	master->mux_table = mux_table;
+
+	pmc_read(master->base, master->layout->offset, &val);
+	clk = &master->clk;
+	clk->flags = CLK_GET_RATE_NOCACHE | CLK_IS_CRITICAL;
+	ret = clk_register(clk, UBOOT_DM_CLK_AT91_MASTER, name,
+			   parent_names[val & AT91_PMC_CSS]);
+	if (ret) {
+		kfree(master);
+		clk = ERR_PTR(ret);
+	}
+
+	return clk;
+}
+
+U_BOOT_DRIVER(at91_master_clk) = {
+	.name = UBOOT_DM_CLK_AT91_MASTER,
+	.id = UCLASS_CLK,
+	.ops = &master_ops,
+	.flags = DM_FLAG_PRE_RELOC,
+};
+
+const struct clk_master_layout at91rm9200_master_layout = {
+	.mask = 0x31F,
+	.pres_shift = 2,
+	.offset = AT91_PMC_MCKR,
+};
+
+const struct clk_master_layout at91sam9x5_master_layout = {
+	.mask = 0x373,
+	.pres_shift = 4,
+	.offset = AT91_PMC_MCKR,
+};
diff --git a/drivers/clk/at91/pmc.h b/drivers/clk/at91/pmc.h
index b24d2d89903d..33c7a66e84b5 100644
--- a/drivers/clk/at91/pmc.h
+++ b/drivers/clk/at91/pmc.h
@@ -19,6 +19,21 @@  struct clk_range {
 	unsigned long max;
 };
 
+struct clk_master_layout {
+	u32 offset;
+	u32 mask;
+	u8 pres_shift;
+};
+
+extern const struct clk_master_layout at91rm9200_master_layout;
+extern const struct clk_master_layout at91sam9x5_master_layout;
+
+struct clk_master_characteristics {
+	struct clk_range output;
+	u32 divisors[4];
+	u8 have_div3_pres;
+};
+
 struct clk_pll_characteristics {
 	struct clk_range input;
 	int num_output;
@@ -59,6 +74,12 @@  sam9x60_clk_register_frac_pll(void __iomem *base, const char *name,
 			const char *parent_name, u8 id,
 			const struct clk_pll_characteristics *characteristics,
 			const struct clk_pll_layout *layout, bool critical);
+struct clk *
+at91_clk_register_master(void __iomem *base, const char *name,
+			const char * const *parent_names, int num_parents,
+			const struct clk_master_layout *layout,
+			const struct clk_master_characteristics *characteristics,
+			const u32 *mux_table);
 
 int at91_clk_mux_val_to_index(const u32 *table, u32 num_parents, u32 val);
 int at91_clk_mux_index_to_val(const u32 *table, u32 num_parents, u32 index);