clk: Add Allwinner A64 CLK driver
Add initial clock driver for Allwinner A64.
Implement USB clock enable and disable functions for
OHCI, EHCI, OTG and USBPHY gate and clock registers
via ccu clk gate table.
Signed-off-by: Jagan Teki <jagan@amarulasolutions.com>
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index eadf7f8..51c931b 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -104,6 +104,7 @@
source "drivers/clk/mvebu/Kconfig"
source "drivers/clk/owl/Kconfig"
source "drivers/clk/renesas/Kconfig"
+source "drivers/clk/sunxi/Kconfig"
source "drivers/clk/tegra/Kconfig"
source "drivers/clk/uniphier/Kconfig"
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 9acbb1a..6a4ff91 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -22,6 +22,7 @@
obj-$(CONFIG_CLK_MPC83XX) += mpc83xx_clk.o
obj-$(CONFIG_CLK_OWL) += owl/
obj-$(CONFIG_CLK_RENESAS) += renesas/
+obj-$(CONFIG_ARCH_SUNXI) += sunxi/
obj-$(CONFIG_CLK_STM32F) += clk_stm32f.o
obj-$(CONFIG_CLK_STM32MP1) += clk_stm32mp1.o
obj-$(CONFIG_CLK_UNIPHIER) += uniphier/
diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig
new file mode 100644
index 0000000..bf5ecb3
--- /dev/null
+++ b/drivers/clk/sunxi/Kconfig
@@ -0,0 +1,18 @@
+config CLK_SUNXI
+ bool "Clock support for Allwinner SoCs"
+ depends on CLK && ARCH_SUNXI
+ default y
+ help
+ This enables support for common clock driver API on Allwinner
+ SoCs.
+
+if CLK_SUNXI
+
+config CLK_SUN50I_A64
+ bool "Clock driver for Allwinner A64"
+ default MACH_SUN50I
+ help
+ This enables common clock driver support for platforms based
+ on Allwinner A64 SoC.
+
+endif # CLK_SUNXI
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
new file mode 100644
index 0000000..fb20d28
--- /dev/null
+++ b/drivers/clk/sunxi/Makefile
@@ -0,0 +1,9 @@
+#
+# Copyright (C) 2018 Amarula Solutions.
+#
+# SPDX-License-Identifier: GPL-2.0+
+#
+
+obj-$(CONFIG_CLK_SUNXI) += clk_sunxi.o
+
+obj-$(CONFIG_CLK_SUN50I_A64) += clk_a64.o
diff --git a/drivers/clk/sunxi/clk_a64.c b/drivers/clk/sunxi/clk_a64.c
new file mode 100644
index 0000000..803a2f7
--- /dev/null
+++ b/drivers/clk/sunxi/clk_a64.c
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Amarula Solutions.
+ * Author: Jagan Teki <jagan@amarulasolutions.com>
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <errno.h>
+#include <asm/arch/ccu.h>
+#include <dt-bindings/clock/sun50i-a64-ccu.h>
+
+static const struct ccu_clk_gate a64_gates[] = {
+ [CLK_BUS_OTG] = GATE(0x060, BIT(23)),
+ [CLK_BUS_EHCI0] = GATE(0x060, BIT(24)),
+ [CLK_BUS_EHCI1] = GATE(0x060, BIT(25)),
+ [CLK_BUS_OHCI0] = GATE(0x060, BIT(28)),
+ [CLK_BUS_OHCI1] = GATE(0x060, BIT(29)),
+
+ [CLK_USB_PHY0] = GATE(0x0cc, BIT(8)),
+ [CLK_USB_PHY1] = GATE(0x0cc, BIT(9)),
+ [CLK_USB_HSIC] = GATE(0x0cc, BIT(10)),
+ [CLK_USB_HSIC_12M] = GATE(0x0cc, BIT(11)),
+ [CLK_USB_OHCI0] = GATE(0x0cc, BIT(16)),
+ [CLK_USB_OHCI1] = GATE(0x0cc, BIT(17)),
+};
+
+static const struct ccu_desc a64_ccu_desc = {
+ .gates = a64_gates,
+};
+
+static const struct udevice_id a64_ccu_ids[] = {
+ { .compatible = "allwinner,sun50i-a64-ccu",
+ .data = (ulong)&a64_ccu_desc },
+ { }
+};
+
+U_BOOT_DRIVER(clk_sun50i_a64) = {
+ .name = "sun50i_a64_ccu",
+ .id = UCLASS_CLK,
+ .of_match = a64_ccu_ids,
+ .priv_auto_alloc_size = sizeof(struct ccu_priv),
+ .ops = &sunxi_clk_ops,
+ .probe = sunxi_clk_probe,
+};
diff --git a/drivers/clk/sunxi/clk_sunxi.c b/drivers/clk/sunxi/clk_sunxi.c
new file mode 100644
index 0000000..62ce299
--- /dev/null
+++ b/drivers/clk/sunxi/clk_sunxi.c
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2018 Amarula Solutions.
+ * Author: Jagan Teki <jagan@amarulasolutions.com>
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <errno.h>
+#include <asm/io.h>
+#include <asm/arch/ccu.h>
+#include <linux/log2.h>
+
+static const struct ccu_clk_gate *priv_to_gate(struct ccu_priv *priv,
+ unsigned long id)
+{
+ return &priv->desc->gates[id];
+}
+
+static int sunxi_set_gate(struct clk *clk, bool on)
+{
+ struct ccu_priv *priv = dev_get_priv(clk->dev);
+ const struct ccu_clk_gate *gate = priv_to_gate(priv, clk->id);
+ u32 reg;
+
+ if (!(gate->flags & CCU_CLK_F_IS_VALID)) {
+ printf("%s: (CLK#%ld) unhandled\n", __func__, clk->id);
+ return 0;
+ }
+
+ debug("%s: (CLK#%ld) off#0x%x, BIT(%d)\n", __func__,
+ clk->id, gate->off, ilog2(gate->bit));
+
+ reg = readl(priv->base + gate->off);
+ if (on)
+ reg |= gate->bit;
+ else
+ reg &= ~gate->bit;
+
+ writel(reg, priv->base + gate->off);
+
+ return 0;
+}
+
+static int sunxi_clk_enable(struct clk *clk)
+{
+ return sunxi_set_gate(clk, true);
+}
+
+static int sunxi_clk_disable(struct clk *clk)
+{
+ return sunxi_set_gate(clk, false);
+}
+
+struct clk_ops sunxi_clk_ops = {
+ .enable = sunxi_clk_enable,
+ .disable = sunxi_clk_disable,
+};
+
+int sunxi_clk_probe(struct udevice *dev)
+{
+ struct ccu_priv *priv = dev_get_priv(dev);
+
+ priv->base = dev_read_addr_ptr(dev);
+ if (!priv->base)
+ return -ENOMEM;
+
+ priv->desc = (const struct ccu_desc *)dev_get_driver_data(dev);
+ if (!priv->desc)
+ return -EINVAL;
+
+ return 0;
+}