A rather minimal driver for that reads back configured clock rates for
hardware we support.

Signed-off-by: Lubomir Rintel <lkund...@v3.sk>
---
 drivers/clk/Kconfig      |   8 +++
 drivers/clk/Makefile     |   1 +
 drivers/clk/clk-jz4730.c | 121 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 130 insertions(+)
 create mode 100644 drivers/clk/clk-jz4730.c

diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index 4dfbad7986b..a138c6ebcde 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -113,6 +113,14 @@ config CLK_HSDK
          Enable this to support the cgu clocks on Synopsys ARC HSDK and
          Synopsys ARC HSDK-4xD boards
 
+config CLK_JZ4730
+       bool "Enable clock driver for Ingenic JZ4730 cgu"
+       depends on CLK && SOC_JZ4730
+       default y
+       help
+         This clock driver adds support for clock generators present on
+         Ingenic JZ4730 SoC.
+
 config CLK_VERSAL
        bool "Enable clock driver support for Versal"
        depends on ARCH_VERSAL
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d1e295ac7c1..daad6333b36 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_CLK_BOSTON) += clk_boston.o
 obj-$(CONFIG_CLK_EXYNOS) += exynos/
 obj-$(CONFIG_$(SPL_TPL_)CLK_INTEL) += intel/
 obj-$(CONFIG_CLK_HSDK) += clk-hsdk-cgu.o
+obj-$(CONFIG_CLK_JZ4730) += clk-jz4730.o
 obj-$(CONFIG_CLK_K210) += kendryte/
 obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o
 obj-$(CONFIG_CLK_OCTEON) += clk_octeon.o
diff --git a/drivers/clk/clk-jz4730.c b/drivers/clk/clk-jz4730.c
new file mode 100644
index 00000000000..332b1ea82d6
--- /dev/null
+++ b/drivers/clk/clk-jz4730.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * JZ4730 Clock Generation Unit driver.
+ *
+ * Copyright (C) 2020 Lubomir Rintel <lkund...@v3.sk>
+ */
+
+#include <dt-bindings/clock/jz4730-cgu.h>
+#include <common.h>
+#include <dm.h>
+#include <clk-uclass.h>
+#include <div64.h>
+#include <linux/bitops.h>
+#include <asm/io.h>
+
+#define CPM_CFCR       0x00
+#define CPM_PLCR1      0x10
+#define CPM_OCR                0x1c
+
+#define CPM_OCR_EXT_RTC_CLK    BIT(8)
+
+#define CPM_PLCR1_PLL1EN       BIT(8)
+
+#define CPM_CFCR_PFR_SHIFT     8
+
+#define CPM_PLCR1_PLL1FD_SHIFT 23
+#define CPM_PLCR1_PLL1FD_MASK  (0x1ff << CPM_PLCR1_PLL1FD_SHIFT)
+#define CPM_PLCR1_PLL1RD_SHIFT 18
+#define CPM_PLCR1_PLL1RD_MASK  (0x1f << CPM_PLCR1_PLL1RD_SHIFT)
+#define CPM_PLCR1_PLL1OD_SHIFT 16
+#define CPM_PLCR1_PLL1OD_MASK  (0x03 << CPM_PLCR1_PLL1OD_SHIFT)
+
+struct jz4730_cgu_priv {
+       void __iomem *base;
+       unsigned long ext_rate;
+};
+
+static inline unsigned int pdiv(u32 cfcr, u32 shift)
+{
+       static unsigned int pdiv_table[] = { 1, 2, 3, 4, 6, 8, 12, 16, 24, 32 };
+
+       return pdiv_table[cfcr >> shift & 0xf];
+}
+
+static inline unsigned long pll_rate(u32 plcr1, unsigned long ext_rate)
+{
+       unsigned long long rate;
+
+       rate = ext_rate;
+       rate *= ((plcr1 & CPM_PLCR1_PLL1FD_MASK) >> CPM_PLCR1_PLL1FD_SHIFT) + 2;
+       do_div(rate, ((plcr1 & CPM_PLCR1_PLL1RD_MASK) >> 
CPM_PLCR1_PLL1RD_SHIFT) + 2);
+
+       return rate;
+}
+
+static ulong jz4730_cgu_clk_get_rate(struct clk *clk)
+{
+       struct jz4730_cgu_priv *priv = dev_get_priv(clk->dev);
+       u32 cfcr, plcr1, ocr;
+
+       switch (clk->id) {
+       case JZ4730_CLK_PCLK:
+               plcr1 = readl(priv->base + CPM_PLCR1);
+               if (plcr1 & CPM_PLCR1_PLL1EN) {
+                       cfcr = readl(priv->base + CPM_CFCR);
+                       return pll_rate(plcr1, priv->ext_rate) /
+                               pdiv(cfcr, CPM_CFCR_PFR_SHIFT);
+               }
+               return priv->ext_rate;
+       case JZ4730_CLK_WDT:
+               ocr = readl(priv->base + CPM_OCR);
+               if (ocr & CPM_OCR_EXT_RTC_CLK)
+                       return -EINVAL;
+               return priv->ext_rate / 128;
+       case JZ4730_CLK_UART0:
+       case JZ4730_CLK_UART1:
+       case JZ4730_CLK_UART2:
+       case JZ4730_CLK_UART3:
+       case JZ4730_CLK_I2C:
+       case JZ4730_CLK_TCU:
+               return priv->ext_rate;
+       default:
+               return -EINVAL;
+       }
+}
+
+static const struct clk_ops jz4730_cgu_clk_ops = {
+       .get_rate = jz4730_cgu_clk_get_rate,
+};
+
+static int jz4730_cgu_clk_probe(struct udevice *dev)
+{
+       struct jz4730_cgu_priv *priv = dev_get_priv(dev);
+       struct clk clk;
+       int ret;
+
+       priv->base = dev_remap_addr(dev);
+       if (!priv->base)
+               return -EINVAL;
+
+       ret = clk_get_by_name(dev, "ext", &clk);
+       if (ret)
+               return ret;
+       priv->ext_rate = clk_get_rate(&clk);
+
+       return 0;
+}
+
+static const struct udevice_id jz4730_cgu_clk_of_match[] = {
+       { .compatible = "ingenic,jz4730-cgu" },
+       { },
+};
+
+U_BOOT_DRIVER(jz4730_cgu_clk) = {
+       .name = "jz4730-cgu",
+       .id = UCLASS_CLK,
+       .of_match = jz4730_cgu_clk_of_match,
+       .probe = jz4730_cgu_clk_probe,
+       .priv_auto_alloc_size = sizeof(struct jz4730_cgu_priv),
+       .ops = &jz4730_cgu_clk_ops,
+};
-- 
2.28.0

Reply via email to