Skip to contentSkip to conversation

clk: socfpga: Add initial Arria10 clock driver

PATCH New Marek Vasut 2 participants 2 messages Aug 8, 2018 – Aug 9, 2018 Common clk

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 clk: socfpga: Add initial Arria10 clock driver

Add clock driver for the Arria10, which allows reading the clock frequency from all the clock described in the DT. The driver also allows enabling and disabling the clock. Reconfiguring frequency is not supported thus far.

Since the DT bindings for the SoCFPGA clock are massively misdesigned and the handoff DT adds additional incorrectly described entries to the DT, the driver contains workarounds which attempt to rectify all of those problems.

Cc Chin Liang See <[email protected]>
Cc Dinh Nguyen <[email protected]>
Cc Ley Foon Tan <[email protected]>
---
 drivers/clk/Makefile             |   1 +
 drivers/clk/altera/Makefile      |   7 +
 drivers/clk/altera/clk-arria10.c | 363 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 371 insertions(+)
 create mode 100644 drivers/clk/altera/Makefile
 create mode 100644 drivers/clk/altera/clk-arria10.c
drivers/clk/Makefile +1 -0
index 146283c723..034bf44078 100644
@@ -11,6 +11,7 @@ obj-y += tegra/
 obj-$(CONFIG_ARCH_ASPEED) += aspeed/
 obj-$(CONFIG_ARCH_MESON) += clk_meson.o
 obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
+obj-$(CONFIG_ARCH_SOCFPGA) += altera/
 obj-$(CONFIG_CLK_AT91) += at91/
 obj-$(CONFIG_CLK_MVEBU) += mvebu/
 obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o
drivers/clk/altera/Makefile new file +7 -0
new file mode 100644
index 0000000000..2542b7f51b
@@ -0,0 +1,7 @@
+#
+# Copyright (C) 2018 Marek Vasut <[email protected]>
+#
+# SPDX-License-Identifier:      GPL-2.0+
+#
+
+obj-$(CONFIG_TARGET_SOCFPGA_ARRIA10) += clk-arria10.o
drivers/clk/altera/clk-arria10.c new file +363 -0

363 lines not on this page · raw message

Signature · 1 line
2.16.2
#1 lore raw

PATCH 1/1 clk: socfpga: Add initial Arria10 clock driver

Quoted 453 lines · Marek Vasut

Add clock driver for the Arria10, which allows reading the clock frequency from all the clock described in the DT. The driver also allows enabling and disabling the clock. Reconfiguring frequency is not supported thus far.

Since the DT bindings for the SoCFPGA clock are massively misdesigned and the handoff DT adds additional incorrectly described entries to the DT, the driver contains workarounds which attempt to rectify all of those problems.

Cc Chin Liang See <[email protected]>
Cc Dinh Nguyen <[email protected]>
Cc Ley Foon Tan <[email protected]>

---
 drivers/clk/Makefile             |   1 +
 drivers/clk/altera/Makefile      |   7 +
 drivers/clk/altera/clk-arria10.c | 363
+++++++++++++++++++++++++++++++++++++++

 3 files changed, 371 insertions(+)

 create mode 100644 drivers/clk/altera/Makefile create mode 100644 drivers/clk/altera/clk-arria10.c

drivers/clk/Makefile +0 -0
index 146283c723..034bf44078 100644
@@ -11,6 +11,7 @@ obj-y += tegra/

 obj-$(CONFIG_ARCH_ASPEED) += aspeed/
 obj-$(CONFIG_ARCH_MESON) += clk_meson.o
 obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
+obj-$(CONFIG_ARCH_SOCFPGA) += altera/
 obj-$(CONFIG_CLK_AT91) += at91/
 obj-$(CONFIG_CLK_MVEBU) += mvebu/
 obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o

drivers/clk/altera/Makefile new file +7 -0
new file mode 100644
index 0000000000..2542b7f51b
@@ -0,0 +1,7 @@
+#
+# Copyright (C) 2018 Marek Vasut <[email protected]>
+#
+# SPDX-License-Identifier:      GPL-2.0+
+#
+
+obj-$(CONFIG_TARGET_SOCFPGA_ARRIA10) += clk-arria10.o
drivers/clk/altera/clk-arria10.c new file +40 -0
new file mode 100644
index 0000000000..78102c760d
@@ -0,0 +1,363 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018 Marek Vasut <[email protected]>
+ */
+
+#include <common.h>
+#include <asm/io.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <dm/lists.h>
+#include <dm/util.h>
+
+#include <asm/arch/clock_manager.h>
+
+enum socfpga_a10_clk_type {
+	SOCFPGA_A10_CLK_MAIN_PLL,
+	SOCFPGA_A10_CLK_PER_PLL,
+	SOCFPGA_A10_CLK_PERIP_CLK,
+	SOCFPGA_A10_CLK_GATE_CLK,
+	SOCFPGA_A10_CLK_UNKNOWN_CLK,
+};
+
+struct socfpga_a10_clk_platdata {
+	enum socfpga_a10_clk_type type;
+	struct clk_bulk	clks;
+	u32		regs;
+	/* Fixed divider */
+	u16		fix_div;
+	/* Control register */
+	u16		ctl_reg;
+	/* Divider register */
+	u16		div_reg;
+	u8		div_len;
+	u8		div_off;
+	/* Clock gating register */
+	u16		gate_reg;
+	u8		gate_bit;
+};
+
+static int socfpga_a10_clk_get_upstream(struct clk *clk, struct clk

**upclk)

drivers/clk/altera/clk-arria10.c new file +2 -0
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(clk->dev);

drivers/clk/altera/clk-arria10.c new file +41 -0
+	u32 reg, maxval;
+
+	if (plat->clks.count == 0)
+		return 0;
+
+	if (plat->clks.count == 1) {
+		*upclk = &plat->clks.clks[0];
+		return 0;
+	}
+
+	if (!plat->ctl_reg) {
+		dev_err(clk->dev, "Invalid control register\n");
+		return -EINVAL;
+	}
+
+	reg = readl(plat->regs + plat->ctl_reg);
+
+	/* Assume PLLs are ON for now */
+	if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) {
+		reg = (reg >> 8) & 0x3;
+		maxval = 2;
+	} else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) {
+		reg = (reg >> 8) & 0x3;
+		maxval = 3;
+	} else {
+		reg = (reg >> 16) & 0x7;
+		maxval = 4;
+	}
+
+	if (reg > maxval) {
+		dev_err(clk->dev, "Invalid clock source\n");
+		return -EINVAL;
+	}
+
+	*upclk = &plat->clks.clks[reg];
+	return 0;
+}
+
+static int socfpga_a10_clk_endisable(struct clk *clk, bool enable)
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(clk->dev);

drivers/clk/altera/clk-arria10.c new file +5 -0
+	struct clk *upclk = NULL;
+	int ret;
+
+	if (!enable && plat->gate_reg)
+		clrbits_le32(plat->regs + plat->gate_reg, BIT(plat-
Quoted 1 line

gate_bit));

drivers/clk/altera/clk-arria10.c new file +14 -0
+
+	ret = socfpga_a10_clk_get_upstream(clk, &upclk);
+	if (ret)
+		return ret;
+
+	if (upclk) {
+		if (enable)
+			clk_enable(upclk);
+		else
+			clk_disable(upclk);
+	}
+
+	if (enable && plat->gate_reg)
+		setbits_le32(plat->regs + plat->gate_reg, BIT(plat-
Quoted 1 line

gate_bit));

drivers/clk/altera/clk-arria10.c new file +17 -0
+
+	return 0;
+}
+
+static int socfpga_a10_clk_enable(struct clk *clk)
+{
+	return socfpga_a10_clk_endisable(clk, true);
+}
+
+static int socfpga_a10_clk_disable(struct clk *clk)
+{
+	return socfpga_a10_clk_endisable(clk, false);
+}
+
+static ulong socfpga_a10_clk_get_rate(struct clk *clk)
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(clk->dev);

drivers/clk/altera/clk-arria10.c new file +12 -0
+	struct clk *upclk = NULL;
+	ulong rate = 0, reg, numer, denom;
+	int ret;
+
+	ret = socfpga_a10_clk_get_upstream(clk, &upclk);
+	if (ret || !upclk)
+		return 0;
+
+	rate = clk_get_rate(upclk);
+
+	if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) {
+		reg = readl(plat->regs + plat->ctl_reg + 4);	

/* VCO1 */

drivers/clk/altera/clk-arria10.c new file +8 -0
+		numer = reg & CLKMGR_MAINPLL_VCO1_NUMER_MSK;
+		denom = (reg >> CLKMGR_MAINPLL_VCO1_DENOM_LSB) &
+			CLKMGR_MAINPLL_VCO1_DENOM_MSK;
+
+		rate /= denom + 1;
+		rate *= numer + 1;
+	} else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) {
+		reg = readl(plat->regs + plat->ctl_reg + 4);	

/* VCO1 */

drivers/clk/altera/clk-arria10.c new file +37 -0
+		numer = reg & CLKMGR_PERPLL_VCO1_NUMER_MSK;
+		denom = (reg >> CLKMGR_PERPLL_VCO1_DENOM_LSB) &
+			CLKMGR_PERPLL_VCO1_DENOM_MSK;
+
+		rate /= denom + 1;
+		rate *= numer + 1;
+	} else {
+		rate /= plat->fix_div;
+
+		if (plat->fix_div == 1 && plat->ctl_reg) {
+			reg = readl(plat->regs + plat->ctl_reg);
+			reg &= 0x7ff;
+			rate /= reg + 1;
+		}
+
+		if (plat->div_reg) {
+			reg = readl(plat->regs + plat->div_reg);
+			reg >>= plat->div_off;
+			reg &= (1 << plat->div_len) - 1;
+			if (plat->type == SOCFPGA_A10_CLK_PERIP_CLK)
+				rate /= reg + 1;
+			if (plat->type == SOCFPGA_A10_CLK_GATE_CLK)
+				rate /= 1 << reg;
+		}
+	}
+
+	return rate;
+}
+
+static struct clk_ops socfpga_a10_clk_ops = {
+	.enable		= socfpga_a10_clk_enable,
+	.disable	= socfpga_a10_clk_disable,
+	.get_rate	= socfpga_a10_clk_get_rate,
+};
+
+/*
+ * This workaround tries to fix the massively broken generated

"handoff" DT,
+ * which contains duplicate clock nodes without any connection to the clock
+ * manager DT node. Yet, those "handoff" DT nodes contain configuration of
+ * the fixed input clock of the Arria10 which are missing from the base DT
+ * for Arria10.
+ *
+ * This workaround sets up upstream clock for the fixed input clocks of the
+ * A10 described in the base DT such that they map to the fixed clock from
+ * the "handoff" DT. This does not fully match how the clock look on the
+ * A10, but it is the least intrusive way to fix this mess.
+ */

drivers/clk/altera/clk-arria10.c new file +3 -0
+static void socfpga_a10_handoff_workaround(struct udevice *dev)
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(dev);

drivers/clk/altera/clk-arria10.c new file +6 -0
+	const void *fdt = gd->fdt_blob;
+	struct clk_bulk	*bulk = &plat->clks;
+	int i, ret, offset = dev_of_offset(dev);
+	static const char * const socfpga_a10_fixedclk_map[] = {
+		"osc1", "altera_arria10_hps_eosc1",
+		"cb_intosc_ls_clk",

"altera_arria10_hps_cb_intosc_ls",

drivers/clk/altera/clk-arria10.c new file +7 -0
+		"f2s_free_clk", "altera_arria10_hps_f2h_free",
+	};
+
+	if (fdt_node_check_compatible(fdt, offset, "fixed-clock"))
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(socfpga_a10_fixedclk_map); i +=

2)

drivers/clk/altera/clk-arria10.c new file +8 -0
+		if (!strcmp(dev->name, socfpga_a10_fixedclk_map[i]))
+			break;
+
+	if (i == ARRAY_SIZE(socfpga_a10_fixedclk_map))
+		return;
+
+	ret = uclass_get_device_by_name(UCLASS_CLK,
+					socfpga_a10_fixedclk_map[i +

1], &dev);

drivers/clk/altera/clk-arria10.c new file +39 -0
+	if (ret)
+		return;
+
+	bulk->count = 1;
+	bulk->clks = devm_kcalloc(dev, bulk->count,
+				  sizeof(struct clk), GFP_KERNEL);
+	if (!bulk->clks)
+		return;
+
+	ret = clk_request(dev, &bulk->clks[0]);
+	if (ret)
+		free(bulk->clks);
+}
+
+static int socfpga_a10_clk_bind(struct udevice *dev)
+{
+	const void *fdt = gd->fdt_blob;
+	int offset = dev_of_offset(dev);
+	bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC);
+	const char *name;
+	int ret;
+
+	for (offset = fdt_first_subnode(fdt, offset);
+	     offset > 0;
+	     offset = fdt_next_subnode(fdt, offset)) {
+		name = fdt_get_name(fdt, offset, NULL);
+		if (!name)
+			return -EINVAL;
+
+		if (!strcmp(name, "clocks")) {
+			offset = fdt_first_subnode(fdt, offset);
+			name = fdt_get_name(fdt, offset, NULL);
+			if (!name)
+				return -EINVAL;
+		}
+
+		/* Filter out supported sub-clock */
+		if (fdt_node_check_compatible(fdt, offset,
+					      "altr,socfpga-a10-pll-

clock") &&

drivers/clk/altera/clk-arria10.c new file +2 -0
+		    fdt_node_check_compatible(fdt, offset,
+					      "altr,socfpga-a10-

perip-clk") &&

drivers/clk/altera/clk-arria10.c new file +2 -0
+		    fdt_node_check_compatible(fdt, offset,
+					      "altr,socfpga-a10-

gate-clk") &&

drivers/clk/altera/clk-arria10.c new file +1 -0
+		    fdt_node_check_compatible(fdt, offset, "fixed-

clock"))

drivers/clk/altera/clk-arria10.c new file +3 -0
+			continue;
+
+		if (pre_reloc_only && !dm_fdt_pre_reloc(fdt,

offset))

drivers/clk/altera/clk-arria10.c new file +3 -0
+			continue;
+
+		ret = device_bind_driver_to_node(dev, "clk-a10",

name,

drivers/clk/altera/clk-arria10.c new file +1 -0
+						 offset_to_ofnode(of

fset),

drivers/clk/altera/clk-arria10.c new file +11 -0
+						 NULL);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int socfpga_a10_clk_probe(struct udevice *dev)
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(dev);

drivers/clk/altera/clk-arria10.c new file +9 -0
+	const void *fdt = gd->fdt_blob;
+	int offset = dev_of_offset(dev);
+
+	clk_get_bulk(dev, &plat->clks);
+
+	socfpga_a10_handoff_workaround(dev);
+
+	if (!fdt_node_check_compatible(fdt, offset,
+				       "altr,socfpga-a10-pll-

clock")) {

drivers/clk/altera/clk-arria10.c new file +7 -0
+		/* Main PLL has 3 upstream clock */
+		if (plat->clks.count == 3)
+			plat->type = SOCFPGA_A10_CLK_MAIN_PLL;
+		else
+			plat->type = SOCFPGA_A10_CLK_PER_PLL;
+	} else if (!fdt_node_check_compatible(fdt, offset,
+					      "altr,socfpga-a10-

perip-clk")) {

drivers/clk/altera/clk-arria10.c new file +3 -0
+		plat->type = SOCFPGA_A10_CLK_PERIP_CLK;
+	} else if (!fdt_node_check_compatible(fdt, offset,
+					      "altr,socfpga-a10-

gate-clk")) {

drivers/clk/altera/clk-arria10.c new file +11 -0
+		plat->type = SOCFPGA_A10_CLK_GATE_CLK;
+	} else {
+		plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK;
+	}
+
+	return 0;
+}
+
+static int socfpga_a10_ofdata_to_platdata(struct udevice *dev)
+{
+	struct socfpga_a10_clk_platdata *plat =

dev_get_platdata(dev);

drivers/clk/altera/clk-arria10.c new file +10 -0
+	struct socfpga_a10_clk_platdata *pplat;
+	struct udevice *pdev;
+	const void *fdt = gd->fdt_blob;
+	unsigned int divreg[3], gatereg[2];
+	int ret, offset = dev_of_offset(dev);
+	u32 regs;
+
+	regs = dev_read_u32_default(dev, "reg", 0x0);
+
+	if (!fdt_node_check_compatible(fdt, offset, "altr,clk-mgr")) 

{

drivers/clk/altera/clk-arria10.c new file +17 -0
+		plat->regs = devfdt_get_addr(dev);
+	} else {
+		pdev = dev_get_parent(dev);
+		if (!pdev)
+			return -ENODEV;
+
+		pplat = dev_get_platdata(pdev);
+		if (!pplat)
+			return -EINVAL;
+
+		plat->ctl_reg = regs;
+		plat->regs = pplat->regs;
+	}
+
+	plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK;
+
+	plat->fix_div = dev_read_u32_default(dev, "fixed-divider",

1);

drivers/clk/altera/clk-arria10.c new file +2 -0
+
+	ret = dev_read_u32_array(dev, "div-reg", divreg,

ARRAY_SIZE(divreg));

drivers/clk/altera/clk-arria10.c new file +7 -0
+	if (!ret) {
+		plat->div_reg = divreg[0];
+		plat->div_len = divreg[2];
+		plat->div_off = divreg[1];
+	}
+
+	ret = dev_read_u32_array(dev, "clk-gate", gatereg,

ARRAY_SIZE(gatereg));

drivers/clk/altera/clk-arria10.c new file +24 -0
+	if (!ret) {
+		plat->gate_reg = gatereg[0];
+		plat->gate_bit = gatereg[1];
+	}
+
+	return 0;
+}
+
+static const struct udevice_id socfpga_a10_clk_match[] = {
+	{ .compatible = "altr,clk-mgr" },
+	{}
+};
+
+U_BOOT_DRIVER(socfpga_a10_clk) = {
+	.name		= "clk-a10",
+	.id		= UCLASS_CLK,
+	.flags		= DM_FLAG_PRE_RELOC,
+	.of_match	= socfpga_a10_clk_match,
+	.ops		= &socfpga_a10_clk_ops,
+	.bind		= socfpga_a10_clk_bind,
+	.probe		= socfpga_a10_clk_probe,
+	.ofdata_to_platdata = socfpga_a10_ofdata_to_platdata,
+
+	.platdata_auto_alloc_size = sizeof(struct
Quoted 2 lines

socfpga_a10_clk_platdata),
+};