All of lore.kernel.org
 help / color / mirror / Atom feed
* [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL
@ 2017-03-01 21:19 Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 1/9] sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi Philipp Tomsich
                   ` (9 more replies)
  0 siblings, 10 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:19 UTC (permalink / raw)
  To: u-boot

Hi everyone,

here's the the new version of CLK, RESET and PINCTRL drivers to
configure sunxi from the device-tree.  This adds support for the
upstream CCU node (for reset and pinctrl) and tries to address the
various concerns people had.

Note that (to stay in sync with the Linux files), some of the
patches may not fully adhere to the style (e.g. some of the code
reused verbatim from Linux and in the config tables).

This has been tested with Ethernet, I2C, SPI and MMC on the A64-uQ7.
See my separate patchsets for the conversion of these drivers over
to support DM-based CLK, RESET and PINCTRL configuration.

Changes is v3:
 * add support for the 'new-style' clock subsystem (CCU) by porting
   from Linux and adding the necessary glue implementation
   - add a CCU reset-driver (in addition to the legacy driver, which
     will still be required for the R_* device nodes), which reuses
     the reset-table from ccu-<SOC>.c
   - add a clk-sunxi-ccu.c implementation based on Linux and reusing
     both the low-level clock implementation (ccu_*.h) and the config
     table (ccu-<SOC>.c) from Linux
 * reuse the 'allwinner,pinctrl.txt' documentation for the pinctrl
   binding from Linux
 * adds the includes for the CCU dt-bindings from Linux
 * adds support for printing the CCU subsystem via 'clk dump'


Philipp Tomsich (9):
  sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi
  dm: core: Allow multiple drivers to bind for a single node
  sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding
  Kconfig: sunxi: Select new option for allow multiple drivers to bind
  sunxi: add module reset (UCLASS_RESET) support for sunxi
  linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel
  clk: clk-uclass: add clk_get_by_output_name
  sunxi: add clock driver (UCLASS_CLK) support for sunxi
  cmd: move CONFIG_CMD_CLK to Kconfig

 arch/arm/Kconfig                                   |   1 +
 arch/arm/include/asm/arch-sunxi/gpio-internal.h    |  19 +
 cmd/Kconfig                                        |  10 +
 .../pinctrl/allwinner,pinctrl.txt                  | 142 ++++
 drivers/clk/Makefile                               |   1 +
 drivers/clk/clk-uclass.c                           |  30 +
 drivers/clk/sunxi/Makefile                         |  30 +
 drivers/clk/sunxi/ccu-compatibility.h              | 232 ++++++
 drivers/clk/sunxi/ccu-runtime-divider.c            | 115 +++
 drivers/clk/sunxi/ccu-runtime-fixedfactor.c        |  17 +
 drivers/clk/sunxi/ccu-sun50i-a64.c                 | 810 +++++++++++++++++++++
 drivers/clk/sunxi/ccu-sun50i-a64.h                 |  64 ++
 drivers/clk/sunxi/ccu_common.c                     |  27 +
 drivers/clk/sunxi/ccu_common.h                     |  67 ++
 drivers/clk/sunxi/ccu_div.c                        | 134 ++++
 drivers/clk/sunxi/ccu_div.h                        | 170 +++++
 drivers/clk/sunxi/ccu_frac.c                       | 106 +++
 drivers/clk/sunxi/ccu_frac.h                       |  46 ++
 drivers/clk/sunxi/ccu_gate.c                       |  80 ++
 drivers/clk/sunxi/ccu_gate.h                       |  45 ++
 drivers/clk/sunxi/ccu_mp.c                         | 159 ++++
 drivers/clk/sunxi/ccu_mp.h                         |  70 ++
 drivers/clk/sunxi/ccu_mult.h                       |  39 +
 drivers/clk/sunxi/ccu_mux.c                        | 201 +++++
 drivers/clk/sunxi/ccu_mux.h                        | 105 +++
 drivers/clk/sunxi/ccu_nk.c                         | 154 ++++
 drivers/clk/sunxi/ccu_nk.h                         |  64 ++
 drivers/clk/sunxi/ccu_nkm.c                        | 184 +++++
 drivers/clk/sunxi/ccu_nkm.h                        |  84 +++
 drivers/clk/sunxi/ccu_nkmp.c                       | 171 +++++
 drivers/clk/sunxi/ccu_nkmp.h                       |  64 ++
 drivers/clk/sunxi/ccu_nm.c                         | 148 ++++
 drivers/clk/sunxi/ccu_nm.h                         |  84 +++
 drivers/clk/sunxi/clk-sunxi-ccu.c                  | 550 ++++++++++++++
 drivers/clk/sunxi/clk-sunxi-gate.c                 |  92 +++
 drivers/clk/sunxi/clk-sunxi-mod.c                  | 241 ++++++
 drivers/core/Kconfig                               |  14 +
 drivers/core/lists.c                               |  12 +-
 drivers/gpio/sunxi_gpio.c                          |  15 +-
 drivers/pinctrl/Kconfig                            |  10 +
 drivers/pinctrl/Makefile                           |   2 +
 drivers/pinctrl/sunxi/Makefile                     |  10 +
 drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c       |  92 +++
 drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c         | 577 +++++++++++++++
 drivers/pinctrl/sunxi/pinctrl-sunxi.c              | 355 +++++++++
 drivers/pinctrl/sunxi/pinctrl-sunxi.h              | 311 ++++++++
 drivers/reset/Kconfig                              |   9 +
 drivers/reset/Makefile                             |   1 +
 drivers/reset/sunxi/Makefile                       |   6 +
 drivers/reset/sunxi/ccu-sun50i-a64.c               |  75 ++
 drivers/reset/sunxi/ccu_reset.h                    |   9 +
 drivers/reset/sunxi/reset-sunxi.c                  | 168 +++++
 include/clk.h                                      |  22 +
 include/dt-bindings/clock/sun50i-a64-ccu.h         | 134 ++++
 include/dt-bindings/reset/sun50i-a64-ccu.h         |  98 +++
 include/linux/kernel.h                             |   2 +
 56 files changed, 6470 insertions(+), 8 deletions(-)
 create mode 100644 arch/arm/include/asm/arch-sunxi/gpio-internal.h
 create mode 100644 doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt
 create mode 100644 drivers/clk/sunxi/Makefile
 create mode 100644 drivers/clk/sunxi/ccu-compatibility.h
 create mode 100644 drivers/clk/sunxi/ccu-runtime-divider.c
 create mode 100644 drivers/clk/sunxi/ccu-runtime-fixedfactor.c
 create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.c
 create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.h
 create mode 100644 drivers/clk/sunxi/ccu_common.c
 create mode 100644 drivers/clk/sunxi/ccu_common.h
 create mode 100644 drivers/clk/sunxi/ccu_div.c
 create mode 100644 drivers/clk/sunxi/ccu_div.h
 create mode 100644 drivers/clk/sunxi/ccu_frac.c
 create mode 100644 drivers/clk/sunxi/ccu_frac.h
 create mode 100644 drivers/clk/sunxi/ccu_gate.c
 create mode 100644 drivers/clk/sunxi/ccu_gate.h
 create mode 100644 drivers/clk/sunxi/ccu_mp.c
 create mode 100644 drivers/clk/sunxi/ccu_mp.h
 create mode 100644 drivers/clk/sunxi/ccu_mult.h
 create mode 100644 drivers/clk/sunxi/ccu_mux.c
 create mode 100644 drivers/clk/sunxi/ccu_mux.h
 create mode 100644 drivers/clk/sunxi/ccu_nk.c
 create mode 100644 drivers/clk/sunxi/ccu_nk.h
 create mode 100644 drivers/clk/sunxi/ccu_nkm.c
 create mode 100644 drivers/clk/sunxi/ccu_nkm.h
 create mode 100644 drivers/clk/sunxi/ccu_nkmp.c
 create mode 100644 drivers/clk/sunxi/ccu_nkmp.h
 create mode 100644 drivers/clk/sunxi/ccu_nm.c
 create mode 100644 drivers/clk/sunxi/ccu_nm.h
 create mode 100644 drivers/clk/sunxi/clk-sunxi-ccu.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-gate.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-mod.c
 create mode 100644 drivers/pinctrl/sunxi/Makefile
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.h
 create mode 100644 drivers/reset/sunxi/Makefile
 create mode 100644 drivers/reset/sunxi/ccu-sun50i-a64.c
 create mode 100644 drivers/reset/sunxi/ccu_reset.h
 create mode 100644 drivers/reset/sunxi/reset-sunxi.c
 create mode 100644 include/dt-bindings/clock/sun50i-a64-ccu.h
 create mode 100644 include/dt-bindings/reset/sun50i-a64-ccu.h

-- 
1.9.1

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 1/9] sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 2/9] dm: core: Allow multiple drivers to bind for a single node Philipp Tomsich
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

This change adds a full device-model pinctrl driver for sunxi (tested with
sun50iw1p1) based on the support available in Linux.

Details are:
 * implements a driver for pinctrl devices and assigns sun50i-a64-pinctrl
   and sun50i-a64-r-pinctrl to it
 * dynamically creates the driver_data for a sunxi_gpio (see sunxi_gpio.c)
   driver and binds it to the same device-tree node
 * lifts and reuses the pinctrl-sunxi.h and pinctrl-sun50i-a64.c files from
   Linux (thanks to Maxime and Andre) and adds a pinctrl-sun50i-a64-r.c (to
   be picked up for inclusion into Linux again)

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 arch/arm/include/asm/arch-sunxi/gpio-internal.h    |  19 +
 .../pinctrl/allwinner,pinctrl.txt                  | 142 +++++
 drivers/gpio/sunxi_gpio.c                          |  15 +-
 drivers/pinctrl/Kconfig                            |  10 +
 drivers/pinctrl/Makefile                           |   2 +
 drivers/pinctrl/sunxi/Makefile                     |  10 +
 drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c       |  92 ++++
 drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c         | 577 +++++++++++++++++++++
 drivers/pinctrl/sunxi/pinctrl-sunxi.c              | 351 +++++++++++++
 drivers/pinctrl/sunxi/pinctrl-sunxi.h              | 311 +++++++++++
 10 files changed, 1522 insertions(+), 7 deletions(-)
 create mode 100644 arch/arm/include/asm/arch-sunxi/gpio-internal.h
 create mode 100644 doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt
 create mode 100644 drivers/pinctrl/sunxi/Makefile
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.c
 create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.h

diff --git a/arch/arm/include/asm/arch-sunxi/gpio-internal.h b/arch/arm/include/asm/arch-sunxi/gpio-internal.h
new file mode 100644
index 0000000..4dcdd34
--- /dev/null
+++ b/arch/arm/include/asm/arch-sunxi/gpio-internal.h
@@ -0,0 +1,19 @@
+/*
+ * (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+
+#ifndef _SUNXI_GPIO_INTERNAL_H
+#define _SUNXI_GPIO_INTERNAL_H
+
+/* This data structure is shared between the sunxi_gpio driver and
+ * the sunxi_pinctrl driver.
+ */
+struct sunxi_gpio_soc_data {
+	int start;
+	int no_banks;
+};
+
+#endif
diff --git a/doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt b/doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt
new file mode 100644
index 0000000..de1378b
--- /dev/null
+++ b/doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt
@@ -0,0 +1,142 @@
+* Allwinner A1X Pin Controller
+
+The pins controlled by sunXi pin controller are organized in banks,
+each bank has 32 pins.  Each pin has 7 multiplexing functions, with
+the first two functions being GPIO in and out. The configuration on
+the pins includes drive strength and pull-up.
+
+Required properties:
+- compatible: Should be one of the followings (depending on you SoC):
+  "allwinner,sun4i-a10-pinctrl"
+  "allwinner,sun5i-a10s-pinctrl"
+  "allwinner,sun5i-a13-pinctrl"
+  "allwinner,sun6i-a31-pinctrl"
+  "allwinner,sun6i-a31s-pinctrl"
+  "allwinner,sun6i-a31-r-pinctrl"
+  "allwinner,sun7i-a20-pinctrl"
+  "allwinner,sun8i-a23-pinctrl"
+  "allwinner,sun8i-a23-r-pinctrl"
+  "allwinner,sun8i-a33-pinctrl"
+  "allwinner,sun9i-a80-pinctrl"
+  "allwinner,sun9i-a80-r-pinctrl"
+  "allwinner,sun8i-a83t-pinctrl"
+  "allwinner,sun8i-h3-pinctrl"
+  "allwinner,sun8i-h3-r-pinctrl"
+  "allwinner,sun50i-a64-pinctrl"
+  "nextthing,gr8-pinctrl"
+
+- reg: Should contain the register physical address and length for the
+  pin controller.
+
+- clocks: phandle to the clocks feeding the pin controller:
+  - "apb": the gated APB parent clock
+  - "hosc": the high frequency oscillator in the system
+  - "losc": the low frequency oscillator in the system
+
+Note: For backward compatibility reasons, the hosc and losc clocks are only
+required if you need to use the optional input-debounce property. Any new
+device tree should set them.
+
+Optional properties:
+  - input-debounce: Array of debouncing periods in microseconds. One period per
+    irq bank found in the controller. 0 if no setup required.
+
+
+Please refer to pinctrl-bindings.txt in this directory for details of the
+common pinctrl bindings used by client devices.
+
+A pinctrl node should contain at least one subnodes representing the
+pinctrl groups available on the machine. Each subnode will list the
+pins it needs, and how they should be configured, with regard to muxer
+configuration, drive strength and pullups. If one of these options is
+not set, its actual value will be unspecified.
+
+This driver supports the generic pin multiplexing and configuration
+bindings. For details on each properties, you can refer to
+./pinctrl-bindings.txt.
+
+Required sub-node properties:
+  - pins
+  - function
+
+Optional sub-node properties:
+  - bias-disable
+  - bias-pull-up
+  - bias-pull-down
+  - drive-strength
+
+*** Deprecated pin configuration and multiplexing binding
+
+Required subnode-properties:
+
+- allwinner,pins: List of strings containing the pin name.
+- allwinner,function: Function to mux the pins listed above to.
+
+Optional subnode-properties:
+- allwinner,drive: Integer. Represents the current sent to the pin
+    0: 10 mA
+    1: 20 mA
+    2: 30 mA
+    3: 40 mA
+- allwinner,pull: Integer.
+    0: No resistor
+    1: Pull-up resistor
+    2: Pull-down resistor
+
+Examples:
+
+pio: pinctrl at 01c20800 {
+	compatible = "allwinner,sun5i-a13-pinctrl";
+	reg = <0x01c20800 0x400>;
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	uart1_pins_a: uart1 at 0 {
+		allwinner,pins = "PE10", "PE11";
+		allwinner,function = "uart1";
+		allwinner,drive = <0>;
+		allwinner,pull = <0>;
+	};
+
+	uart1_pins_b: uart1 at 1 {
+		allwinner,pins = "PG3", "PG4";
+		allwinner,function = "uart1";
+		allwinner,drive = <0>;
+		allwinner,pull = <0>;
+	};
+};
+
+
+GPIO and interrupt controller
+-----------------------------
+
+This hardware also acts as a GPIO controller and an interrupt
+controller.
+
+Consumers that would want to refer to one or the other (or both)
+should provide through the usual *-gpios and interrupts properties a
+cell with 3 arguments, first the number of the bank, then the pin
+inside that bank, and finally the flags for the GPIO/interrupts.
+
+Example:
+
+xio: gpio at 38 {
+	compatible = "nxp,pcf8574a";
+	reg = <0x38>;
+
+	gpio-controller;
+	#gpio-cells = <2>;
+
+	interrupt-parent = <&pio>;
+	interrupts = <6 0 IRQ_TYPE_EDGE_FALLING>;
+	interrupt-controller;
+	#interrupt-cells = <2>;
+};
+
+reg_usb1_vbus: usb1-vbus {
+	compatible = "regulator-fixed";
+	regulator-name = "usb1-vbus";
+	regulator-min-microvolt = <5000000>;
+	regulator-max-microvolt = <5000000>;
+	gpio = <&pio 7 6 GPIO_ACTIVE_HIGH>;
+};
diff --git a/drivers/gpio/sunxi_gpio.c b/drivers/gpio/sunxi_gpio.c
index 2b7bc7f..fd0c1ac 100644
--- a/drivers/gpio/sunxi_gpio.c
+++ b/drivers/gpio/sunxi_gpio.c
@@ -16,6 +16,7 @@
 #include <fdtdec.h>
 #include <malloc.h>
 #include <asm/arch/gpio.h>
+#include <asm/arch/gpio-internal.h>
 #include <asm/io.h>
 #include <asm/gpio.h>
 #include <dm/device-internal.h>
@@ -275,11 +276,6 @@ static int gpio_sunxi_probe(struct udevice *dev)
 	return 0;
 }
 
-struct sunxi_gpio_soc_data {
-	int start;
-	int no_banks;
-};
-
 /**
  * We have a top-level GPIO device with no actual GPIOs. It has a child
  * device for each Sunxi bank.
@@ -353,18 +349,22 @@ static const struct udevice_id sunxi_gpio_ids[] = {
 	ID("allwinner,sun8i-a83t-pinctrl",	a_all),
 	ID("allwinner,sun8i-h3-pinctrl",	a_all),
 	ID("allwinner,sun9i-a80-pinctrl",	a_all),
+#if !defined(CONFIG_SUNXI_PINCTRL)
 	/* This is not strictly correct for the A64, as it is missing
 	 * bank 'A'. Yet, the register layout in the pinctrl block is
 	 * backward compatible and any accesses to the registers that
 	 * normally control bank 'A' will have no adverse effect.
 	 */
-	ID("allwinner,sun50i-a64-pinctrl",      a_all),
+	ID("allwinner,sun50i-a64-pinctrl",	a_all),
+#endif
 	ID("allwinner,sun6i-a31-r-pinctrl",	l_2),
 	ID("allwinner,sun8i-a23-r-pinctrl",	l_1),
 	ID("allwinner,sun8i-a83t-r-pinctrl",	l_1),
 	ID("allwinner,sun8i-h3-r-pinctrl",	l_1),
 	ID("allwinner,sun9i-a80-r-pinctrl",	l_3),
-	ID("allwinner,sun50i-a64-r-pinctrl",    l_1),
+#if !defined(CONFIG_SUNXI_PINCTRL)
+	ID("allwinner,sun50i-a64-r-pinctrl",	l_1),
+#endif
 	{ }
 };
 
@@ -376,4 +376,5 @@ U_BOOT_DRIVER(gpio_sunxi) = {
 	.bind	= gpio_sunxi_bind,
 	.probe	= gpio_sunxi_probe,
 };
+
 #endif
diff --git a/drivers/pinctrl/Kconfig b/drivers/pinctrl/Kconfig
index efcb4c0..064a682 100644
--- a/drivers/pinctrl/Kconfig
+++ b/drivers/pinctrl/Kconfig
@@ -175,6 +175,16 @@ config PIC32_PINCTRL
 	  by a device tree node which contains both GPIO defintion and pin control
 	  functions.
 
+config SUNXI_PINCTRL
+        bool "Allwinner Axx pin-control and pin-mux driver"
+	depends on DM && ARCH_SUNXI
+	default y
+	help
+	  Supports pin multiplexing control, drive-strength and bias control on
+	  Allwinner Axx SoCs. The driver is controlled by a device tree node which
+	  contains both the GPIO definitions and the pin control functions for
+	  each multiplex function.
+
 endif
 
 source "drivers/pinctrl/meson/Kconfig"
diff --git a/drivers/pinctrl/Makefile b/drivers/pinctrl/Makefile
index 512112a..da27a91 100644
--- a/drivers/pinctrl/Makefile
+++ b/drivers/pinctrl/Makefile
@@ -16,3 +16,5 @@ obj-$(CONFIG_PIC32_PINCTRL)	+= pinctrl_pic32.o
 obj-$(CONFIG_PINCTRL_EXYNOS)	+= exynos/
 obj-$(CONFIG_PINCTRL_MESON)	+= meson/
 obj-$(CONFIG_PINCTRL_MVEBU)	+= mvebu/
+
+obj-$(CONFIG_ARCH_SUNXI)        += sunxi/
\ No newline at end of file
diff --git a/drivers/pinctrl/sunxi/Makefile b/drivers/pinctrl/sunxi/Makefile
new file mode 100644
index 0000000..11549ec
--- /dev/null
+++ b/drivers/pinctrl/sunxi/Makefile
@@ -0,0 +1,10 @@
+#
+# Copyright (c) 2017 Theobroma Systems Design und Consulting GmbH
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+obj-$(CONFIG_SUNXI_PINCTRL) += pinctrl-sunxi.o
+ifdef CONFIG_SUNXI_PINCTRL
+obj-$(CONFIG_MACH_SUN50I)   += pinctrl-sun50i-a64.o pinctrl-sun50i-a64-r.o
+endif
diff --git a/drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c b/drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c
new file mode 100644
index 0000000..864d1ec
--- /dev/null
+++ b/drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c
@@ -0,0 +1,92 @@
+/*
+ * Allwinner A64 SoCs pinctrl driver.
+ *
+ * Copyright (C) 2017 Theobroma Systems Design und Consulting GmbH.
+ *
+ * Based on pinctrl-sun7i-a20.c, which is:
+ * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <common.h>
+
+#include "pinctrl-sunxi.h"
+
+static const struct sunxi_desc_pin a64_r_pins[] = {
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_rsb"),		/* SCK */
+		  SUNXI_FUNCTION(0x3, "s_i2c"),		/* SCK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)),	/* EINT0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_rsb"),		/* SDA */
+		  SUNXI_FUNCTION(0x3, "s_i2c"),		/* SDA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 1)),	/* EINT1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_uart"),	/* TX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 2)),	/* EINT2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_uart"),	/* RX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 3)),	/* EINT3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_jtag"),        /* MS */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 4)),	/* EINT4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_jtag"),        /* CK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 5)),	/* EINT5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_jtag"),        /* DO */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 6)),	/* EINT6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_jtag"),        /* DI */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 7)),	/* EINT7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_i2c"),         /* SCK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 8)),	/* EINT8 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_i2c"),         /* SDA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 9)),	/* EINT9 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_pwm"),
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 10)),	/* EINT10 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "s_cir"),
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 11)),	/* EINT11 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(L, 12),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 12)),	/* EINT12 */
+};
+
+const struct sunxi_pinctrl_desc a64_r_pinctrl_data = {
+	.pins = a64_r_pins,
+	.npins = ARRAY_SIZE(a64_r_pins),
+	.pin_base = PL_BASE,
+	.irq_banks = 1,
+};
diff --git a/drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c b/drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c
new file mode 100644
index 0000000..7abea03
--- /dev/null
+++ b/drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c
@@ -0,0 +1,577 @@
+/*
+ * Allwinner A64 SoCs pinctrl driver.
+ *
+ * Copyright (C) 2016 - ARM Ltd.
+ * Author: Andre Przywara <andre.przywara@arm.com>
+ *
+ * Based on pinctrl-sun7i-a20.c, which is:
+ * Copyright (C) 2014 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#include <common.h>
+
+#include "pinctrl-sunxi.h"
+
+static const struct sunxi_desc_pin a64_pins[] = {
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart2"),		/* TX */
+		  SUNXI_FUNCTION(0x4, "jtag"),		/* MS0 */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 0)),	/* EINT0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart2"),		/* RX */
+		  SUNXI_FUNCTION(0x4, "jtag"),		/* CK0 */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* VCCEN */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 1)),	/* EINT1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart2"),		/* RTS */
+		  SUNXI_FUNCTION(0x4, "jtag"),		/* DO0 */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* VPPEN */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 2)),	/* EINT2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart2"),		/* CTS */
+		  SUNXI_FUNCTION(0x3, "i2s0"),		/* MCLK */
+		  SUNXI_FUNCTION(0x4, "jtag"),		/* DI0 */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* VPPPP */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 3)),	/* EINT3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif2"),		/* SYNC */
+		  SUNXI_FUNCTION(0x3, "i2s0"),		/* SYNC */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* CLK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 4)),	/* EINT4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif2"),		/* BCLK */
+		  SUNXI_FUNCTION(0x3, "i2s0"),		/* BCLK */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* DATA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 5)),		/* EINT5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif2"),		/* DOUT */
+		  SUNXI_FUNCTION(0x3, "i2s0"),		/* DOUT */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* RST */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 6)),	/* EINT6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif2"),		/* DIN */
+		  SUNXI_FUNCTION(0x3, "i2s0"),		/* DIN */
+		  SUNXI_FUNCTION(0x5, "sim"),		/* DET */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 7)),	/* EINT7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x4, "uart0"),		/* TX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 8)),	/* EINT8 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(B, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x4, "uart0"),		/* RX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 0, 9)),	/* EINT9 */
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NWE */
+		  SUNXI_FUNCTION(0x4, "spi0")),		/* MOSI */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NALE */
+		  SUNXI_FUNCTION(0x3, "mmc2"),		/* DS */
+		  SUNXI_FUNCTION(0x4, "spi0")),		/* MISO */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NCLE */
+		  SUNXI_FUNCTION(0x4, "spi0")),		/* SCK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NCE1 */
+		  SUNXI_FUNCTION(0x4, "spi0")),		/* CS */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0")),	/* NCE0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NRE# */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* CLK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NRB0 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* CMD */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0")),	/* NRB1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ0 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ1 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ2 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ3 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 12),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ4 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 13),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ5 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 14),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ6 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 15),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQ7 */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* D7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(C, 16),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "nand0"),		/* NDQS */
+		  SUNXI_FUNCTION(0x3, "mmc2")),		/* RST */
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D2 */
+		  SUNXI_FUNCTION(0x3, "uart3"),		/* TX */
+		  SUNXI_FUNCTION(0x4, "spi1"),		/* CS */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* CLK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D3 */
+		  SUNXI_FUNCTION(0x3, "uart3"),		/* RX */
+		  SUNXI_FUNCTION(0x4, "spi1"),		/* CLK */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* DE */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D4 */
+		  SUNXI_FUNCTION(0x3, "uart4"),		/* TX */
+		  SUNXI_FUNCTION(0x4, "spi1"),		/* MOSI */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* HSYNC */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D5 */
+		  SUNXI_FUNCTION(0x3, "uart4"),		/* RX */
+		  SUNXI_FUNCTION(0x4, "spi1"),		/* MISO */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* VSYNC */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D6 */
+		  SUNXI_FUNCTION(0x3, "uart4"),		/* RTS */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D7 */
+		  SUNXI_FUNCTION(0x3, "uart4"),		/* CTS */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D10 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D11 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D12 */
+		  SUNXI_FUNCTION(0x4, "emac"),		/* ERXD3 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D13 */
+		  SUNXI_FUNCTION(0x4, "emac"),		/* ERXD2 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D14 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ERXD1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D15 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ERXD0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 12),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D18 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VP0 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ERXCK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 13),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D19 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VN0 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ERXCTL */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 14),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D20 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VP1 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ENULL */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 15),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D21 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VN1 */
+		  SUNXI_FUNCTION(0x4, "emac"),		/* ETXD3 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 16),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D22 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VP2 */
+		  SUNXI_FUNCTION(0x4, "emac"),		/* ETXD2 */
+		  SUNXI_FUNCTION(0x5, "ccir")),		/* D7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 17),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* D23 */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VN2 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ETXD1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 18),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* CLK */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VPC */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ETXD0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 19),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* DE */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VNC */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ETXCK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 20),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* HSYNC */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VP3 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ETXCTL */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 21),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "lcd0"),		/* VSYNC */
+		  SUNXI_FUNCTION(0x3, "lvds0"),		/* VN3 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* ECLKIN */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 22),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "pwm"),		/* PWM0 */
+		  SUNXI_FUNCTION(0x4, "emac")),		/* EMDC */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 23),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x4, "emac")),		/* EMDIO */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(D, 24),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out")),
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* PCK */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* CLK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* CK */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* ERR */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* HSYNC */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* SYNC */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* VSYNC */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* DVLD */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D0 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D1 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D2 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D3 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D4 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D5 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D6 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0"),		/* D7 */
+		  SUNXI_FUNCTION(0x4, "ts0")),		/* D7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 12),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0")),		/* SCK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 13),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "csi0")),		/* SDA */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 14),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "pll"),		/* LOCK_DBG */
+		  SUNXI_FUNCTION(0x3, "i2c2")),		/* SCK */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 15),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x3, "i2c2")),		/* SDA */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 16),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out")),
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(E, 17),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out")),
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* D1 */
+		  SUNXI_FUNCTION(0x3, "jtag")),		/* MSI */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* D0 */
+		  SUNXI_FUNCTION(0x3, "jtag")),		/* DI1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* CLK */
+		  SUNXI_FUNCTION(0x3, "uart0")),	/* TX */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* CMD */
+		  SUNXI_FUNCTION(0x3, "jtag")),		/* DO1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* D3 */
+		  SUNXI_FUNCTION(0x4, "uart0")),	/* RX */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc0"),		/* D2 */
+		  SUNXI_FUNCTION(0x3, "jtag")),		/* CK1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(F, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out")),
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* CLK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 0)),	/* EINT0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* CMD */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 1)),	/* EINT1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* D0 */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 2)),	/* EINT2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* D1 */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 3)),	/* EINT3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* D2 */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 4)),	/* EINT4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mmc1"),		/* D3 */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 5)),	/* EINT5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart1"),		/* TX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 6)),	/* EINT6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart1"),		/* RX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 7)),	/* EINT7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart1"),		/* RTS */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 8)),	/* EINT8 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart1"),		/* CTS */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 9)),	/* EINT9 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif3"),		/* SYNC */
+		  SUNXI_FUNCTION(0x3, "i2s1"),		/* SYNC */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 10)),	/* EINT10 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif3"),		/* BCLK */
+		  SUNXI_FUNCTION(0x3, "i2s1"),		/* BCLK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 11)),	/* EINT11 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 12),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif3"),		/* DOUT */
+		  SUNXI_FUNCTION(0x3, "i2s1"),		/* DOUT */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 12)),	/* EINT12 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(G, 13),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "aif3"),		/* DIN */
+		  SUNXI_FUNCTION(0x3, "i2s1"),		/* DIN */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 1, 13)),	/* EINT13 */
+	/* Hole */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 0),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "i2c0"),		/* SCK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 0)),	/* EINT0 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 1),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "i2c0"),		/* SDA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 1)),	/* EINT1 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 2),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "i2c1"),		/* SCK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 2)),	/* EINT2 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 3),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "i2c1"),		/* SDA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 3)),	/* EINT3 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 4),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart3"),		/* TX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 4)),	/* EINT4 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 5),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart3"),		/* RX */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 5)),	/* EINT5 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 6),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart3"),		/* RTS */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 6)),	/* EINT6 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 7),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "uart3"),		/* CTS */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 7)),	/* EINT7 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 8),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "spdif"),		/* OUT */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 8)),	/* EINT8 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 9),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 9)),	/* EINT9 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 10),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mic"),		/* CLK */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 10)),	/* EINT10 */
+	SUNXI_PIN(SUNXI_PINCTRL_PIN(H, 11),
+		  SUNXI_FUNCTION(0x0, "gpio_in"),
+		  SUNXI_FUNCTION(0x1, "gpio_out"),
+		  SUNXI_FUNCTION(0x2, "mic"),		/* DATA */
+		  SUNXI_FUNCTION_IRQ_BANK(0x6, 2, 11)),	/* EINT11 */
+};
+
+const struct sunxi_pinctrl_desc a64_pinctrl_data = {
+	.pins = a64_pins,
+	.npins = ARRAY_SIZE(a64_pins),
+	.irq_banks = 3,
+};
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
new file mode 100644
index 0000000..1e659fc
--- /dev/null
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
@@ -0,0 +1,351 @@
+/*
+ * (C) Copyright 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * In parts based on linux/drivers/pinctrl/pinctrl-sunxi.c, which is
+ *   Copyright (C) 2012 Maxime Ripard
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+
+
+#include <common.h>
+#include <dm.h>
+#include <errno.h>
+#include <syscon.h>
+#include <asm/io.h>
+#include <asm/arch/clock.h>
+#include <asm/gpio.h>
+#include <asm/arch/gpio-internal.h>
+#include <dm/device-internal.h>
+#include <dm/lists.h>
+#include <dm/pinctrl.h>
+#include <dt-bindings/pinctrl/sun4i-a10.h>
+#include <dt-bindings/gpio/gpio.h>
+#include <linux/kernel.h>
+#include "pinctrl-sunxi.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_pctrl_priv {
+	void *base;
+#if defined(CONFIG_DM_GPIO)
+	struct sunxi_gpio_soc_data gpio_soc_data;
+	struct udevice *gpio_dev;
+#endif
+};
+
+static int sunxi_pctrl_parse_drive_prop(const void *blob, int node)
+{
+	int val;
+
+	/* Try the new style binding */
+	val = fdtdec_get_int(blob, node, "drive-strength", -EINVAL);
+	if (val >= 0) {
+		/* We can't go below 10mA ... */
+		if (val < 10)
+			return -EINVAL;
+
+		/* ... and only up to 40 mA ... */
+		if (val > 40)
+			val = 40;
+
+		/* by steps of 10 mA */
+		return rounddown(val, 10);
+	}
+
+	/* And then fall back to the old binding */
+	val = fdtdec_get_int(blob, node, "allwinner,drive", -EINVAL);
+	if (val < 0)
+		return -EINVAL;
+
+	return (val + 1) * 10;
+}
+
+static int sunxi_pctrl_parse_bias_prop(const void *blob, int node)
+{
+	/* Try the new style binding */
+	if (fdtdec_get_bool(blob, node, "bias-pull-up"))
+		return SUN4I_PINCTRL_PULL_UP;
+
+	if (fdtdec_get_bool(blob, node, "bias-pull-down"))
+		return SUN4I_PINCTRL_PULL_DOWN;
+
+	if (fdtdec_get_bool(blob, node, "bias-disable"))
+		return SUN4I_PINCTRL_NO_PULL;
+
+	/* And fall back to the old binding */
+	return fdtdec_get_int(blob, node, "allwinner,pull", -EINVAL);
+}
+
+static const struct sunxi_desc_pin *sunxi_pctrl_pin_by_name(struct udevice *dev,
+							    const char *name)
+{
+	const struct sunxi_pinctrl_desc *data =
+		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
+	int i;
+
+	for (i = 0; i < data->npins; ++i)
+		if (!strcmp(data->pins[i].pin.name, name))
+			return &data->pins[i];
+
+	return NULL;
+}
+
+static int sunxi_pctrl_muxval_by_name(const struct sunxi_desc_pin *pin,
+				      const char *name)
+{
+	const struct sunxi_desc_function *func;
+
+	if (!pin)
+		return -EINVAL;
+
+	for (func = pin->functions; func->name; func++)
+		if (!strcmp(func->name, name))
+			return func->muxval;
+
+	return -ENOENT;
+}
+
+static void sunxi_pctrl_set_function(struct udevice *dev,
+				     unsigned pin, int function)
+{
+	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
+	const struct sunxi_pinctrl_desc *data =
+		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
+	u32 val, mask;
+
+	if (function < 0)
+		return;
+
+	pin -= data->pin_base;
+	mask = MUX_PINS_MASK << sunxi_mux_offset(pin);
+	val = function << sunxi_mux_offset(pin);
+	clrsetbits_le32(priv->base + sunxi_mux_reg(pin), mask, val);
+}
+
+static void sunxi_pctrl_set_dlevel(struct udevice *dev,
+				   unsigned pin, int dlevel)
+{
+	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
+	const struct sunxi_pinctrl_desc *data =
+		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
+	u32 val, mask;
+
+	if (dlevel < 0)
+		return;
+
+	pin -= data->pin_base;
+	mask = DLEVEL_PINS_MASK << sunxi_dlevel_offset(pin);
+	val = dlevel << sunxi_dlevel_offset(pin);
+	clrsetbits_le32(priv->base + sunxi_dlevel_reg(pin), mask, val);
+}
+
+static void sunxi_pctrl_set_bias(struct udevice *dev,
+				 unsigned pin, int bias)
+{
+	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
+	const struct sunxi_pinctrl_desc *data =
+		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
+	u32 val, mask;
+
+	if (bias < 0)
+		return;
+
+	pin -= data->pin_base;
+	mask = PULL_PINS_MASK << sunxi_pull_offset(pin);
+	val = bias << sunxi_pull_offset(pin);
+	clrsetbits_le32(priv->base + sunxi_pull_reg(pin), mask, val);
+}
+
+static const char *sunxi_pctrl_find_pins_prop(const char *blob,
+					      int node,
+					      int *npins)
+{
+	int count;
+
+	/* Try the generic binding */
+	count = fdt_stringlist_count(blob, node, "pins");
+	if (count > 0) {
+		*npins = count;
+		return "pins";
+	}
+
+	/* And fall back to our legacy one */
+	count = fdt_stringlist_count(blob, node, "allwinner,pins");
+	if (count > 0) {
+		*npins = count;
+		return "allwinner,pins";
+	}
+
+	return NULL;
+}
+
+static const char* sunxi_pctrl_parse_function(const char *blob, int node)
+{
+	const char *function = NULL;
+
+	/* Try the generic binding */
+	function = fdt_getprop(blob, node, "function", NULL);
+
+	/* And fall back to our legacy one */
+	if (!function)
+		function = fdt_getprop(blob, node, "allwinner,function", NULL);
+
+	return function;
+}
+
+static int sunxi_pctrl_set_state(struct udevice *dev, struct udevice *config)
+{
+	const void *blob = gd->fdt_blob;
+	int node = config->of_offset;
+	const char *pin_prop;
+	const char *function;
+	int drive, bias;
+	int i, npins;
+
+	debug("%s: %s %s\n", __func__, dev->name, config->name);
+
+	pin_prop = sunxi_pctrl_find_pins_prop(blob, node, &npins);
+	if (!pin_prop) {
+		debug("%s: missing pins property in node %s\n",
+		      dev->name, config->name);
+		return -EINVAL;
+	}
+
+	function = sunxi_pctrl_parse_function(blob, node);
+	if (!function) {
+		debug("%s: missing allwinner,function property in node %s\n",
+		      dev->name, config->name);
+		return -EINVAL;
+	}
+
+	drive = sunxi_pctrl_parse_drive_prop(blob, node);
+	bias = sunxi_pctrl_parse_bias_prop(blob, node);
+
+	debug("%s: function %s, drive %d, bias %d\n",
+	      config->name, function, drive, bias);
+
+	for (i = 0; i < npins; ++i) {
+		const struct sunxi_desc_pin *pin;
+		const char *pin_name =
+			fdt_stringlist_get(blob, node, pin_prop, i, NULL);
+		int muxval;
+
+		if (!pin_name)
+			continue;
+
+		pin = sunxi_pctrl_pin_by_name(dev, pin_name);
+		if (!pin) {
+			debug("%s: unknown pin %s\n", dev->name, pin_name);
+			continue;
+		}
+
+		muxval = sunxi_pctrl_muxval_by_name(pin, function);
+
+		sunxi_pctrl_set_function(dev, pin->pin.number, muxval);
+		sunxi_pctrl_set_dlevel(dev, pin->pin.number, drive);
+		sunxi_pctrl_set_bias(dev, pin->pin.number, bias);
+	}
+
+	return 0;
+}
+
+static inline void soc_data_from_desc(const struct sunxi_pinctrl_desc *data,
+				      struct sunxi_gpio_soc_data *soc_data)
+{
+	int i;
+	unsigned pinnum;
+	unsigned low = data->pin_base / PINS_PER_BANK;
+	unsigned high = data->pin_base;
+
+	for (i = 0; i < data->npins; ++i) {
+		pinnum = data->pins[i].pin.number;
+		high = max(high, pinnum);
+	}
+
+	/* convert pin-numbers to bank numbers */
+	high /= PINS_PER_BANK;
+
+	soc_data->start = low;
+	soc_data->no_banks = high - low + 1;
+}
+
+static int sunxi_pctrl_bind_gpio(struct udevice *dev)
+{
+#if defined(CONFIG_DM_GPIO)
+	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
+	const struct sunxi_pinctrl_desc *data =
+		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
+	struct driver *gpio_driver;
+	char name[20];
+	int ret;
+
+	/* Fill the soc_data for the gpio driver from the pinctrl_desc */
+	soc_data_from_desc(data, &priv->gpio_soc_data);
+
+	gpio_driver = lists_driver_lookup_name("gpio_sunxi");
+	if (!gpio_driver)
+		return -ENOENT;
+
+	ret = device_bind_with_driver_data(dev, gpio_driver, "sunxi_gpio",
+					   (ulong)&priv->gpio_soc_data,
+					   dev->of_offset, &priv->gpio_dev);
+
+	if (ret < 0)
+		return ret;
+
+	snprintf(name, sizeof(name), "sunxi_gpio@%x", (uint32_t)priv->base);
+	device_set_name(priv->gpio_dev, name);
+#endif
+
+	return 0;
+}
+
+static int sunxi_pctrl_probe(struct udevice *dev)
+{
+	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr_base;
+	fdt_size_t size;
+
+	addr_base = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob,
+						       dev->of_offset,
+						       "reg", 0, &size,
+						       false);
+	if (addr_base == FDT_ADDR_T_NONE)
+		return -EINVAL;
+
+	priv->base = (void *)addr_base;
+
+	return sunxi_pctrl_bind_gpio(dev);
+}
+
+static struct pinctrl_ops sunxi_pctrl_ops = {
+	.set_state	  = sunxi_pctrl_set_state,
+};
+
+#if defined(CONFIG_MACH_SUN50I)
+extern const struct sunxi_pinctrl_desc a64_pinctrl_data;
+extern const struct sunxi_pinctrl_desc a64_r_pinctrl_data;
+#endif
+
+static const struct udevice_id sunxi_pctrl_ids[] = {
+#if defined(CONFIG_MACH_SUN50I)
+	{ .compatible = "allwinner,sun50i-a64-pinctrl",
+	  .data = (ulong)&a64_pinctrl_data },
+	{ .compatible = "allwinner,sun50i-a64-r-pinctrl",
+	  .data = (ulong)&a64_r_pinctrl_data },
+#endif
+	{ }
+};
+
+U_BOOT_DRIVER(pinctrl_sunxi) = {
+	.name		= "sunxi_pctrl",
+	.id		= UCLASS_PINCTRL,
+	.of_match	= sunxi_pctrl_ids,
+	.priv_auto_alloc_size = sizeof(struct sunxi_pctrl_priv),
+	.ops		= &sunxi_pctrl_ops,
+	.bind		= dm_scan_fdt_dev,
+	.probe		= sunxi_pctrl_probe,
+};
+
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h
new file mode 100644
index 0000000..8508626
--- /dev/null
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h
@@ -0,0 +1,311 @@
+/*
+ * Allwinner A1X SoCs pinctrl driver.
+ *
+ * Copyright (C) 2012 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is licensed under the terms of the GNU General Public
+ * License version 2.  This program is licensed "as is" without any
+ * warranty of any kind, whether express or implied.
+ */
+
+#ifndef __PINCTRL_SUNXI_H
+#define __PINCTRL_SUNXI_H
+
+#define PA_BASE	0
+#define PB_BASE	32
+#define PC_BASE	64
+#define PD_BASE	96
+#define PE_BASE	128
+#define PF_BASE	160
+#define PG_BASE	192
+#define PH_BASE	224
+#define PI_BASE	256
+#define PL_BASE	352
+#define PM_BASE	384
+#define PN_BASE	416
+
+#ifdef __UBOOT__
+/* Convenience macro to define a single named or anonymous pin descriptor */
+#define PINCTRL_PIN(a, b) { .number = a, .name = b }
+
+/**
+ * struct pinctrl_pin_desc - boards/machines provide information on their
+ * pins, pads or other muxable units in this struct
+ * @number: unique pin number from the global pin number space
+ * @name: a name for this pin
+ * @drv_data: driver-defined per-pin data. pinctrl core does not touch this
+ */
+struct pinctrl_pin_desc {
+	unsigned number;
+	const char *name;
+#ifndef __UBOOT__
+	void *drv_data;
+#endif
+};
+#endif
+
+#define SUNXI_PINCTRL_PIN(bank, pin)		\
+	PINCTRL_PIN(P ## bank ## _BASE + (pin), "P" #bank #pin)
+
+#define SUNXI_PIN_NAME_MAX_LEN	5
+
+#define BANK_MEM_SIZE		0x24
+#define MUX_REGS_OFFSET		0x0
+#define DATA_REGS_OFFSET	0x10
+#define DLEVEL_REGS_OFFSET	0x14
+#define PULL_REGS_OFFSET	0x1c
+
+#define PINS_PER_BANK		32
+#define MUX_PINS_PER_REG	8
+#define MUX_PINS_BITS		4
+#define MUX_PINS_MASK		0x0f
+#define DATA_PINS_PER_REG	32
+#define DATA_PINS_BITS		1
+#define DATA_PINS_MASK		0x01
+#define DLEVEL_PINS_PER_REG	16
+#define DLEVEL_PINS_BITS	2
+#define DLEVEL_PINS_MASK	0x03
+#define PULL_PINS_PER_REG	16
+#define PULL_PINS_BITS		2
+#define PULL_PINS_MASK		0x03
+
+#define IRQ_PER_BANK		32
+
+#define IRQ_CFG_REG		0x200
+#define IRQ_CFG_IRQ_PER_REG		8
+#define IRQ_CFG_IRQ_BITS		4
+#define IRQ_CFG_IRQ_MASK		((1 << IRQ_CFG_IRQ_BITS) - 1)
+#define IRQ_CTRL_REG		0x210
+#define IRQ_CTRL_IRQ_PER_REG		32
+#define IRQ_CTRL_IRQ_BITS		1
+#define IRQ_CTRL_IRQ_MASK		((1 << IRQ_CTRL_IRQ_BITS) - 1)
+#define IRQ_STATUS_REG		0x214
+#define IRQ_STATUS_IRQ_PER_REG		32
+#define IRQ_STATUS_IRQ_BITS		1
+#define IRQ_STATUS_IRQ_MASK		((1 << IRQ_STATUS_IRQ_BITS) - 1)
+
+#define IRQ_MEM_SIZE		0x20
+
+#define IRQ_EDGE_RISING		0x00
+#define IRQ_EDGE_FALLING	0x01
+#define IRQ_LEVEL_HIGH		0x02
+#define IRQ_LEVEL_LOW		0x03
+#define IRQ_EDGE_BOTH		0x04
+
+#define SUN4I_FUNC_INPUT	0
+#define SUN4I_FUNC_IRQ		6
+
+struct sunxi_desc_function {
+	const char	*name;
+	u8		muxval;
+	u8		irqbank;
+	u8		irqnum;
+};
+
+struct sunxi_desc_pin {
+	struct pinctrl_pin_desc		pin;
+	struct sunxi_desc_function	*functions;
+};
+
+struct sunxi_pinctrl_desc {
+	const struct sunxi_desc_pin	*pins;
+	int				npins;
+	unsigned			pin_base;
+	unsigned			irq_banks;
+	unsigned			irq_bank_base;
+	bool				irq_read_needs_mux;
+};
+
+struct sunxi_pinctrl_function {
+	const char	*name;
+	const char	**groups;
+	unsigned	ngroups;
+};
+
+struct sunxi_pinctrl_group {
+	const char	*name;
+	unsigned long	config;
+	unsigned	pin;
+};
+
+#ifndef __UBOOT__
+struct sunxi_pinctrl {
+	void __iomem			*membase;
+	struct gpio_chip		*chip;
+	const struct sunxi_pinctrl_desc	*desc;
+	struct device			*dev;
+	struct irq_domain		*domain;
+	struct sunxi_pinctrl_function	*functions;
+	unsigned			nfunctions;
+	struct sunxi_pinctrl_group	*groups;
+	unsigned			ngroups;
+	int				*irq;
+	unsigned			*irq_array;
+	spinlock_t			lock;
+	struct pinctrl_dev		*pctl_dev;
+};
+#endif
+
+#define SUNXI_PIN(_pin, ...)					\
+	{							\
+		.pin = _pin,					\
+		.functions = (struct sunxi_desc_function[]){	\
+			__VA_ARGS__, { } },			\
+	}
+
+#define SUNXI_FUNCTION(_val, _name)				\
+	{							\
+		.name = _name,					\
+		.muxval = _val,					\
+	}
+
+#define SUNXI_FUNCTION_IRQ(_val, _irq)				\
+	{							\
+		.name = "irq",					\
+		.muxval = _val,					\
+		.irqnum = _irq,					\
+	}
+
+#define SUNXI_FUNCTION_IRQ_BANK(_val, _bank, _irq)		\
+	{							\
+		.name = "irq",					\
+		.muxval = _val,					\
+		.irqbank = _bank,				\
+		.irqnum = _irq,					\
+	}
+
+/*
+ * The sunXi PIO registers are organized as is:
+ * 0x00 - 0x0c	Muxing values.
+ *		8 pins per register, each pin having a 4bits value
+ * 0x10		Pin values
+ *		32 bits per register, each pin corresponding to one bit
+ * 0x14 - 0x18	Drive level
+ *		16 pins per register, each pin having a 2bits value
+ * 0x1c - 0x20	Pull-Up values
+ *		16 pins per register, each pin having a 2bits value
+ *
+ * This is for the first bank. Each bank will have the same layout,
+ * with an offset being a multiple of 0x24.
+ *
+ * The following functions calculate from the pin number the register
+ * and the bit offset that we should access.
+ */
+static inline u32 sunxi_mux_reg(u16 pin)
+{
+	u8 bank = pin / PINS_PER_BANK;
+	u32 offset = bank * BANK_MEM_SIZE;
+	offset += MUX_REGS_OFFSET;
+	offset += pin % PINS_PER_BANK / MUX_PINS_PER_REG * 0x04;
+	return round_down(offset, 4);
+}
+
+static inline u32 sunxi_mux_offset(u16 pin)
+{
+	u32 pin_num = pin % MUX_PINS_PER_REG;
+	return pin_num * MUX_PINS_BITS;
+}
+
+static inline u32 sunxi_data_reg(u16 pin)
+{
+	u8 bank = pin / PINS_PER_BANK;
+	u32 offset = bank * BANK_MEM_SIZE;
+	offset += DATA_REGS_OFFSET;
+	offset += pin % PINS_PER_BANK / DATA_PINS_PER_REG * 0x04;
+	return round_down(offset, 4);
+}
+
+static inline u32 sunxi_data_offset(u16 pin)
+{
+	u32 pin_num = pin % DATA_PINS_PER_REG;
+	return pin_num * DATA_PINS_BITS;
+}
+
+static inline u32 sunxi_dlevel_reg(u16 pin)
+{
+	u8 bank = pin / PINS_PER_BANK;
+	u32 offset = bank * BANK_MEM_SIZE;
+	offset += DLEVEL_REGS_OFFSET;
+	offset += pin % PINS_PER_BANK / DLEVEL_PINS_PER_REG * 0x04;
+	return round_down(offset, 4);
+}
+
+static inline u32 sunxi_dlevel_offset(u16 pin)
+{
+	u32 pin_num = pin % DLEVEL_PINS_PER_REG;
+	return pin_num * DLEVEL_PINS_BITS;
+}
+
+static inline u32 sunxi_pull_reg(u16 pin)
+{
+	u8 bank = pin / PINS_PER_BANK;
+	u32 offset = bank * BANK_MEM_SIZE;
+	offset += PULL_REGS_OFFSET;
+	offset += pin % PINS_PER_BANK / PULL_PINS_PER_REG * 0x04;
+	return round_down(offset, 4);
+}
+
+static inline u32 sunxi_pull_offset(u16 pin)
+{
+	u32 pin_num = pin % PULL_PINS_PER_REG;
+	return pin_num * PULL_PINS_BITS;
+}
+
+static inline u32 sunxi_irq_cfg_reg(u16 irq, unsigned bank_base)
+{
+	u8 bank = irq / IRQ_PER_BANK;
+	u8 reg = (irq % IRQ_PER_BANK) / IRQ_CFG_IRQ_PER_REG * 0x04;
+
+	return IRQ_CFG_REG + (bank_base + bank) * IRQ_MEM_SIZE + reg;
+}
+
+static inline u32 sunxi_irq_cfg_offset(u16 irq)
+{
+	u32 irq_num = irq % IRQ_CFG_IRQ_PER_REG;
+	return irq_num * IRQ_CFG_IRQ_BITS;
+}
+
+static inline u32 sunxi_irq_ctrl_reg_from_bank(u8 bank, unsigned bank_base)
+{
+	return IRQ_CTRL_REG + (bank_base + bank) * IRQ_MEM_SIZE;
+}
+
+static inline u32 sunxi_irq_ctrl_reg(u16 irq, unsigned bank_base)
+{
+	u8 bank = irq / IRQ_PER_BANK;
+
+	return sunxi_irq_ctrl_reg_from_bank(bank, bank_base);
+}
+
+static inline u32 sunxi_irq_ctrl_offset(u16 irq)
+{
+	u32 irq_num = irq % IRQ_CTRL_IRQ_PER_REG;
+	return irq_num * IRQ_CTRL_IRQ_BITS;
+}
+
+static inline u32 sunxi_irq_status_reg_from_bank(u8 bank, unsigned bank_base)
+{
+	return IRQ_STATUS_REG + (bank_base + bank) * IRQ_MEM_SIZE;
+}
+
+static inline u32 sunxi_irq_status_reg(u16 irq, unsigned bank_base)
+{
+	u8 bank = irq / IRQ_PER_BANK;
+
+	return sunxi_irq_status_reg_from_bank(bank, bank_base);
+}
+
+static inline u32 sunxi_irq_status_offset(u16 irq)
+{
+	u32 irq_num = irq % IRQ_STATUS_IRQ_PER_REG;
+	return irq_num * IRQ_STATUS_IRQ_BITS;
+}
+
+#ifndef __UBOOT__
+int sunxi_pinctrl_init(struct platform_device *pdev,
+		       const struct sunxi_pinctrl_desc *desc);
+#endif
+
+#endif /* __PINCTRL_SUNXI_H */
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 2/9] dm: core: Allow multiple drivers to bind for a single node
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 1/9] sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 3/9] sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding Philipp Tomsich
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

Currently, driver binding stops once it encounters the first
compatible driver that doesn't refuse to bind. However, there are
cases where a single node will need to be handled by multiple driver
classes. For those cases we provide a configurable option to continue
to bind after the first driver has been found.

The first use cases for this are from the DM conversion of the sunxi
(Allwinner) architecture:
 * pinctrl (UCLASS_PINCTRL) and gpio (UCLASS_GPIO) drivers need to
   bind against a single node
 * clock (UCLASS_CLK) and reset (UCLASS_RESET) drivers also need to
   bind against a single node

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/core/Kconfig | 14 ++++++++++++++
 drivers/core/lists.c | 12 +++++++++++-
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/drivers/core/Kconfig b/drivers/core/Kconfig
index 8749561..913101c 100644
--- a/drivers/core/Kconfig
+++ b/drivers/core/Kconfig
@@ -31,6 +31,20 @@ config DM_WARN
 	  This will cause dm_warn() to be compiled out - it will do nothing
 	  when called.
 
+config DM_ALLOW_MULTIPLE_DRIVERS
+        bool "Allow multiple drivers to bind for one node"
+	depends on DM
+	default n
+	help
+	  The driver model in U-Boot originally did not allow multiple
+	  drivers to bind for a single device node.
+
+	  If enabled, multiple drivers can now bind for a single node
+	  by using the same compatible string for matching: lists_bind_fdt()
+	  will assume that binding multiple drivers is desirable, if the
+	  caller does not request the pointer to the udevice structure to
+	  be returned (i.e. if devp is NULL).
+
 config DM_DEVICE_REMOVE
 	bool "Support device removal"
 	depends on DM
diff --git a/drivers/core/lists.c b/drivers/core/lists.c
index 23b6ba7..9124693 100644
--- a/drivers/core/lists.c
+++ b/drivers/core/lists.c
@@ -166,7 +166,11 @@ int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
 		dm_dbg("   - attempt to match compatible string '%s'\n",
 		       compat);
 
-		for (entry = driver; entry != driver + n_ents; entry++) {
+		entry = driver;
+#if defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
+allow_more_matches:
+#endif
+		for (; entry != driver + n_ents; entry++) {
 			ret = driver_check_compatible(entry->of_match, &id,
 						      compat);
 			if (!ret)
@@ -190,6 +194,12 @@ int lists_bind_fdt(struct udevice *parent, const void *blob, int offset,
 			found = true;
 			if (devp)
 				*devp = dev;
+#if defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
+			else {
+				entry++;
+				goto allow_more_matches;
+			}
+#endif
 		}
 		break;
 	}
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 3/9] sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 1/9] sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 2/9] dm: core: Allow multiple drivers to bind for a single node Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 4/9] Kconfig: sunxi: Select new option for allow multiple drivers to bind Philipp Tomsich
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

Our gpio and pinctrl driver need to be bound against the same
node. While this can be done by hand (i.e. explicitly looking up the
driver, creating the driver-data and binding the device), it is much
easier done when the new option for the binding of multiple drivers
against a single node is configured.

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/gpio/sunxi_gpio.c             | 4 ++--
 drivers/pinctrl/sunxi/pinctrl-sunxi.c | 8 ++++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/gpio/sunxi_gpio.c b/drivers/gpio/sunxi_gpio.c
index fd0c1ac..cbec1b9 100644
--- a/drivers/gpio/sunxi_gpio.c
+++ b/drivers/gpio/sunxi_gpio.c
@@ -349,7 +349,7 @@ static const struct udevice_id sunxi_gpio_ids[] = {
 	ID("allwinner,sun8i-a83t-pinctrl",	a_all),
 	ID("allwinner,sun8i-h3-pinctrl",	a_all),
 	ID("allwinner,sun9i-a80-pinctrl",	a_all),
-#if !defined(CONFIG_SUNXI_PINCTRL)
+#if !defined(CONFIG_SUNXI_PINCTRL) || defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 	/* This is not strictly correct for the A64, as it is missing
 	 * bank 'A'. Yet, the register layout in the pinctrl block is
 	 * backward compatible and any accesses to the registers that
@@ -362,7 +362,7 @@ static const struct udevice_id sunxi_gpio_ids[] = {
 	ID("allwinner,sun8i-a83t-r-pinctrl",	l_1),
 	ID("allwinner,sun8i-h3-r-pinctrl",	l_1),
 	ID("allwinner,sun9i-a80-r-pinctrl",	l_3),
-#if !defined(CONFIG_SUNXI_PINCTRL)
+#if !defined(CONFIG_SUNXI_PINCTRL) || defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 	ID("allwinner,sun50i-a64-r-pinctrl",	l_1),
 #endif
 	{ }
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
index 1e659fc..dc1a1f7 100644
--- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c
+++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c
@@ -16,7 +16,9 @@
 #include <asm/io.h>
 #include <asm/arch/clock.h>
 #include <asm/gpio.h>
+#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 #include <asm/arch/gpio-internal.h>
+#endif
 #include <dm/device-internal.h>
 #include <dm/lists.h>
 #include <dm/pinctrl.h>
@@ -29,7 +31,7 @@ DECLARE_GLOBAL_DATA_PTR;
 
 struct sunxi_pctrl_priv {
 	void *base;
-#if defined(CONFIG_DM_GPIO)
+#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 	struct sunxi_gpio_soc_data gpio_soc_data;
 	struct udevice *gpio_dev;
 #endif
@@ -251,6 +253,7 @@ static int sunxi_pctrl_set_state(struct udevice *dev, struct udevice *config)
 	return 0;
 }
 
+#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 static inline void soc_data_from_desc(const struct sunxi_pinctrl_desc *data,
 				      struct sunxi_gpio_soc_data *soc_data)
 {
@@ -270,10 +273,11 @@ static inline void soc_data_from_desc(const struct sunxi_pinctrl_desc *data,
 	soc_data->start = low;
 	soc_data->no_banks = high - low + 1;
 }
+#endif
 
 static int sunxi_pctrl_bind_gpio(struct udevice *dev)
 {
-#if defined(CONFIG_DM_GPIO)
+#if defined(CONFIG_DM_GPIO) && !defined(CONFIG_DM_ALLOW_MULTIPLE_DRIVERS)
 	struct sunxi_pctrl_priv *priv = dev_get_priv(dev);
 	const struct sunxi_pinctrl_desc *data =
 		(struct sunxi_pinctrl_desc *)dev_get_driver_data(dev);
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 4/9] Kconfig: sunxi: Select new option for allow multiple drivers to bind
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (2 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 3/9] sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 5/9] sunxi: add module reset (UCLASS_RESET) support for sunxi Philipp Tomsich
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

For pinctrl & gpio and reset & clk, the sunxi (Allwinner SoC) board
support needs to bind multiple drivers to a single DT node.  Select
the necessary config-option when selecting ARCH_SUNXI.

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 arch/arm/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig
index 0229800..c62d61b 100644
--- a/arch/arm/Kconfig
+++ b/arch/arm/Kconfig
@@ -644,6 +644,7 @@ config ARCH_SUNXI
 	select CMD_MMC if MMC
 	select CMD_USB if DISTRO_DEFAULTS
 	select DM
+	select DM_ALLOW_MULTIPLE_DRIVERS
 	select DM_ETH
 	select DM_GPIO
 	select DM_KEYBOARD
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 5/9] sunxi: add module reset (UCLASS_RESET) support for sunxi
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (3 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 4/9] Kconfig: sunxi: Select new option for allow multiple drivers to bind Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 6/9] linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel Philipp Tomsich
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

In order to have the device model describe the module reset bits
on sunxi (well, at least for anything newer than sun6i), we need
a (rather simple) driver for 'allwinner,sun6i-a31-clock-reset'
nodes.

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/reset/Kconfig                      |   9 ++
 drivers/reset/Makefile                     |   1 +
 drivers/reset/sunxi/Makefile               |   6 ++
 drivers/reset/sunxi/ccu-sun50i-a64.c       |  75 +++++++++++++
 drivers/reset/sunxi/ccu_reset.h            |   9 ++
 drivers/reset/sunxi/reset-sunxi.c          | 168 +++++++++++++++++++++++++++++
 include/dt-bindings/reset/sun50i-a64-ccu.h |  98 +++++++++++++++++
 7 files changed, 366 insertions(+)
 create mode 100644 drivers/reset/sunxi/Makefile
 create mode 100644 drivers/reset/sunxi/ccu-sun50i-a64.c
 create mode 100644 drivers/reset/sunxi/ccu_reset.h
 create mode 100644 drivers/reset/sunxi/reset-sunxi.c
 create mode 100644 include/dt-bindings/reset/sun50i-a64-ccu.h

diff --git a/drivers/reset/Kconfig b/drivers/reset/Kconfig
index c42b0bc..8db25fc 100644
--- a/drivers/reset/Kconfig
+++ b/drivers/reset/Kconfig
@@ -43,4 +43,13 @@ config RESET_UNIPHIER
 	  Say Y if you want to control reset signals provided by System Control
 	  block, Media I/O block, Peripheral Block.
 
+config RESET_SUNXI
+        bool "Reset controller driver for Allwiner SoCs"
+	depends on DM_RESET && ARCH_SUNXI
+	default y
+	help
+	  Support for reset controllers on Allwinner SoCs.
+	  Say Y if you want to control reset signals provided by CCU (e.g. sun50i)
+	  or PRCM (e.g. sun6i, sun9i) blocks.
+
 endmenu
diff --git a/drivers/reset/Makefile b/drivers/reset/Makefile
index 5c4305c..a4994e9 100644
--- a/drivers/reset/Makefile
+++ b/drivers/reset/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_SANDBOX_MBOX) += sandbox-reset-test.o
 obj-$(CONFIG_TEGRA_CAR_RESET) += tegra-car-reset.o
 obj-$(CONFIG_TEGRA186_RESET) += tegra186-reset.o
 obj-$(CONFIG_RESET_UNIPHIER) += reset-uniphier.o
+obj-$(CONFIG_RESET_SUNXI) += sunxi/
diff --git a/drivers/reset/sunxi/Makefile b/drivers/reset/sunxi/Makefile
new file mode 100644
index 0000000..559c79a
--- /dev/null
+++ b/drivers/reset/sunxi/Makefile
@@ -0,0 +1,6 @@
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+obj-y += reset-sunxi.o
+obj-$(CONFIG_MACH_SUN50I) += ccu-sun50i-a64.o
\ No newline at end of file
diff --git a/drivers/reset/sunxi/ccu-sun50i-a64.c b/drivers/reset/sunxi/ccu-sun50i-a64.c
new file mode 100644
index 0000000..f7c388e
--- /dev/null
+++ b/drivers/reset/sunxi/ccu-sun50i-a64.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * The tabular data contained in this file is reused verbatim from
+ * Linux (drivers/clk/sunxi-ng.c), which is:
+ *   Copyright (c) 2016 Maxime Ripard. All rights reserved.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+
+#include <common.h>
+#include <dt-bindings/reset/sun50i-a64-ccu.h>
+
+#include "ccu_reset.h"
+
+const struct ccu_reset_map sun50i_a64_ccu_resets[] = {
+	[RST_USB_PHY0]		=  { 0x0cc, BIT(0) },
+	[RST_USB_PHY1]		=  { 0x0cc, BIT(1) },
+	[RST_USB_HSIC]		=  { 0x0cc, BIT(2) },
+
+	[RST_DRAM]		=  { 0x0f4, BIT(31) },
+	[RST_MBUS]		=  { 0x0fc, BIT(31) },
+
+	[RST_BUS_MIPI_DSI]	=  { 0x2c0, BIT(1) },
+	[RST_BUS_CE]		=  { 0x2c0, BIT(5) },
+	[RST_BUS_DMA]		=  { 0x2c0, BIT(6) },
+	[RST_BUS_MMC0]		=  { 0x2c0, BIT(8) },
+	[RST_BUS_MMC1]		=  { 0x2c0, BIT(9) },
+	[RST_BUS_MMC2]		=  { 0x2c0, BIT(10) },
+	[RST_BUS_NAND]		=  { 0x2c0, BIT(13) },
+	[RST_BUS_DRAM]		=  { 0x2c0, BIT(14) },
+	[RST_BUS_EMAC]		=  { 0x2c0, BIT(17) },
+	[RST_BUS_TS]		=  { 0x2c0, BIT(18) },
+	[RST_BUS_HSTIMER]	=  { 0x2c0, BIT(19) },
+	[RST_BUS_SPI0]		=  { 0x2c0, BIT(20) },
+	[RST_BUS_SPI1]		=  { 0x2c0, BIT(21) },
+	[RST_BUS_OTG]		=  { 0x2c0, BIT(23) },
+	[RST_BUS_EHCI0]		=  { 0x2c0, BIT(24) },
+	[RST_BUS_EHCI1]		=  { 0x2c0, BIT(25) },
+	[RST_BUS_OHCI0]		=  { 0x2c0, BIT(28) },
+	[RST_BUS_OHCI1]		=  { 0x2c0, BIT(29) },
+
+	[RST_BUS_VE]		=  { 0x2c4, BIT(0) },
+	[RST_BUS_TCON0]		=  { 0x2c4, BIT(3) },
+	[RST_BUS_TCON1]		=  { 0x2c4, BIT(4) },
+	[RST_BUS_DEINTERLACE]	=  { 0x2c4, BIT(5) },
+	[RST_BUS_CSI]		=  { 0x2c4, BIT(8) },
+	[RST_BUS_HDMI0]		=  { 0x2c4, BIT(10) },
+	[RST_BUS_HDMI1]		=  { 0x2c4, BIT(11) },
+	[RST_BUS_DE]		=  { 0x2c4, BIT(12) },
+	[RST_BUS_GPU]		=  { 0x2c4, BIT(20) },
+	[RST_BUS_MSGBOX]	=  { 0x2c4, BIT(21) },
+	[RST_BUS_SPINLOCK]	=  { 0x2c4, BIT(22) },
+	[RST_BUS_DBG]		=  { 0x2c4, BIT(31) },
+
+	[RST_BUS_LVDS]		=  { 0x2c8, BIT(0) },
+
+	[RST_BUS_CODEC]		=  { 0x2d0, BIT(0) },
+	[RST_BUS_SPDIF]		=  { 0x2d0, BIT(1) },
+	[RST_BUS_THS]		=  { 0x2d0, BIT(8) },
+	[RST_BUS_I2S0]		=  { 0x2d0, BIT(12) },
+	[RST_BUS_I2S1]		=  { 0x2d0, BIT(13) },
+	[RST_BUS_I2S2]		=  { 0x2d0, BIT(14) },
+
+	[RST_BUS_I2C0]		=  { 0x2d8, BIT(0) },
+	[RST_BUS_I2C1]		=  { 0x2d8, BIT(1) },
+	[RST_BUS_I2C2]		=  { 0x2d8, BIT(2) },
+	[RST_BUS_SCR]		=  { 0x2d8, BIT(5) },
+	[RST_BUS_UART0]		=  { 0x2d8, BIT(16) },
+	[RST_BUS_UART1]		=  { 0x2d8, BIT(17) },
+	[RST_BUS_UART2]		=  { 0x2d8, BIT(18) },
+	[RST_BUS_UART3]		=  { 0x2d8, BIT(19) },
+	[RST_BUS_UART4]		=  { 0x2d8, BIT(20) },
+};
diff --git a/drivers/reset/sunxi/ccu_reset.h b/drivers/reset/sunxi/ccu_reset.h
new file mode 100644
index 0000000..83b30a0
--- /dev/null
+++ b/drivers/reset/sunxi/ccu_reset.h
@@ -0,0 +1,9 @@
+#ifndef _CCU_RESET_H_
+#define _CCU_RESET_H_
+
+struct ccu_reset_map {
+	uint16_t reg;
+	uint32_t bit;
+};
+
+#endif /* _CCU_RESET_H_ */
diff --git a/drivers/reset/sunxi/reset-sunxi.c b/drivers/reset/sunxi/reset-sunxi.c
new file mode 100644
index 0000000..8fbfa85
--- /dev/null
+++ b/drivers/reset/sunxi/reset-sunxi.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <reset-uclass.h>
+#include <dm/device.h>
+#include <linux/bitops.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+
+#include "ccu_reset.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_reset_priv {
+	void *base;
+	size_t  size;
+};
+
+static int sunxi_reset_request(struct reset_ctl *reset_ctl)
+{
+	debug("%s (%s): id %ld\n",
+	      reset_ctl->dev->name, __func__, reset_ctl->id);
+	return 0;
+}
+
+static int sunxi_reset_free(struct reset_ctl *reset_ctl)
+{
+	debug("%s (%s): id %ld\n",
+	      reset_ctl->dev->name, __func__, reset_ctl->id);
+	return 0;
+}
+
+static int sunxi_reset_update_bit(struct udevice *dev,
+				  const uint32_t off,
+				  const uint32_t bitmask,
+				  const bool assert)
+{
+	const struct sunxi_reset_priv *priv = dev_get_priv(dev);
+
+	debug("%s (%s): base %p offset %x bit %x assert %d size %ld\n",
+	      dev->name, __func__,
+	      priv->base, off, bitmask, assert, priv->size);
+
+	if (off >= priv->size)
+		return -EINVAL;
+
+	if (assert)
+		clrbits_le32(priv->base + off, bitmask);
+	else
+		setbits_le32(priv->base + off, bitmask);
+
+	return 0;
+}
+
+static int sunxi_ccu_reset_update(struct reset_ctl *reset_ctl, bool assert)
+{
+	struct udevice *dev = reset_ctl->dev;
+	const struct ccu_reset_map *reset_map =
+		(const struct ccu_reset_map *)dev_get_driver_data(dev);
+	const struct ccu_reset_map *entry = &reset_map[reset_ctl->id];
+
+	return sunxi_reset_update_bit(dev, entry->reg, entry->bit, assert);
+}
+
+static int sunxi_ccu_reset_assert(struct reset_ctl *reset_ctl)
+{
+	return sunxi_ccu_reset_update(reset_ctl, true);
+}
+
+static int sunxi_ccu_reset_deassert(struct reset_ctl *reset_ctl)
+{
+	return sunxi_ccu_reset_update(reset_ctl, false);
+}
+
+static int sunxi_reset_update(struct reset_ctl *reset_ctl, bool assert)
+{
+	struct udevice *dev = reset_ctl->dev;
+	const unsigned long id = reset_ctl->id;
+	const unsigned long offset = (id / 32) * sizeof(uint32_t);
+	const unsigned int bit = id % 32;
+
+	return sunxi_reset_update_bit(dev, offset, BIT(bit), assert);
+}
+
+static int sunxi_reset_assert(struct reset_ctl *reset_ctl)
+{
+	return sunxi_reset_update(reset_ctl, true);
+}
+
+static int sunxi_reset_deassert(struct reset_ctl *reset_ctl)
+{
+	return sunxi_reset_update(reset_ctl, false);
+}
+
+static int sunxi_reset_probe(struct udevice *dev)
+{
+	struct sunxi_reset_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: failed to find base address ('reg')\n", dev->name);
+		return -ENODEV;
+	}
+	priv->base = (void *)addr;
+	priv->size = size;
+
+	if (!priv->base)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static const struct reset_ops sunxi_reset_ops = {
+	.request = sunxi_reset_request,
+	.free = sunxi_reset_free,
+	.rst_assert = sunxi_reset_assert,
+	.rst_deassert = sunxi_reset_deassert,
+};
+
+static const struct udevice_id sunxi_reset_match[] = {
+	{ .compatible = "allwinner,sun6i-a31-clock-reset" },
+	{ }
+};
+
+
+U_BOOT_DRIVER(sunxi_reset) = {
+	.name = "sunxi-reset",
+	.id = UCLASS_RESET,
+	.of_match = sunxi_reset_match,
+	.ops = &sunxi_reset_ops,
+	.priv_auto_alloc_size = sizeof(struct sunxi_reset_priv),
+	.probe = sunxi_reset_probe,
+};
+
+static const struct reset_ops sunxi_ccu_reset_ops = {
+	.request = sunxi_reset_request,
+	.free = sunxi_reset_free,
+	.rst_assert = sunxi_ccu_reset_assert,
+	.rst_deassert = sunxi_ccu_reset_deassert,
+};
+
+#if defined(CONFIG_MACH_SUN50I)
+extern const struct ccu_reset_map sun50i_a64_ccu_resets;
+#endif
+
+static const struct udevice_id sunxi_ccu_reset_match[] = {
+#if defined(CONFIG_MACH_SUN50I)
+	{ .compatible = "allwinner,sun50i-a64-ccu",
+	  .data = (ulong)&sun50i_a64_ccu_resets },
+#endif
+	{ }
+};
+
+U_BOOT_DRIVER(sunxi_ccu_reset) = {
+	.name = "sunxi-ccu-reset",
+	.id = UCLASS_RESET,
+	.of_match = sunxi_ccu_reset_match,
+	.ops = &sunxi_ccu_reset_ops,
+	.priv_auto_alloc_size = sizeof(struct sunxi_reset_priv),
+	.probe = sunxi_reset_probe,
+};
diff --git a/include/dt-bindings/reset/sun50i-a64-ccu.h b/include/dt-bindings/reset/sun50i-a64-ccu.h
new file mode 100644
index 0000000..db60b29
--- /dev/null
+++ b/include/dt-bindings/reset/sun50i-a64-ccu.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is dual-licensed: you can use it either under the terms
+ * of the GPL or the X11 license, at your option. Note that this dual
+ * licensing only applies to this file, and not this project as a
+ * whole.
+ *
+ *  a) This file is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License as
+ *     published by the Free Software Foundation; either version 2 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This file is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ * Or, alternatively,
+ *
+ *  b) Permission is hereby granted, free of charge, to any person
+ *     obtaining a copy of this software and associated documentation
+ *     files (the "Software"), to deal in the Software without
+ *     restriction, including without limitation the rights to use,
+ *     copy, modify, merge, publish, distribute, sublicense, and/or
+ *     sell copies of the Software, and to permit persons to whom the
+ *     Software is furnished to do so, subject to the following
+ *     conditions:
+ *
+ *     The above copyright notice and this permission notice shall be
+ *     included in all copies or substantial portions of the Software.
+ *
+ *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *     OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DT_BINDINGS_RST_SUN50I_A64_H_
+#define _DT_BINDINGS_RST_SUN50I_A64_H_
+
+#define RST_USB_PHY0		0
+#define RST_USB_PHY1		1
+#define RST_USB_HSIC		2
+#define RST_DRAM		3
+#define RST_MBUS		4
+#define RST_BUS_MIPI_DSI	5
+#define RST_BUS_CE		6
+#define RST_BUS_DMA		7
+#define RST_BUS_MMC0		8
+#define RST_BUS_MMC1		9
+#define RST_BUS_MMC2		10
+#define RST_BUS_NAND		11
+#define RST_BUS_DRAM		12
+#define RST_BUS_EMAC		13
+#define RST_BUS_TS		14
+#define RST_BUS_HSTIMER		15
+#define RST_BUS_SPI0		16
+#define RST_BUS_SPI1		17
+#define RST_BUS_OTG		18
+#define RST_BUS_EHCI0		19
+#define RST_BUS_EHCI1		20
+#define RST_BUS_OHCI0		21
+#define RST_BUS_OHCI1		22
+#define RST_BUS_VE		23
+#define RST_BUS_TCON0		24
+#define RST_BUS_TCON1		25
+#define RST_BUS_DEINTERLACE	26
+#define RST_BUS_CSI		27
+#define RST_BUS_HDMI0		28
+#define RST_BUS_HDMI1		29
+#define RST_BUS_DE		30
+#define RST_BUS_GPU		31
+#define RST_BUS_MSGBOX		32
+#define RST_BUS_SPINLOCK	33
+#define RST_BUS_DBG		34
+#define RST_BUS_LVDS		35
+#define RST_BUS_CODEC		36
+#define RST_BUS_SPDIF		37
+#define RST_BUS_THS		38
+#define RST_BUS_I2S0		39
+#define RST_BUS_I2S1		40
+#define RST_BUS_I2S2		41
+#define RST_BUS_I2C0		42
+#define RST_BUS_I2C1		43
+#define RST_BUS_I2C2		44
+#define RST_BUS_SCR		45
+#define RST_BUS_UART0		46
+#define RST_BUS_UART1		47
+#define RST_BUS_UART2		48
+#define RST_BUS_UART3		49
+#define RST_BUS_UART4		50
+
+#endif /* _DT_BINDINGS_RST_SUN50I_A64_H_ */
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 6/9] linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (4 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 5/9] sunxi: add module reset (UCLASS_RESET) support for sunxi Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 7/9] clk: clk-uclass: add clk_get_by_output_name Philipp Tomsich
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

The DIV_ROUND_UP_ULL is required for porting the sunxi clock (CCU)
binding from linux.
---
 include/linux/kernel.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/linux/kernel.h b/include/linux/kernel.h
index 0b61671..dc3f36a 100644
--- a/include/linux/kernel.h
+++ b/include/linux/kernel.h
@@ -56,6 +56,8 @@
 
 #define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
 #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+#define DIV_ROUND_UP_ULL(ll,d) \
+	({ unsigned long long _tmp = (ll)+(d)-1; do_div(_tmp, d); _tmp; })
 
 #if BITS_PER_LONG == 32
 # define DIV_ROUND_UP_SECTOR_T(ll,d) DIV_ROUND_UP_ULL(ll, d)
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 7/9] clk: clk-uclass: add clk_get_by_output_name
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (5 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 6/9] linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi Philipp Tomsich
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/clk/clk-uclass.c | 30 ++++++++++++++++++++++++++++++
 include/clk.h            | 22 ++++++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git a/drivers/clk/clk-uclass.c b/drivers/clk/clk-uclass.c
index 6fcfd69..a6a65b2 100644
--- a/drivers/clk/clk-uclass.c
+++ b/drivers/clk/clk-uclass.c
@@ -10,6 +10,7 @@
 #include <clk.h>
 #include <clk-uclass.h>
 #include <dm.h>
+#include <dm/uclass-internal.h>
 #include <dt-structs.h>
 #include <errno.h>
 
@@ -113,6 +114,35 @@ int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk)
 
 	return clk_get_by_index(dev, index, clk);
 }
+
+int clk_get_by_output_name(const char *output_name, struct clk *clk)
+{
+	struct udevice *dev;
+	int idx;
+	int ret;
+
+	debug("%s(output=%s, clk==%p)\n", __func__, output_name, clk);
+
+	/* Try to find the clock among the already registered devices */
+	for (ret = uclass_find_first_device(UCLASS_CLK, &dev); dev;
+	     ret = uclass_find_next_device(&dev)) {
+		if (ret)
+			continue;
+
+		idx = fdt_stringlist_search(gd->fdt_blob,
+					    dev_of_offset(dev),
+					    "clock-output-names",
+					    output_name);
+		if (idx < 0)
+			continue;
+
+		clk->dev = dev;
+		clk->id = idx;
+		return idx;
+	}
+
+	return -ENOENT;
+}
 #endif /* OF_CONTROL */
 
 int clk_request(struct udevice *dev, struct clk *clk)
diff --git a/include/clk.h b/include/clk.h
index 5a5c2ff..d12b896 100644
--- a/include/clk.h
+++ b/include/clk.h
@@ -98,6 +98,22 @@ int clk_get_by_index(struct udevice *dev, int index, struct clk *clk);
  * @return 0 if OK, or a negative error code.
  */
 int clk_get_by_name(struct udevice *dev, const char *name, struct clk *clk);
+
+/**
+ * clk_get_by_output_name - Get a clock by its output name
+ *
+ * This looks up a clock from the bound clock-devices. The output_name
+ * is a string that should be present in the 'clock-output-names' list
+ * of one of those clocks.
+ *
+ * @output_name: A name contained in the output-name list of the node
+ *               associated with the target node.
+ * @clk:         A pointer to a clock structure to initialize.
+ * @return the index (of the name in the output-name list) if found, or
+ *             a negative error code.
+ */
+
+int clk_get_by_output_name(const char *output_name, struct clk *clk);
 #else
 static inline int clk_get_by_index(struct udevice *dev, int index,
 				   struct clk *clk)
@@ -110,6 +126,12 @@ static inline int clk_get_by_name(struct udevice *dev, const char *name,
 {
 	return -ENOSYS;
 }
+
+static inline int clk_get_by_output_name(const char *output_name,
+					 struct clk *clk)
+{
+	return -ENOSYS;
+}
 #endif
 
 /**
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (6 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 7/9] clk: clk-uclass: add clk_get_by_output_name Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 9/9] cmd: move CONFIG_CMD_CLK to Kconfig Philipp Tomsich
  2017-03-06  2:08 ` [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL André Przywara
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

When CONFIG_CLK is defined, we now provide support for the basic
clock configuration of peripherals on sunxi:
 * clk-sunxi-ccu.c implements the CCU based (new-style) binding
     based on the Linux implementation.

And for handling the binding of the always-on (R_*) subsystems:
 * clk-sunxi-mod.c implements support for module clocks, which
     performs parent selection (determined via the device-tree)
     and determines/configures a pre-divider and divider when
     setting a clock-rate
 * clk-sunxi-gate.c: implements an clk-gate to gate individual
     modules (i.e. 'allwinner,sunxi-multi-bus-gates-clk')

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 drivers/clk/Makefile                        |   1 +
 drivers/clk/sunxi/Makefile                  |  30 ++
 drivers/clk/sunxi/ccu-compatibility.h       | 232 ++++++++
 drivers/clk/sunxi/ccu-runtime-divider.c     | 115 ++++
 drivers/clk/sunxi/ccu-runtime-fixedfactor.c |  17 +
 drivers/clk/sunxi/ccu-sun50i-a64.c          | 810 ++++++++++++++++++++++++++++
 drivers/clk/sunxi/ccu-sun50i-a64.h          |  64 +++
 drivers/clk/sunxi/ccu_common.c              |  27 +
 drivers/clk/sunxi/ccu_common.h              |  67 +++
 drivers/clk/sunxi/ccu_div.c                 | 134 +++++
 drivers/clk/sunxi/ccu_div.h                 | 170 ++++++
 drivers/clk/sunxi/ccu_frac.c                | 106 ++++
 drivers/clk/sunxi/ccu_frac.h                |  46 ++
 drivers/clk/sunxi/ccu_gate.c                |  80 +++
 drivers/clk/sunxi/ccu_gate.h                |  45 ++
 drivers/clk/sunxi/ccu_mp.c                  | 159 ++++++
 drivers/clk/sunxi/ccu_mp.h                  |  70 +++
 drivers/clk/sunxi/ccu_mult.h                |  39 ++
 drivers/clk/sunxi/ccu_mux.c                 | 201 +++++++
 drivers/clk/sunxi/ccu_mux.h                 | 105 ++++
 drivers/clk/sunxi/ccu_nk.c                  | 154 ++++++
 drivers/clk/sunxi/ccu_nk.h                  |  64 +++
 drivers/clk/sunxi/ccu_nkm.c                 | 184 +++++++
 drivers/clk/sunxi/ccu_nkm.h                 |  84 +++
 drivers/clk/sunxi/ccu_nkmp.c                | 171 ++++++
 drivers/clk/sunxi/ccu_nkmp.h                |  64 +++
 drivers/clk/sunxi/ccu_nm.c                  | 148 +++++
 drivers/clk/sunxi/ccu_nm.h                  |  84 +++
 drivers/clk/sunxi/clk-sunxi-ccu.c           | 550 +++++++++++++++++++
 drivers/clk/sunxi/clk-sunxi-gate.c          |  92 ++++
 drivers/clk/sunxi/clk-sunxi-mod.c           | 241 +++++++++
 include/dt-bindings/clock/sun50i-a64-ccu.h  | 134 +++++
 32 files changed, 4488 insertions(+)
 create mode 100644 drivers/clk/sunxi/Makefile
 create mode 100644 drivers/clk/sunxi/ccu-compatibility.h
 create mode 100644 drivers/clk/sunxi/ccu-runtime-divider.c
 create mode 100644 drivers/clk/sunxi/ccu-runtime-fixedfactor.c
 create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.c
 create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.h
 create mode 100644 drivers/clk/sunxi/ccu_common.c
 create mode 100644 drivers/clk/sunxi/ccu_common.h
 create mode 100644 drivers/clk/sunxi/ccu_div.c
 create mode 100644 drivers/clk/sunxi/ccu_div.h
 create mode 100644 drivers/clk/sunxi/ccu_frac.c
 create mode 100644 drivers/clk/sunxi/ccu_frac.h
 create mode 100644 drivers/clk/sunxi/ccu_gate.c
 create mode 100644 drivers/clk/sunxi/ccu_gate.h
 create mode 100644 drivers/clk/sunxi/ccu_mp.c
 create mode 100644 drivers/clk/sunxi/ccu_mp.h
 create mode 100644 drivers/clk/sunxi/ccu_mult.h
 create mode 100644 drivers/clk/sunxi/ccu_mux.c
 create mode 100644 drivers/clk/sunxi/ccu_mux.h
 create mode 100644 drivers/clk/sunxi/ccu_nk.c
 create mode 100644 drivers/clk/sunxi/ccu_nk.h
 create mode 100644 drivers/clk/sunxi/ccu_nkm.c
 create mode 100644 drivers/clk/sunxi/ccu_nkm.h
 create mode 100644 drivers/clk/sunxi/ccu_nkmp.c
 create mode 100644 drivers/clk/sunxi/ccu_nkmp.h
 create mode 100644 drivers/clk/sunxi/ccu_nm.c
 create mode 100644 drivers/clk/sunxi/ccu_nm.h
 create mode 100644 drivers/clk/sunxi/clk-sunxi-ccu.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-gate.c
 create mode 100644 drivers/clk/sunxi/clk-sunxi-mod.c
 create mode 100644 include/dt-bindings/clock/sun50i-a64-ccu.h

diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index 884c21c..7ae8029 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -7,6 +7,7 @@
 
 obj-$(CONFIG_CLK) += clk-uclass.o clk_fixed_rate.o
 obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/
+obj-$(CONFIG_ARCH_SUNXI) += sunxi/
 obj-$(CONFIG_SANDBOX) += clk_sandbox.o
 obj-$(CONFIG_SANDBOX) += clk_sandbox_test.o
 obj-$(CONFIG_MACH_PIC32) += clk_pic32.o
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
new file mode 100644
index 0000000..8124b99
--- /dev/null
+++ b/drivers/clk/sunxi/Makefile
@@ -0,0 +1,30 @@
+#
+# SPDX-License-Identifier:	GPL-2.0+
+#
+
+# Legacy clock drivers (used for the always-on subsystem)
+obj-y += clk-sunxi-mod.o
+obj-y += clk-sunxi-gate.o
+
+# CCU modules (ported from Linux)
+obj-y += \
+	 ccu_common.o \
+	 ccu_div.o \
+	 ccu_frac.o \
+	 ccu_gate.o \
+	 ccu_mp.o \
+	 ccu_mux.o \
+	 ccu_nk.o \
+	 ccu_nkm.o \
+	 ccu_nkmp.o \
+	 ccu_nm.o
+
+# CCU runtime
+obj-y += ccu-runtime-divider.o \
+	 ccu-runtime-fixedfactor.o
+
+obj-y += clk-sunxi-ccu.o
+
+# CCU per-cpu config
+obj-$(CONFIG_MACH_SUN50I) += ccu-sun50i-a64.o
+
diff --git a/drivers/clk/sunxi/ccu-compatibility.h b/drivers/clk/sunxi/ccu-compatibility.h
new file mode 100644
index 0000000..a5bb9ca
--- /dev/null
+++ b/drivers/clk/sunxi/ccu-compatibility.h
@@ -0,0 +1,232 @@
+/*
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _SUNXI_CLK_CCU_COMPATIBILITY_H_
+#define _SUNXI_CLK_CCU_COMPATIBILITY_H_
+
+/*
+ * The defines in this file provide for compatibility with data structures
+ * and definitions used in the Linux kernel and originate there in the file
+ *    include/linux/clk-provider.h
+ */
+
+#include <linux/compiler.h>
+#include <linux/compat.h>
+#include <linux/kernel.h>
+#include <linux/log2.h>
+#include <asm/io.h>
+
+/*
+ * flags used across common struct clk.  these flags should only affect the
+ * top-level framework.  custom flags for dealing with hardware specifics
+ * belong in struct clk_foo
+ */
+#define CLK_SET_RATE_GATE       BIT(0) /* must be gated across rate change */
+#define CLK_SET_PARENT_GATE     BIT(1) /* must be gated across re-parent */
+#define CLK_SET_RATE_PARENT     BIT(2) /* propagate rate change up one level */
+#define CLK_IGNORE_UNUSED       BIT(3) /* do not gate even if unused */
+/* unused */
+#define CLK_IS_BASIC            BIT(5) /* Basic clk, can't do a to_clk_foo() */
+#define CLK_GET_RATE_NOCACHE    BIT(6) /* do not use the cached clk rate */
+#define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */
+#define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */
+#define CLK_RECALC_NEW_RATES    BIT(9) /* recalc rates after notifications */
+#define CLK_SET_RATE_UNGATE     BIT(10) /* clock needs to run to set rate */
+#define CLK_IS_CRITICAL         BIT(11) /* do not gate, ever */
+/* parents need enable during gate/ungate, set rate and re-parent */
+#define CLK_OPS_PARENT_ENABLE   BIT(12)
+
+struct clk_hw;
+
+/**
+ * struct clk_rate_request - Structure encoding the clk constraints that
+ * a clock user might require.
+ *
+ * @rate:		Requested clock rate. This field will be adjusted by
+ *			clock drivers according to hardware capabilities.
+ * @min_rate:		Minimum rate imposed by clk users.
+ * @max_rate:		Maximum rate imposed by clk users.
+ * @best_parent_rate:	The best parent rate a parent can provide to fulfill the
+ *			requested constraints.
+ * @best_parent_hw:	The most appropriate parent clock that fulfills the
+ *			requested constraints.
+ *
+ */
+struct clk_rate_request {
+	unsigned long rate;
+	unsigned long min_rate;
+	unsigned long max_rate;
+	unsigned long best_parent_rate;
+	struct clk_hw *best_parent_hw;
+};
+
+/**
+ * struct sunxi_ccu_clk_ops - Callback operations for hardware clocks
+ *
+ * Based on 'struct clk_ops' in linux/clk-provider.h with all callbacks
+ * that are not used by the CCU driver implementation removed.
+ *
+ * For full documentation on each callback, please refer to the Linux
+ * source tree.
+ */
+struct sunxi_ccu_clk_ops {
+	int		(*enable)(struct clk_hw *hw);
+	void		(*disable)(struct clk_hw *hw);
+	int		(*is_enabled)(struct clk_hw *hw);
+	unsigned long	(*recalc_rate)(struct clk_hw *hw,
+				       unsigned long parent_rate);
+	long		(*round_rate)(struct clk_hw *hw, unsigned long rate,
+				      unsigned long *parent_rate);
+	int		(*determine_rate)(struct clk_hw *hw,
+					  struct clk_rate_request *req);
+	int		(*set_parent)(struct clk_hw *hw, u8 index);
+	u8		(*get_parent)(struct clk_hw *hw);
+	int		(*set_rate)(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate);
+};
+
+/**
+ * struct clk_hw - handle for traversing from a struct clk to its corresponding
+ * hardware-specific structure, adapted for use with U-Boot and the CCU driver.
+ *
+ * The key differences to the Linux implementation relate to our driver being
+ * much simpler than their shared clock infrastructure, so we don't cache much
+ * info (i.e. no struct clock_core), but need a pointer back to our device
+ * driver instance.
+ */
+
+struct clk_hw {
+	struct udevice *dev;
+	struct clk_hw **parents;
+	const struct clk_init_data *init;
+};
+
+/**
+ * struct clk_init_data - holds init data that's common to all clocks and is
+ * shared between the clock provider and the common clock framework.
+ *
+ * @name: clock name
+ * @ops: operations this clock supports
+ * @parent_names: array of string names for all possible parents
+ * @num_parents: number of possible parents
+ * @flags: framework-level hints and quirks
+ */
+struct clk_init_data {
+	const char              *name;
+	const struct sunxi_ccu_clk_ops    *ops;
+	const char              * const *parent_names;
+	u8                      num_parents;
+	unsigned long           flags;
+};
+
+struct clk_div_table {
+	unsigned int	val;
+	unsigned int	div;
+};
+
+/**
+ * struct clk_divider - adjustable divider clock
+ *
+ * @hw:		handle between common and hardware-specific interfaces
+ * @reg:	register containing the divider
+ * @shift:	shift to the divider bit field
+ * @width:	width of the divider bit field
+ * @table:	array of value/divider pairs, last entry should have div = 0
+ * @lock:	register lock
+ *
+ * Clock with an adjustable divider affecting its output frequency.  Implements
+ * .recalc_rate, .set_rate and .round_rate
+ *
+ * Flags:
+ * CLK_DIVIDER_ONE_BASED - by default the divisor is the value read from the
+ *	register plus one.  If CLK_DIVIDER_ONE_BASED is set then the divider is
+ *	the raw value read from the register, with the value of zero considered
+ *	invalid, unless CLK_DIVIDER_ALLOW_ZERO is set.
+ * CLK_DIVIDER_POWER_OF_TWO - clock divisor is 2 raised to the value read from
+ *	the hardware register
+ * CLK_DIVIDER_ALLOW_ZERO - Allow zero divisors.  For dividers which have
+ *	CLK_DIVIDER_ONE_BASED set, it is possible to end up with a zero divisor.
+ *	Some hardware implementations gracefully handle this case and allow a
+ *	zero divisor by not modifying their input clock
+ *	(divide by one / bypass).
+ * CLK_DIVIDER_HIWORD_MASK - The divider settings are only in lower 16-bit
+ *	of this register, and mask of divider bits are in higher 16-bit of this
+ *	register.  While setting the divider bits, higher 16-bit should also be
+ *	updated to indicate changing divider bits.
+ * CLK_DIVIDER_ROUND_CLOSEST - Makes the best calculated divider to be rounded
+ *	to the closest integer instead of the up one.
+ * CLK_DIVIDER_READ_ONLY - The divider settings are preconfigured and should
+ *	not be changed by the clock framework.
+ * CLK_DIVIDER_MAX_AT_ZERO - For dividers which are like CLK_DIVIDER_ONE_BASED
+ *	except when the value read from the register is zero, the divisor is
+ *	2^width of the field.
+ */
+struct clk_divider {
+	struct clk_hw	hw;
+	void __iomem	*reg;
+	u8		shift;
+	u8		width;
+	u8		flags;
+	const struct clk_div_table	*table;
+};
+
+#define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
+
+#define CLK_DIVIDER_ONE_BASED		BIT(0)
+#define CLK_DIVIDER_POWER_OF_TWO	BIT(1)
+#define CLK_DIVIDER_ALLOW_ZERO		BIT(2)
+#define CLK_DIVIDER_HIWORD_MASK		BIT(3)
+#define CLK_DIVIDER_ROUND_CLOSEST	BIT(4)
+#define CLK_DIVIDER_READ_ONLY		BIT(5)
+#define CLK_DIVIDER_MAX_AT_ZERO		BIT(6)
+
+unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
+		unsigned int val, const struct clk_div_table *table,
+		unsigned long flags);
+int divider_get_val(unsigned long rate, unsigned long parent_rate,
+		const struct clk_div_table *table, u8 width,
+		unsigned long flags);
+
+/**
+ * struct clk_fixed_factor - fixed multiplier and divider clock
+ *
+ * @hw:		handle between common and hardware-specific interfaces
+ * @mult:	multiplier
+ * @div:	divider
+ *
+ * Clock with a fixed multiplier and divider. The output frequency is the
+ * parent clock rate divided by div and multiplied by mult.
+ * Implements .recalc_rate, .set_rate and .round_rate
+ */
+
+struct clk_fixed_factor {
+	struct clk_hw	hw;
+	unsigned int	mult;
+	unsigned int	div;
+};
+
+#define to_clk_fixed_factor(_hw) container_of(_hw, struct clk_fixed_factor, hw)
+
+extern const struct sunxi_ccu_clk_ops clk_fixed_factor_ops;
+
+
+/* helper functions */
+const char *clk_hw_get_name(const struct clk_hw *hw);
+unsigned int clk_hw_get_num_parents(const struct clk_hw *hw);
+struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw);
+struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw,
+					  unsigned int index);
+unsigned long clk_hw_get_rate(const struct clk_hw *hw);
+unsigned long clk_hw_get_flags(const struct clk_hw *hw);
+bool clk_hw_is_enabled(const struct clk_hw *hw);
+int __clk_mux_determine_rate(struct clk_hw *hw,
+			     struct clk_rate_request *req);
+void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent);
+
+struct clk_hw_onecell_data {
+	unsigned int num;
+	struct clk_hw *hws[];
+};
+
+#endif
diff --git a/drivers/clk/sunxi/ccu-runtime-divider.c b/drivers/clk/sunxi/ccu-runtime-divider.c
new file mode 100644
index 0000000..7ddaf39
--- /dev/null
+++ b/drivers/clk/sunxi/ccu-runtime-divider.c
@@ -0,0 +1,115 @@
+/*
+ * Derived from drivers/clk/clk-divider.c in Linux.
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <div64.h>
+
+#include "ccu_common.h"
+
+#define div_mask(width)	((1 << (width)) - 1)
+
+static unsigned int _get_table_div(const struct clk_div_table *table,
+							unsigned int val)
+{
+	const struct clk_div_table *clkt;
+
+	for (clkt = table; clkt->div; clkt++)
+		if (clkt->val == val)
+			return clkt->div;
+	return 0;
+}
+
+static unsigned int _get_div(const struct clk_div_table *table,
+			     unsigned int val, unsigned long flags, u8 width)
+{
+	if (flags & CLK_DIVIDER_ONE_BASED)
+		return val;
+	if (flags & CLK_DIVIDER_POWER_OF_TWO)
+		return 1 << val;
+	if (flags & CLK_DIVIDER_MAX_AT_ZERO)
+		return val ? val : div_mask(width) + 1;
+	if (table)
+		return _get_table_div(table, val);
+	return val + 1;
+}
+
+static bool _is_valid_table_div(const struct clk_div_table *table,
+							 unsigned int div)
+{
+	const struct clk_div_table *clkt;
+
+	for (clkt = table; clkt->div; clkt++)
+		if (clkt->div == div)
+			return true;
+	return false;
+}
+
+static bool _is_valid_div(const struct clk_div_table *table, unsigned int div,
+			  unsigned long flags)
+{
+	if (flags & CLK_DIVIDER_POWER_OF_TWO)
+		return is_power_of_2(div);
+	if (table)
+		return _is_valid_table_div(table, div);
+	return true;
+}
+
+
+static unsigned int _get_table_val(const struct clk_div_table *table,
+							unsigned int div)
+{
+	const struct clk_div_table *clkt;
+
+	for (clkt = table; clkt->div; clkt++)
+		if (clkt->div == div)
+			return clkt->val;
+	return 0;
+}
+
+static unsigned int _get_val(const struct clk_div_table *table,
+			     unsigned int div, unsigned long flags, u8 width)
+{
+	if (flags & CLK_DIVIDER_ONE_BASED)
+		return div;
+	if (flags & CLK_DIVIDER_POWER_OF_TWO)
+		return __ffs(div);
+	if (flags & CLK_DIVIDER_MAX_AT_ZERO)
+		return (div == div_mask(width) + 1) ? 0 : div;
+	if (table)
+		return  _get_table_val(table, div);
+	return div - 1;
+}
+
+int divider_get_val(unsigned long rate, unsigned long parent_rate,
+		    const struct clk_div_table *table, u8 width,
+		    unsigned long flags)
+{
+	unsigned int div, value;
+
+	div = DIV_ROUND_UP_ULL((u64)parent_rate, rate);
+
+	if (!_is_valid_div(table, div, flags))
+		return -EINVAL;
+
+	value = _get_val(table, div, flags, width);
+
+	return min_t(unsigned int, value, div_mask(width));
+}
+
+unsigned long divider_recalc_rate(struct clk_hw *hw, unsigned long parent_rate,
+				  unsigned int val,
+				  const struct clk_div_table *table,
+				  unsigned long flags)
+{
+	struct clk_divider *divider = to_clk_divider(hw);
+	unsigned int div;
+
+	div = _get_div(table, val, flags, divider->width);
+	if (!div)
+		return parent_rate;
+
+	return DIV_ROUND_UP_ULL((u64)parent_rate, div);
+}
diff --git a/drivers/clk/sunxi/ccu-runtime-fixedfactor.c b/drivers/clk/sunxi/ccu-runtime-fixedfactor.c
new file mode 100644
index 0000000..53a727b
--- /dev/null
+++ b/drivers/clk/sunxi/ccu-runtime-fixedfactor.c
@@ -0,0 +1,17 @@
+/*
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include "ccu_common.h"
+
+static unsigned long clk_fixed_factor_recalc_rate(struct clk_hw *hw,
+						  unsigned long parent_rate)
+{
+	struct clk_fixed_factor *cff = to_clk_fixed_factor(hw);
+	return (parent_rate * cff->mult) / cff->div;
+}
+
+const struct sunxi_ccu_clk_ops clk_fixed_factor_ops = {
+	.recalc_rate = clk_fixed_factor_recalc_rate,
+};
diff --git a/drivers/clk/sunxi/ccu-sun50i-a64.c b/drivers/clk/sunxi/ccu-sun50i-a64.c
new file mode 100644
index 0000000..8b78eb8
--- /dev/null
+++ b/drivers/clk/sunxi/ccu-sun50i-a64.c
@@ -0,0 +1,810 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu-sun50i-a64.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_common.h"
+
+#include "ccu_div.h"
+#include "ccu_gate.h"
+#include "ccu_mp.h"
+#include "ccu_mult.h"
+#include "ccu_nk.h"
+#include "ccu_nkm.h"
+#include "ccu_nkmp.h"
+#include "ccu_nm.h"
+
+#include "ccu-sun50i-a64.h"
+
+static struct ccu_nkmp pll_cpux_clk = {
+	.enable		= BIT(31),
+	.lock		= BIT(28),
+	.n		= _SUNXI_CCU_MULT(8, 5),
+	.k		= _SUNXI_CCU_MULT(4, 2),
+	.m		= _SUNXI_CCU_DIV(0, 2),
+	.p		= _SUNXI_CCU_DIV_MAX(16, 2, 4),
+	.common		= {
+		.reg		= 0x000,
+		.hw.init	= CLK_HW_INIT("pll-cpux",
+					      "osc24M",
+					      &ccu_nkmp_ops,
+					      CLK_SET_RATE_UNGATE),
+	},
+};
+
+/*
+ * The Audio PLL is supposed to have 4 outputs: 3 fixed factors from
+ * the base (2x, 4x and 8x), and one variable divider (the one true
+ * pll audio).
+ *
+ * We don't have any need for the variable divider for now, so we just
+ * hardcode it to match with the clock names
+ */
+#define SUN50I_A64_PLL_AUDIO_REG	0x008
+
+static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_audio_base_clk, "pll-audio-base",
+				   "osc24M", 0x008,
+				   8, 7,	/* N */
+				   0, 5,	/* M */
+				   BIT(31),	/* gate */
+				   BIT(28),	/* lock */
+				   CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video0_clk, "pll-video0",
+					"osc24M", 0x010,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_ve_clk, "pll-ve",
+					"osc24M", 0x018,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NKM_WITH_GATE_LOCK(pll_ddr0_clk, "pll-ddr0",
+				    "osc24M", 0x020,
+				    8, 5,	/* N */
+				    4, 2,	/* K */
+				    0, 2,	/* M */
+				    BIT(31),	/* gate */
+				    BIT(28),	/* lock */
+				    CLK_SET_RATE_UNGATE);
+
+
+static struct ccu_nk pll_periph0_clk = {
+	.enable		= BIT(31),
+	.lock		= BIT(28),
+	.n		= _SUNXI_CCU_MULT(8, 5),
+	.k		= _SUNXI_CCU_MULT_MIN(4, 2, 2),
+	.fixed_post_div	= 2,
+	.common		= {
+		.reg		= 0x028,
+		.features	= CCU_FEATURE_FIXED_POSTDIV,
+		.hw.init	= CLK_HW_INIT("pll-periph0", "osc24M",
+					      &ccu_nk_ops, CLK_SET_RATE_UNGATE),
+	},
+};
+
+static struct ccu_nk pll_periph1_clk = {
+	.enable		= BIT(31),
+	.lock		= BIT(28),
+	.n		= _SUNXI_CCU_MULT(8, 5),
+	.k		= _SUNXI_CCU_MULT_MIN(4, 2, 2),
+	.fixed_post_div	= 2,
+	.common		= {
+		.reg		= 0x02c,
+		.features	= CCU_FEATURE_FIXED_POSTDIV,
+		.hw.init	= CLK_HW_INIT("pll-periph1", "osc24M",
+					      &ccu_nk_ops, CLK_SET_RATE_UNGATE),
+	},
+};
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_video1_clk, "pll-video1",
+					"osc24M", 0x030,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_gpu_clk, "pll-gpu",
+					"osc24M", 0x038,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+/*
+ * The output function can be changed to something more complex that
+ * we do not handle yet.
+ *
+ * Hardcode the mode so that we don't fall in that case.
+ */
+#define SUN50I_A64_PLL_MIPI_REG		0x040
+
+static struct ccu_nkm pll_mipi_clk = {
+	.enable		= BIT(31),
+	.lock		= BIT(28),
+	.n		= _SUNXI_CCU_MULT(8, 4),
+	.k		= _SUNXI_CCU_MULT_MIN(4, 2, 2),
+	.m		= _SUNXI_CCU_DIV(0, 4),
+	.common		= {
+		.reg		= 0x040,
+		.hw.init	= CLK_HW_INIT("pll-mipi", "pll-video0",
+					      &ccu_nkm_ops, CLK_SET_RATE_UNGATE),
+	},
+};
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_hsic_clk, "pll-hsic",
+					"osc24M", 0x044,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(pll_de_clk, "pll-de",
+					"osc24M", 0x048,
+					8, 7,		/* N */
+					0, 4,		/* M */
+					BIT(24),	/* frac enable */
+					BIT(25),	/* frac select */
+					270000000,	/* frac rate 0 */
+					297000000,	/* frac rate 1 */
+					BIT(31),	/* gate */
+					BIT(28),	/* lock */
+					CLK_SET_RATE_UNGATE);
+
+static SUNXI_CCU_NM_WITH_GATE_LOCK(pll_ddr1_clk, "pll-ddr1",
+				   "osc24M", 0x04c,
+				   8, 7,	/* N */
+				   0, 2,	/* M */
+				   BIT(31),	/* gate */
+				   BIT(28),	/* lock */
+				   CLK_SET_RATE_UNGATE);
+
+static const char * const cpux_parents[] = { "osc32k", "osc24M",
+					     "pll-cpux", "pll-cpux" };
+static SUNXI_CCU_MUX(cpux_clk, "cpux", cpux_parents,
+		     0x050, 16, 2, CLK_SET_RATE_PARENT | CLK_IS_CRITICAL);
+
+static SUNXI_CCU_M(axi_clk, "axi", "cpux", 0x050, 0, 2, 0);
+
+static const char * const ahb1_parents[] = { "osc32k", "osc24M",
+					     "axi", "pll-periph0" };
+static struct ccu_div ahb1_clk = {
+	.div		= _SUNXI_CCU_DIV_FLAGS(4, 2, CLK_DIVIDER_POWER_OF_TWO),
+
+	.mux		= {
+		.shift	= 12,
+		.width	= 2,
+
+		.variable_prediv	= {
+			.index	= 3,
+			.shift	= 6,
+			.width	= 2,
+		},
+	},
+
+	.common		= {
+		.reg		= 0x054,
+		.features	= CCU_FEATURE_VARIABLE_PREDIV,
+		.hw.init	= CLK_HW_INIT_PARENTS("ahb1",
+						      ahb1_parents,
+						      &ccu_div_ops,
+						      0),
+	},
+};
+
+static struct clk_div_table apb1_div_table[] = {
+	{ .val = 0, .div = 2 },
+	{ .val = 1, .div = 2 },
+	{ .val = 2, .div = 4 },
+	{ .val = 3, .div = 8 },
+	{ /* Sentinel */ },
+};
+static SUNXI_CCU_DIV_TABLE(apb1_clk, "apb1", "ahb1",
+			   0x054, 8, 2, apb1_div_table, 0);
+
+static const char * const apb2_parents[] = { "osc32k", "osc24M",
+					     "pll-periph0-2x",
+					     "pll-periph0-2x" };
+static SUNXI_CCU_MP_WITH_MUX(apb2_clk, "apb2", apb2_parents, 0x058,
+			     0, 5,	/* M */
+			     16, 2,	/* P */
+			     24, 2,	/* mux */
+			     0);
+
+static const char * const ahb2_parents[] = { "ahb1", "pll-periph0" };
+static const struct ccu_mux_fixed_prediv ahb2_fixed_predivs[] = {
+	{ .index = 1, .div = 2 },
+};
+static struct ccu_mux ahb2_clk = {
+	.mux		= {
+		.shift	= 0,
+		.width	= 1,
+		.fixed_predivs	= ahb2_fixed_predivs,
+		.n_predivs	= ARRAY_SIZE(ahb2_fixed_predivs),
+	},
+
+	.common		= {
+		.reg		= 0x05c,
+		.features	= CCU_FEATURE_FIXED_PREDIV,
+		.hw.init	= CLK_HW_INIT_PARENTS("ahb2",
+						      ahb2_parents,
+						      &ccu_mux_ops,
+						      0),
+	},
+};
+
+static SUNXI_CCU_GATE(bus_mipi_dsi_clk,	"bus-mipi-dsi",	"ahb1",
+		      0x060, BIT(1), 0);
+static SUNXI_CCU_GATE(bus_ce_clk,	"bus-ce",	"ahb1",
+		      0x060, BIT(5), 0);
+static SUNXI_CCU_GATE(bus_dma_clk,	"bus-dma",	"ahb1",
+		      0x060, BIT(6), 0);
+static SUNXI_CCU_GATE(bus_mmc0_clk,	"bus-mmc0",	"ahb1",
+		      0x060, BIT(8), 0);
+static SUNXI_CCU_GATE(bus_mmc1_clk,	"bus-mmc1",	"ahb1",
+		      0x060, BIT(9), 0);
+static SUNXI_CCU_GATE(bus_mmc2_clk,	"bus-mmc2",	"ahb1",
+		      0x060, BIT(10), 0);
+static SUNXI_CCU_GATE(bus_nand_clk,	"bus-nand",	"ahb1",
+		      0x060, BIT(13), 0);
+static SUNXI_CCU_GATE(bus_dram_clk,	"bus-dram",	"ahb1",
+		      0x060, BIT(14), 0);
+static SUNXI_CCU_GATE(bus_emac_clk,	"bus-emac",	"ahb2",
+		      0x060, BIT(17), 0);
+static SUNXI_CCU_GATE(bus_ts_clk,	"bus-ts",	"ahb1",
+		      0x060, BIT(18), 0);
+static SUNXI_CCU_GATE(bus_hstimer_clk,	"bus-hstimer",	"ahb1",
+		      0x060, BIT(19), 0);
+static SUNXI_CCU_GATE(bus_spi0_clk,	"bus-spi0",	"ahb1",
+		      0x060, BIT(20), 0);
+static SUNXI_CCU_GATE(bus_spi1_clk,	"bus-spi1",	"ahb1",
+		      0x060, BIT(21), 0);
+static SUNXI_CCU_GATE(bus_otg_clk,	"bus-otg",	"ahb1",
+		      0x060, BIT(23), 0);
+static SUNXI_CCU_GATE(bus_ehci0_clk,	"bus-ehci0",	"ahb1",
+		      0x060, BIT(24), 0);
+static SUNXI_CCU_GATE(bus_ehci1_clk,	"bus-ehci1",	"ahb2",
+		      0x060, BIT(25), 0);
+static SUNXI_CCU_GATE(bus_ohci0_clk,	"bus-ohci0",	"ahb1",
+		      0x060, BIT(28), 0);
+static SUNXI_CCU_GATE(bus_ohci1_clk,	"bus-ohci1",	"ahb2",
+		      0x060, BIT(29), 0);
+
+static SUNXI_CCU_GATE(bus_ve_clk,	"bus-ve",	"ahb1",
+		      0x064, BIT(0), 0);
+static SUNXI_CCU_GATE(bus_tcon0_clk,	"bus-tcon0",	"ahb1",
+		      0x064, BIT(3), 0);
+static SUNXI_CCU_GATE(bus_tcon1_clk,	"bus-tcon1",	"ahb1",
+		      0x064, BIT(4), 0);
+static SUNXI_CCU_GATE(bus_deinterlace_clk,	"bus-deinterlace",	"ahb1",
+		      0x064, BIT(5), 0);
+static SUNXI_CCU_GATE(bus_csi_clk,	"bus-csi",	"ahb1",
+		      0x064, BIT(8), 0);
+static SUNXI_CCU_GATE(bus_hdmi_clk,	"bus-hdmi",	"ahb1",
+		      0x064, BIT(11), 0);
+static SUNXI_CCU_GATE(bus_de_clk,	"bus-de",	"ahb1",
+		      0x064, BIT(12), 0);
+static SUNXI_CCU_GATE(bus_gpu_clk,	"bus-gpu",	"ahb1",
+		      0x064, BIT(20), 0);
+static SUNXI_CCU_GATE(bus_msgbox_clk,	"bus-msgbox",	"ahb1",
+		      0x064, BIT(21), 0);
+static SUNXI_CCU_GATE(bus_spinlock_clk,	"bus-spinlock",	"ahb1",
+		      0x064, BIT(22), 0);
+
+static SUNXI_CCU_GATE(bus_codec_clk,	"bus-codec",	"apb1",
+		      0x068, BIT(0), 0);
+static SUNXI_CCU_GATE(bus_spdif_clk,	"bus-spdif",	"apb1",
+		      0x068, BIT(1), 0);
+static SUNXI_CCU_GATE(bus_pio_clk,	"bus-pio",	"apb1",
+		      0x068, BIT(5), 0);
+static SUNXI_CCU_GATE(bus_ths_clk,	"bus-ths",	"apb1",
+		      0x068, BIT(8), 0);
+static SUNXI_CCU_GATE(bus_i2s0_clk,	"bus-i2s0",	"apb1",
+		      0x068, BIT(12), 0);
+static SUNXI_CCU_GATE(bus_i2s1_clk,	"bus-i2s1",	"apb1",
+		      0x068, BIT(13), 0);
+static SUNXI_CCU_GATE(bus_i2s2_clk,	"bus-i2s2",	"apb1",
+		      0x068, BIT(14), 0);
+
+static SUNXI_CCU_GATE(bus_i2c0_clk,	"bus-i2c0",	"apb2",
+		      0x06c, BIT(0), 0);
+static SUNXI_CCU_GATE(bus_i2c1_clk,	"bus-i2c1",	"apb2",
+		      0x06c, BIT(1), 0);
+static SUNXI_CCU_GATE(bus_i2c2_clk,	"bus-i2c2",	"apb2",
+		      0x06c, BIT(2), 0);
+static SUNXI_CCU_GATE(bus_scr_clk,	"bus-scr",	"apb2",
+		      0x06c, BIT(5), 0);
+static SUNXI_CCU_GATE(bus_uart0_clk,	"bus-uart0",	"apb2",
+		      0x06c, BIT(16), 0);
+static SUNXI_CCU_GATE(bus_uart1_clk,	"bus-uart1",	"apb2",
+		      0x06c, BIT(17), 0);
+static SUNXI_CCU_GATE(bus_uart2_clk,	"bus-uart2",	"apb2",
+		      0x06c, BIT(18), 0);
+static SUNXI_CCU_GATE(bus_uart3_clk,	"bus-uart3",	"apb2",
+		      0x06c, BIT(19), 0);
+static SUNXI_CCU_GATE(bus_uart4_clk,	"bus-uart4",	"apb2",
+		      0x06c, BIT(20), 0);
+
+static SUNXI_CCU_GATE(bus_dbg_clk,	"bus-dbg",	"ahb1",
+		      0x070, BIT(7), 0);
+
+static struct clk_div_table ths_div_table[] = {
+	{ .val = 0, .div = 1 },
+	{ .val = 1, .div = 2 },
+	{ .val = 2, .div = 4 },
+	{ .val = 3, .div = 6 },
+};
+static const char * const ths_parents[] = { "osc24M" };
+static struct ccu_div ths_clk = {
+	.enable	= BIT(31),
+	.div	= _SUNXI_CCU_DIV_TABLE(0, 2, ths_div_table),
+	.mux	= _SUNXI_CCU_MUX(24, 2),
+	.common	= {
+		.reg		= 0x074,
+		.hw.init	= CLK_HW_INIT_PARENTS("ths",
+						      ths_parents,
+						      &ccu_div_ops,
+						      0),
+	},
+};
+
+static const char * const mod0_default_parents[] = { "osc24M", "pll-periph0",
+						     "pll-periph1" };
+static SUNXI_CCU_MP_WITH_MUX_GATE(nand_clk, "nand", mod0_default_parents, 0x080,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static const char * const mmc_default_parents[] = { "osc24M", "pll-periph0-2x",
+						    "pll-periph1-2x" };
+static SUNXI_CCU_MP_WITH_MUX_GATE(mmc0_clk, "mmc0", mmc_default_parents, 0x088,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static SUNXI_CCU_MP_WITH_MUX_GATE(mmc1_clk, "mmc1", mmc_default_parents, 0x08c,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static SUNXI_CCU_MP_WITH_MUX_GATE(mmc2_clk, "mmc2", mmc_default_parents, 0x090,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static const char * const ts_parents[] = { "osc24M", "pll-periph0", };
+static SUNXI_CCU_MP_WITH_MUX_GATE(ts_clk, "ts", ts_parents, 0x098,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 4,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static SUNXI_CCU_MP_WITH_MUX_GATE(ce_clk, "ce", mmc_default_parents, 0x09c,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static SUNXI_CCU_MP_WITH_MUX_GATE(spi0_clk, "spi0", mod0_default_parents, 0x0a0,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static SUNXI_CCU_MP_WITH_MUX_GATE(spi1_clk, "spi1", mod0_default_parents, 0x0a4,
+				  0, 4,		/* M */
+				  16, 2,	/* P */
+				  24, 2,	/* mux */
+				  BIT(31),	/* gate */
+				  0);
+
+static const char * const i2s_parents[] = { "pll-audio-8x", "pll-audio-4x",
+					    "pll-audio-2x", "pll-audio" };
+static SUNXI_CCU_MUX_WITH_GATE(i2s0_clk, "i2s0", i2s_parents,
+			       0x0b0, 16, 2, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_MUX_WITH_GATE(i2s1_clk, "i2s1", i2s_parents,
+			       0x0b4, 16, 2, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_MUX_WITH_GATE(i2s2_clk, "i2s2", i2s_parents,
+			       0x0b8, 16, 2, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_M_WITH_GATE(spdif_clk, "spdif", "pll-audio",
+			     0x0c0, 0, 4, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_GATE(usb_phy0_clk,	"usb-phy0",	"osc24M",
+		      0x0cc, BIT(8), 0);
+static SUNXI_CCU_GATE(usb_phy1_clk,	"usb-phy1",	"osc24M",
+		      0x0cc, BIT(9), 0);
+static SUNXI_CCU_GATE(usb_hsic_clk,	"usb-hsic",	"pll-hsic",
+		      0x0cc, BIT(10), 0);
+static SUNXI_CCU_GATE(usb_hsic_12m_clk,	"usb-hsic-12M",	"osc12M",
+		      0x0cc, BIT(11), 0);
+static SUNXI_CCU_GATE(usb_ohci0_clk,	"usb-ohci0",	"osc12M",
+		      0x0cc, BIT(16), 0);
+static SUNXI_CCU_GATE(usb_ohci1_clk,	"usb-ohci1",	"usb-ohci0",
+		      0x0cc, BIT(17), 0);
+
+static const char * const dram_parents[] = { "pll-ddr0", "pll-ddr1" };
+static SUNXI_CCU_M_WITH_MUX(dram_clk, "dram", dram_parents,
+			    0x0f4, 0, 4, 20, 2, CLK_IS_CRITICAL);
+
+static SUNXI_CCU_GATE(dram_ve_clk,	"dram-ve",	"dram",
+		      0x100, BIT(0), 0);
+static SUNXI_CCU_GATE(dram_csi_clk,	"dram-csi",	"dram",
+		      0x100, BIT(1), 0);
+static SUNXI_CCU_GATE(dram_deinterlace_clk,	"dram-deinterlace",	"dram",
+		      0x100, BIT(2), 0);
+static SUNXI_CCU_GATE(dram_ts_clk,	"dram-ts",	"dram",
+		      0x100, BIT(3), 0);
+
+static const char * const de_parents[] = { "pll-periph0-2x", "pll-de" };
+static SUNXI_CCU_M_WITH_MUX_GATE(de_clk, "de", de_parents,
+				 0x104, 0, 4, 24, 3, BIT(31), 0);
+
+static const char * const tcon0_parents[] = { "pll-mipi", "pll-video0-2x" };
+static const u8 tcon0_table[] = { 0, 2, };
+static SUNXI_CCU_MUX_TABLE_WITH_GATE(tcon0_clk, "tcon0", tcon0_parents,
+				     tcon0_table, 0x118, 24, 3, BIT(31),
+				     CLK_SET_RATE_PARENT);
+
+static const char * const tcon1_parents[] = { "pll-video0", "pll-video1" };
+static const u8 tcon1_table[] = { 0, 2, };
+static struct ccu_div tcon1_clk = {
+	.enable		= BIT(31),
+	.div		= _SUNXI_CCU_DIV(0, 4),
+	.mux		= _SUNXI_CCU_MUX_TABLE(24, 2, tcon1_table),
+	.common		= {
+		.reg		= 0x11c,
+		.hw.init	= CLK_HW_INIT_PARENTS("tcon1",
+						      tcon1_parents,
+						      &ccu_div_ops,
+						      CLK_SET_RATE_PARENT),
+	},
+};
+
+static const char * const deinterlace_parents[] = { "pll-periph0", "pll-periph1" };
+static SUNXI_CCU_M_WITH_MUX_GATE(deinterlace_clk, "deinterlace", deinterlace_parents,
+				 0x124, 0, 4, 24, 3, BIT(31), 0);
+
+static SUNXI_CCU_GATE(csi_misc_clk,	"csi-misc",	"osc24M",
+		      0x130, BIT(31), 0);
+
+static const char * const csi_sclk_parents[] = { "pll-periph0", "pll-periph1" };
+static SUNXI_CCU_M_WITH_MUX_GATE(csi_sclk_clk, "csi-sclk", csi_sclk_parents,
+				 0x134, 16, 4, 24, 3, BIT(31), 0);
+
+static const char * const csi_mclk_parents[] = { "osc24M", "pll-video1", "pll-periph1" };
+static SUNXI_CCU_M_WITH_MUX_GATE(csi_mclk_clk, "csi-mclk", csi_mclk_parents,
+				 0x134, 0, 5, 8, 3, BIT(15), 0);
+
+static SUNXI_CCU_M_WITH_GATE(ve_clk, "ve", "pll-ve",
+			     0x13c, 16, 3, BIT(31), 0);
+
+static SUNXI_CCU_GATE(ac_dig_clk,	"ac-dig",	"pll-audio",
+		      0x140, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_GATE(ac_dig_4x_clk,	"ac-dig-4x",	"pll-audio-4x",
+		      0x140, BIT(30), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_GATE(avs_clk,		"avs",		"osc24M",
+		      0x144, BIT(31), 0);
+
+static const char * const hdmi_parents[] = { "pll-video0", "pll-video1" };
+static SUNXI_CCU_M_WITH_MUX_GATE(hdmi_clk, "hdmi", hdmi_parents,
+				 0x150, 0, 4, 24, 2, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_GATE(hdmi_ddc_clk,	"hdmi-ddc",	"osc24M",
+		      0x154, BIT(31), 0);
+
+static const char * const mbus_parents[] = { "osc24M", "pll-periph0-2x",
+						 "pll-ddr0", "pll-ddr1" };
+static SUNXI_CCU_M_WITH_MUX_GATE(mbus_clk, "mbus", mbus_parents,
+				 0x15c, 0, 3, 24, 2, BIT(31), CLK_IS_CRITICAL);
+
+static const char * const dsi_dphy_parents[] = { "pll-video0", "pll-periph0" };
+static const u8 dsi_dphy_table[] = { 0, 2, };
+static SUNXI_CCU_M_WITH_MUX_TABLE_GATE(dsi_dphy_clk, "dsi-dphy",
+				       dsi_dphy_parents, dsi_dphy_table,
+				       0x168, 0, 4, 8, 2, BIT(31), CLK_SET_RATE_PARENT);
+
+static SUNXI_CCU_M_WITH_GATE(gpu_clk, "gpu", "pll-gpu",
+			     0x1a0, 0, 3, BIT(31), CLK_SET_RATE_PARENT);
+
+/* Fixed Factor clocks */
+static CLK_FIXED_FACTOR(osc12M_clk, "osc12M", "osc24M", 1, 2, 0);
+
+/* We hardcode the divider to 4 for now */
+static CLK_FIXED_FACTOR(pll_audio_clk, "pll-audio",
+			"pll-audio-base", 4, 1, CLK_SET_RATE_PARENT);
+static CLK_FIXED_FACTOR(pll_audio_2x_clk, "pll-audio-2x",
+			"pll-audio-base", 2, 1, CLK_SET_RATE_PARENT);
+static CLK_FIXED_FACTOR(pll_audio_4x_clk, "pll-audio-4x",
+			"pll-audio-base", 1, 1, CLK_SET_RATE_PARENT);
+static CLK_FIXED_FACTOR(pll_audio_8x_clk, "pll-audio-8x",
+			"pll-audio-base", 1, 2, CLK_SET_RATE_PARENT);
+static CLK_FIXED_FACTOR(pll_periph0_2x_clk, "pll-periph0-2x",
+			"pll-periph0", 1, 2, 0);
+static CLK_FIXED_FACTOR(pll_periph1_2x_clk, "pll-periph1-2x",
+			"pll-periph1", 1, 2, 0);
+static CLK_FIXED_FACTOR(pll_video0_2x_clk, "pll-video0-2x",
+			"pll-video0", 1, 2, CLK_SET_RATE_PARENT);
+
+static struct ccu_common *sun50i_a64_ccu_clks[] = {
+	&pll_cpux_clk.common,
+	&pll_audio_base_clk.common,
+	&pll_video0_clk.common,
+	&pll_ve_clk.common,
+	&pll_ddr0_clk.common,
+	&pll_periph0_clk.common,
+	&pll_periph1_clk.common,
+	&pll_video1_clk.common,
+	&pll_gpu_clk.common,
+	&pll_mipi_clk.common,
+	&pll_hsic_clk.common,
+	&pll_de_clk.common,
+	&pll_ddr1_clk.common,
+	&cpux_clk.common,
+	&axi_clk.common,
+	&ahb1_clk.common,
+	&apb1_clk.common,
+	&apb2_clk.common,
+	&ahb2_clk.common,
+	&bus_mipi_dsi_clk.common,
+	&bus_ce_clk.common,
+	&bus_dma_clk.common,
+	&bus_mmc0_clk.common,
+	&bus_mmc1_clk.common,
+	&bus_mmc2_clk.common,
+	&bus_nand_clk.common,
+	&bus_dram_clk.common,
+	&bus_emac_clk.common,
+	&bus_ts_clk.common,
+	&bus_hstimer_clk.common,
+	&bus_spi0_clk.common,
+	&bus_spi1_clk.common,
+	&bus_otg_clk.common,
+	&bus_ehci0_clk.common,
+	&bus_ehci1_clk.common,
+	&bus_ohci0_clk.common,
+	&bus_ohci1_clk.common,
+	&bus_ve_clk.common,
+	&bus_tcon0_clk.common,
+	&bus_tcon1_clk.common,
+	&bus_deinterlace_clk.common,
+	&bus_csi_clk.common,
+	&bus_hdmi_clk.common,
+	&bus_de_clk.common,
+	&bus_gpu_clk.common,
+	&bus_msgbox_clk.common,
+	&bus_spinlock_clk.common,
+	&bus_codec_clk.common,
+	&bus_spdif_clk.common,
+	&bus_pio_clk.common,
+	&bus_ths_clk.common,
+	&bus_i2s0_clk.common,
+	&bus_i2s1_clk.common,
+	&bus_i2s2_clk.common,
+	&bus_i2c0_clk.common,
+	&bus_i2c1_clk.common,
+	&bus_i2c2_clk.common,
+	&bus_scr_clk.common,
+	&bus_uart0_clk.common,
+	&bus_uart1_clk.common,
+	&bus_uart2_clk.common,
+	&bus_uart3_clk.common,
+	&bus_uart4_clk.common,
+	&bus_dbg_clk.common,
+	&ths_clk.common,
+	&nand_clk.common,
+	&mmc0_clk.common,
+	&mmc1_clk.common,
+	&mmc2_clk.common,
+	&ts_clk.common,
+	&ce_clk.common,
+	&spi0_clk.common,
+	&spi1_clk.common,
+	&i2s0_clk.common,
+	&i2s1_clk.common,
+	&i2s2_clk.common,
+	&spdif_clk.common,
+	&usb_phy0_clk.common,
+	&usb_phy1_clk.common,
+	&usb_hsic_clk.common,
+	&usb_hsic_12m_clk.common,
+	&usb_ohci0_clk.common,
+	&usb_ohci1_clk.common,
+	&dram_clk.common,
+	&dram_ve_clk.common,
+	&dram_csi_clk.common,
+	&dram_deinterlace_clk.common,
+	&dram_ts_clk.common,
+	&de_clk.common,
+	&tcon0_clk.common,
+	&tcon1_clk.common,
+	&deinterlace_clk.common,
+	&csi_misc_clk.common,
+	&csi_sclk_clk.common,
+	&csi_mclk_clk.common,
+	&ve_clk.common,
+	&ac_dig_clk.common,
+	&ac_dig_4x_clk.common,
+	&avs_clk.common,
+	&hdmi_clk.common,
+	&hdmi_ddc_clk.common,
+	&mbus_clk.common,
+	&dsi_dphy_clk.common,
+	&gpu_clk.common,
+};
+
+static struct clk_hw_onecell_data sun50i_a64_hw_clks = {
+	.hws	= {
+		[CLK_OSC_12M]		= &osc12M_clk.hw,
+		[CLK_PLL_CPUX]		= &pll_cpux_clk.common.hw,
+		[CLK_PLL_AUDIO_BASE]	= &pll_audio_base_clk.common.hw,
+		[CLK_PLL_AUDIO]		= &pll_audio_clk.hw,
+		[CLK_PLL_AUDIO_2X]	= &pll_audio_2x_clk.hw,
+		[CLK_PLL_AUDIO_4X]	= &pll_audio_4x_clk.hw,
+		[CLK_PLL_AUDIO_8X]	= &pll_audio_8x_clk.hw,
+		[CLK_PLL_VIDEO0]	= &pll_video0_clk.common.hw,
+		[CLK_PLL_VIDEO0_2X]	= &pll_video0_2x_clk.hw,
+		[CLK_PLL_VE]		= &pll_ve_clk.common.hw,
+		[CLK_PLL_DDR0]		= &pll_ddr0_clk.common.hw,
+		[CLK_PLL_PERIPH0]	= &pll_periph0_clk.common.hw,
+		[CLK_PLL_PERIPH0_2X]	= &pll_periph0_2x_clk.hw,
+		[CLK_PLL_PERIPH1]	= &pll_periph1_clk.common.hw,
+		[CLK_PLL_PERIPH1_2X]	= &pll_periph1_2x_clk.hw,
+		[CLK_PLL_VIDEO1]	= &pll_video1_clk.common.hw,
+		[CLK_PLL_GPU]		= &pll_gpu_clk.common.hw,
+		[CLK_PLL_MIPI]          = &pll_mipi_clk.common.hw,
+		[CLK_PLL_HSIC]		= &pll_hsic_clk.common.hw,
+		[CLK_PLL_DE]		= &pll_de_clk.common.hw,
+		[CLK_PLL_DDR1]		= &pll_ddr1_clk.common.hw,
+		[CLK_CPUX]		= &cpux_clk.common.hw,
+		[CLK_AXI]		= &axi_clk.common.hw,
+		[CLK_AHB1]		= &ahb1_clk.common.hw,
+		[CLK_APB1]		= &apb1_clk.common.hw,
+		[CLK_APB2]		= &apb2_clk.common.hw,
+		[CLK_AHB2]		= &ahb2_clk.common.hw,
+		[CLK_BUS_MIPI_DSI]	= &bus_mipi_dsi_clk.common.hw,
+		[CLK_BUS_CE]		= &bus_ce_clk.common.hw,
+		[CLK_BUS_DMA]		= &bus_dma_clk.common.hw,
+		[CLK_BUS_MMC0]		= &bus_mmc0_clk.common.hw,
+		[CLK_BUS_MMC1]		= &bus_mmc1_clk.common.hw,
+		[CLK_BUS_MMC2]		= &bus_mmc2_clk.common.hw,
+		[CLK_BUS_NAND]		= &bus_nand_clk.common.hw,
+		[CLK_BUS_DRAM]		= &bus_dram_clk.common.hw,
+		[CLK_BUS_EMAC]		= &bus_emac_clk.common.hw,
+		[CLK_BUS_TS]		= &bus_ts_clk.common.hw,
+		[CLK_BUS_HSTIMER]	= &bus_hstimer_clk.common.hw,
+		[CLK_BUS_SPI0]		= &bus_spi0_clk.common.hw,
+		[CLK_BUS_SPI1]		= &bus_spi1_clk.common.hw,
+		[CLK_BUS_OTG]		= &bus_otg_clk.common.hw,
+		[CLK_BUS_EHCI0]		= &bus_ehci0_clk.common.hw,
+		[CLK_BUS_EHCI1]		= &bus_ehci1_clk.common.hw,
+		[CLK_BUS_OHCI0]		= &bus_ohci0_clk.common.hw,
+		[CLK_BUS_OHCI1]		= &bus_ohci1_clk.common.hw,
+		[CLK_BUS_VE]		= &bus_ve_clk.common.hw,
+		[CLK_BUS_TCON0]		= &bus_tcon0_clk.common.hw,
+		[CLK_BUS_TCON1]		= &bus_tcon1_clk.common.hw,
+		[CLK_BUS_DEINTERLACE]	= &bus_deinterlace_clk.common.hw,
+		[CLK_BUS_CSI]		= &bus_csi_clk.common.hw,
+		[CLK_BUS_HDMI]		= &bus_hdmi_clk.common.hw,
+		[CLK_BUS_DE]		= &bus_de_clk.common.hw,
+		[CLK_BUS_GPU]		= &bus_gpu_clk.common.hw,
+		[CLK_BUS_MSGBOX]	= &bus_msgbox_clk.common.hw,
+		[CLK_BUS_SPINLOCK]	= &bus_spinlock_clk.common.hw,
+		[CLK_BUS_CODEC]		= &bus_codec_clk.common.hw,
+		[CLK_BUS_SPDIF]		= &bus_spdif_clk.common.hw,
+		[CLK_BUS_PIO]		= &bus_pio_clk.common.hw,
+		[CLK_BUS_THS]		= &bus_ths_clk.common.hw,
+		[CLK_BUS_I2S0]		= &bus_i2s0_clk.common.hw,
+		[CLK_BUS_I2S1]		= &bus_i2s1_clk.common.hw,
+		[CLK_BUS_I2S2]		= &bus_i2s2_clk.common.hw,
+		[CLK_BUS_I2C0]		= &bus_i2c0_clk.common.hw,
+		[CLK_BUS_I2C1]		= &bus_i2c1_clk.common.hw,
+		[CLK_BUS_I2C2]		= &bus_i2c2_clk.common.hw,
+		[CLK_BUS_UART0]		= &bus_uart0_clk.common.hw,
+		[CLK_BUS_UART1]		= &bus_uart1_clk.common.hw,
+		[CLK_BUS_UART2]		= &bus_uart2_clk.common.hw,
+		[CLK_BUS_UART3]		= &bus_uart3_clk.common.hw,
+		[CLK_BUS_UART4]		= &bus_uart4_clk.common.hw,
+		[CLK_BUS_SCR]		= &bus_scr_clk.common.hw,
+		[CLK_BUS_DBG]		= &bus_dbg_clk.common.hw,
+		[CLK_THS]		= &ths_clk.common.hw,
+		[CLK_NAND]		= &nand_clk.common.hw,
+		[CLK_MMC0]		= &mmc0_clk.common.hw,
+		[CLK_MMC1]		= &mmc1_clk.common.hw,
+		[CLK_MMC2]		= &mmc2_clk.common.hw,
+		[CLK_TS]		= &ts_clk.common.hw,
+		[CLK_CE]		= &ce_clk.common.hw,
+		[CLK_SPI0]		= &spi0_clk.common.hw,
+		[CLK_SPI1]		= &spi1_clk.common.hw,
+		[CLK_I2S0]		= &i2s0_clk.common.hw,
+		[CLK_I2S1]		= &i2s1_clk.common.hw,
+		[CLK_I2S2]		= &i2s2_clk.common.hw,
+		[CLK_SPDIF]		= &spdif_clk.common.hw,
+		[CLK_USB_PHY0]		= &usb_phy0_clk.common.hw,
+		[CLK_USB_PHY1]		= &usb_phy1_clk.common.hw,
+		[CLK_USB_HSIC]		= &usb_hsic_clk.common.hw,
+		[CLK_USB_HSIC_12M]	= &usb_hsic_12m_clk.common.hw,
+		[CLK_USB_OHCI0]		= &usb_ohci0_clk.common.hw,
+		[CLK_USB_OHCI1]		= &usb_ohci1_clk.common.hw,
+		[CLK_DRAM]		= &dram_clk.common.hw,
+		[CLK_DRAM_VE]		= &dram_ve_clk.common.hw,
+		[CLK_DRAM_CSI]		= &dram_csi_clk.common.hw,
+		[CLK_DRAM_DEINTERLACE]	= &dram_deinterlace_clk.common.hw,
+		[CLK_DRAM_TS]		= &dram_ts_clk.common.hw,
+		[CLK_DE]		= &de_clk.common.hw,
+		[CLK_TCON0]		= &tcon0_clk.common.hw,
+		[CLK_TCON1]		= &tcon1_clk.common.hw,
+		[CLK_DEINTERLACE]	= &deinterlace_clk.common.hw,
+		[CLK_CSI_MISC]		= &csi_misc_clk.common.hw,
+		[CLK_CSI_SCLK]		= &csi_sclk_clk.common.hw,
+		[CLK_CSI_MCLK]		= &csi_mclk_clk.common.hw,
+		[CLK_VE]		= &ve_clk.common.hw,
+		[CLK_AC_DIG]		= &ac_dig_clk.common.hw,
+		[CLK_AC_DIG_4X]		= &ac_dig_4x_clk.common.hw,
+		[CLK_AVS]		= &avs_clk.common.hw,
+		[CLK_HDMI]		= &hdmi_clk.common.hw,
+		[CLK_HDMI_DDC]		= &hdmi_ddc_clk.common.hw,
+		[CLK_MBUS]		= &mbus_clk.common.hw,
+		[CLK_DSI_DPHY]		= &dsi_dphy_clk.common.hw,
+		[CLK_GPU]		= &gpu_clk.common.hw,
+	},
+	.num	= CLK_NUMBER,
+};
+
+const struct sunxi_ccu_desc sun50i_a64_ccu_desc = {
+	.ccu_clks	= sun50i_a64_ccu_clks,
+	.num_ccu_clks	= ARRAY_SIZE(sun50i_a64_ccu_clks),
+
+	.hw_clks	= &sun50i_a64_hw_clks,
+};
+
diff --git a/drivers/clk/sunxi/ccu-sun50i-a64.h b/drivers/clk/sunxi/ccu-sun50i-a64.h
new file mode 100644
index 0000000..daf45e2
--- /dev/null
+++ b/drivers/clk/sunxi/ccu-sun50i-a64.h
@@ -0,0 +1,64 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu-sun50i-a64.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_SUN50I_A64_H_
+#define _CCU_SUN50I_A64_H_
+
+#include <dt-bindings/clock/sun50i-a64-ccu.h>
+#include <dt-bindings/reset/sun50i-a64-ccu.h>
+
+#define CLK_OSC_12M			0
+#define CLK_PLL_CPUX			1
+#define CLK_PLL_AUDIO_BASE		2
+#define CLK_PLL_AUDIO			3
+#define CLK_PLL_AUDIO_2X		4
+#define CLK_PLL_AUDIO_4X		5
+#define CLK_PLL_AUDIO_8X		6
+#define CLK_PLL_VIDEO0			7
+#define CLK_PLL_VIDEO0_2X		8
+#define CLK_PLL_VE			9
+#define CLK_PLL_DDR0			10
+#define CLK_PLL_PERIPH0			11
+#define CLK_PLL_PERIPH0_2X		12
+#define CLK_PLL_PERIPH1			13
+#define CLK_PLL_PERIPH1_2X		14
+#define CLK_PLL_VIDEO1			15
+#define CLK_PLL_GPU			16
+#define CLK_PLL_MIPI			17
+#define CLK_PLL_HSIC			18
+#define CLK_PLL_DE			19
+#define CLK_PLL_DDR1			20
+#define CLK_CPUX			21
+#define CLK_AXI				22
+#define CLK_APB				23
+#define CLK_AHB1			24
+#define CLK_APB1			25
+#define CLK_APB2			26
+#define CLK_AHB2			27
+
+/* All the bus gates are exported */
+
+/* The first bunch of module clocks are exported */
+
+#define CLK_USB_OHCI0_12M		90
+
+#define CLK_USB_OHCI1_12M		92
+
+#define CLK_DRAM			94
+
+/* All the DRAM gates are exported */
+
+/* Some more module clocks are exported */
+
+#define CLK_MBUS			112
+
+/* And the DSI and GPU module clock is exported */
+
+#define CLK_NUMBER			(CLK_GPU + 1)
+
+#endif /* _CCU_SUN50I_A64_H_ */
diff --git a/drivers/clk/sunxi/ccu_common.c b/drivers/clk/sunxi/ccu_common.c
new file mode 100644
index 0000000..6dba33c
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_common.c
@@ -0,0 +1,27 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_common.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * Ported to U-Boot by
+ *   Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <linux/compat.h>
+#include <linux/iopoll.h>
+
+#include "ccu_common.h"
+
+void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock)
+{
+	u32 reg;
+
+	if (!lock)
+		return;
+
+	WARN_ON(readl_poll_timeout(common->base + common->reg, reg,
+				   reg & lock, 70000));
+}
diff --git a/drivers/clk/sunxi/ccu_common.h b/drivers/clk/sunxi/ccu_common.h
new file mode 100644
index 0000000..719690e
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_common.h
@@ -0,0 +1,67 @@
+/*
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _SUNXI_CLK_CCU_COMMON_H_
+#define _SUNXI_CLK_CCU_COMMON_H_
+
+#include "ccu-compatibility.h"
+#include <clk-uclass.h>
+
+#define CCU_FEATURE_FRACTIONAL		BIT(0)
+#define CCU_FEATURE_VARIABLE_PREDIV	BIT(1)
+#define CCU_FEATURE_FIXED_PREDIV	BIT(2)
+#define CCU_FEATURE_FIXED_POSTDIV	BIT(3)
+
+#define CLK_HW_INIT(_name, _parent, _ops, _flags)			\
+	&(struct clk_init_data) {					\
+		.flags		= _flags,				\
+		.name		= _name,				\
+		.parent_names	= (const char *[]) { _parent },		\
+		.num_parents	= 1,					\
+		.ops            = _ops,				        \
+	}
+
+#define CLK_HW_INIT_PARENTS(_name, _parents, _ops, _flags)		\
+	&(struct clk_init_data) {					\
+		.flags		= _flags,				\
+		.name		= _name,				\
+		.parent_names	= _parents,				\
+		.num_parents	= ARRAY_SIZE(_parents),			\
+		.ops            = _ops,				        \
+	}
+
+#define CLK_FIXED_FACTOR(_struct, _name, _parent,			\
+			_div, _mult, _flags)				\
+	struct clk_fixed_factor _struct = {				\
+		.div		= _div,					\
+		.mult		= _mult,				\
+		.hw.init	= CLK_HW_INIT(_name,			\
+					      _parent,			\
+					      &clk_fixed_factor_ops,	\
+					      _flags),			\
+	}
+
+struct ccu_common {
+	void __iomem	*base;
+	u16		reg;
+
+	unsigned long	features;
+	struct clk_hw	hw;
+};
+
+static inline struct ccu_common *hw_to_ccu_common(struct clk_hw *hw)
+{
+	return container_of(hw, struct ccu_common, hw);
+}
+
+struct sunxi_ccu_desc {
+	struct ccu_common		**ccu_clks;
+	unsigned long			num_ccu_clks;
+
+	struct clk_hw_onecell_data	*hw_clks;
+};
+
+void ccu_helper_wait_for_lock(struct ccu_common *common, u32 lock);
+
+#endif /* _COMMON_H_ */
diff --git a/drivers/clk/sunxi/ccu_div.c b/drivers/clk/sunxi/ccu_div.c
new file mode 100644
index 0000000..b22770c
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_div.c
@@ -0,0 +1,134 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_div.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+#include "ccu_div.h"
+
+static unsigned long ccu_div_round_rate(struct ccu_mux_internal *mux,
+					unsigned long parent_rate,
+					unsigned long rate,
+					void *data)
+{
+	struct ccu_div *cd = data;
+	unsigned long val;
+
+	/*
+	 * We can't use divider_round_rate that assumes that there's
+	 * several parents, while we might be called to evaluate
+	 * several different parents.
+	 */
+	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
+			      cd->div.flags);
+
+	return divider_recalc_rate(&cd->common.hw, parent_rate, val,
+				   cd->div.table, cd->div.flags);
+}
+
+static void ccu_div_disable(struct clk_hw *hw)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_gate_helper_disable(&cd->common, cd->enable);
+}
+
+static int ccu_div_enable(struct clk_hw *hw)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_gate_helper_enable(&cd->common, cd->enable);
+}
+
+static int ccu_div_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_gate_helper_is_enabled(&cd->common, cd->enable);
+}
+
+static unsigned long ccu_div_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+	unsigned long val;
+	u32 reg;
+
+	reg = readl(cd->common.base + cd->common.reg);
+	val = reg >> cd->div.shift;
+	val &= (1 << cd->div.width) - 1;
+
+	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
+						&parent_rate);
+
+	return divider_recalc_rate(hw, parent_rate, val, cd->div.table,
+				   cd->div.flags);
+}
+
+static int ccu_div_determine_rate(struct clk_hw *hw,
+				struct clk_rate_request *req)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_mux_helper_determine_rate(&cd->common, &cd->mux,
+					     req, ccu_div_round_rate, cd);
+}
+
+static int ccu_div_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+	unsigned long flags;
+	unsigned long val;
+	u32 reg;
+
+	ccu_mux_helper_adjust_parent_for_prediv(&cd->common, &cd->mux, -1,
+						&parent_rate);
+
+	val = divider_get_val(rate, parent_rate, cd->div.table, cd->div.width,
+			      cd->div.flags);
+
+	spin_lock_irqsave(cd->common.lock, flags);
+
+	reg = readl(cd->common.base + cd->common.reg);
+	reg &= ~GENMASK(cd->div.width + cd->div.shift - 1, cd->div.shift);
+
+	writel(reg | (val << cd->div.shift),
+	       cd->common.base + cd->common.reg);
+
+	spin_unlock_irqrestore(cd->common.lock, flags);
+
+	return 0;
+}
+
+static u8 ccu_div_get_parent(struct clk_hw *hw)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_mux_helper_get_parent(&cd->common, &cd->mux);
+}
+
+static int ccu_div_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu_div *cd = hw_to_ccu_div(hw);
+
+	return ccu_mux_helper_set_parent(&cd->common, &cd->mux, index);
+}
+
+const struct sunxi_ccu_clk_ops ccu_div_ops = {
+	.disable	= ccu_div_disable,
+	.enable		= ccu_div_enable,
+	.is_enabled	= ccu_div_is_enabled,
+
+	.get_parent	= ccu_div_get_parent,
+	.set_parent	= ccu_div_set_parent,
+
+	.determine_rate	= ccu_div_determine_rate,
+	.recalc_rate	= ccu_div_recalc_rate,
+	.set_rate	= ccu_div_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_div.h b/drivers/clk/sunxi/ccu_div.h
new file mode 100644
index 0000000..9f38314
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_div.h
@@ -0,0 +1,170 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_div.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_DIV_H_
+#define _CCU_DIV_H_
+
+#include "ccu_common.h"
+#include "ccu_mux.h"
+
+/**
+ * struct ccu_div_internal - Internal divider description
+ * @shift: Bit offset of the divider in its register
+ * @width: Width of the divider field in its register
+ * @max: Maximum value allowed for that divider. This is the
+ *       arithmetic value, not the maximum value to be set in the
+ *       register.
+ * @flags: clk_divider flags to apply on this divider
+ * @table: Divider table pointer (if applicable)
+ *
+ * That structure represents a single divider, and is meant to be
+ * embedded in other structures representing the various clock
+ * classes.
+ *
+ * It is basically a wrapper around the clk_divider functions
+ * arguments.
+ */
+struct ccu_div_internal {
+	u8			shift;
+	u8			width;
+
+	u32			max;
+
+	u32			flags;
+
+	struct clk_div_table	*table;
+};
+
+#define _SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, _flags)	\
+	{								\
+		.shift	= _shift,					\
+		.width	= _width,					\
+		.flags	= _flags,					\
+		.table	= _table,					\
+	}
+
+#define _SUNXI_CCU_DIV_TABLE(_shift, _width, _table)			\
+	_SUNXI_CCU_DIV_TABLE_FLAGS(_shift, _width, _table, 0)
+
+#define _SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, _max, _flags) \
+	{								\
+		.shift	= _shift,					\
+		.width	= _width,					\
+		.flags	= _flags,					\
+		.max	= _max,						\
+	}
+
+#define _SUNXI_CCU_DIV_FLAGS(_shift, _width, _flags)			\
+	_SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, 0, _flags)
+
+#define _SUNXI_CCU_DIV_MAX(_shift, _width, _max)			\
+	_SUNXI_CCU_DIV_MAX_FLAGS(_shift, _width, _max, 0)
+
+#define _SUNXI_CCU_DIV(_shift, _width)					\
+	_SUNXI_CCU_DIV_FLAGS(_shift, _width, 0)
+
+struct ccu_div {
+	u32			enable;
+
+	struct ccu_div_internal		div;
+	struct ccu_mux_internal	mux;
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg,	\
+				      _shift, _width,			\
+				      _table, _gate, _flags)		\
+	struct ccu_div _struct = {					\
+		.div		= _SUNXI_CCU_DIV_TABLE(_shift, _width,	\
+						       _table),		\
+		.enable		= _gate,				\
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_div_ops,	\
+						      _flags),		\
+		}							\
+	}
+
+
+#define SUNXI_CCU_DIV_TABLE(_struct, _name, _parent, _reg,		\
+			    _shift, _width,				\
+			    _table, _flags)				\
+	SUNXI_CCU_DIV_TABLE_WITH_GATE(_struct, _name, _parent, _reg,	\
+				      _shift, _width, _table, 0,	\
+				      _flags)
+
+#define SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name,			\
+					_parents, _table,		\
+					_reg,				\
+					_mshift, _mwidth,		\
+					_muxshift, _muxwidth,		\
+					_gate, _flags)			\
+	struct ccu_div _struct = {					\
+		.enable	= _gate,					\
+		.div	= _SUNXI_CCU_DIV(_mshift, _mwidth),		\
+		.mux	= _SUNXI_CCU_MUX_TABLE(_muxshift, _muxwidth, _table), \
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT_PARENTS(_name,	\
+							      _parents, \
+							      &ccu_div_ops, \
+							      _flags),	\
+		},							\
+	}
+
+#define SUNXI_CCU_M_WITH_MUX_GATE(_struct, _name, _parents, _reg,	\
+				  _mshift, _mwidth, _muxshift, _muxwidth, \
+				  _gate, _flags)			\
+	SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name,			\
+					_parents, NULL,			\
+					_reg, _mshift, _mwidth,		\
+					_muxshift, _muxwidth,		\
+					_gate, _flags)
+
+#define SUNXI_CCU_M_WITH_MUX(_struct, _name, _parents, _reg,		\
+			     _mshift, _mwidth, _muxshift, _muxwidth,	\
+			     _flags)					\
+	SUNXI_CCU_M_WITH_MUX_TABLE_GATE(_struct, _name,			\
+					_parents, NULL,			\
+					_reg, _mshift, _mwidth,		\
+					_muxshift, _muxwidth,		\
+					0, _flags)
+
+
+#define SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg,		\
+			      _mshift, _mwidth,	_gate,			\
+			      _flags)					\
+	struct ccu_div _struct = {					\
+		.enable	= _gate,					\
+		.div	= _SUNXI_CCU_DIV(_mshift, _mwidth),		\
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_div_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+#define SUNXI_CCU_M(_struct, _name, _parent, _reg, _mshift, _mwidth,	\
+		    _flags)						\
+	SUNXI_CCU_M_WITH_GATE(_struct, _name, _parent, _reg,		\
+			      _mshift, _mwidth, 0, _flags)
+
+static inline struct ccu_div *hw_to_ccu_div(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_div, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_div_ops;
+
+#endif /* _CCU_DIV_H_ */
diff --git a/drivers/clk/sunxi/ccu_frac.c b/drivers/clk/sunxi/ccu_frac.c
new file mode 100644
index 0000000..baecf95
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_frac.c
@@ -0,0 +1,106 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_common.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include "ccu_frac.h"
+
+bool ccu_frac_helper_is_enabled(struct ccu_common *common,
+				struct ccu_frac_internal *cf)
+{
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return false;
+
+	return !(readl(common->base + common->reg) & cf->enable);
+}
+
+void ccu_frac_helper_enable(struct ccu_common *common,
+			    struct ccu_frac_internal *cf)
+{
+	unsigned long flags;
+	u32 reg;
+
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return;
+
+	spin_lock_irqsave(common->lock, flags);
+	reg = readl(common->base + common->reg);
+	writel(reg & ~cf->enable, common->base + common->reg);
+	spin_unlock_irqrestore(common->lock, flags);
+}
+
+void ccu_frac_helper_disable(struct ccu_common *common,
+			     struct ccu_frac_internal *cf)
+{
+	unsigned long flags;
+	u32 reg;
+
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return;
+
+	spin_lock_irqsave(common->lock, flags);
+	reg = readl(common->base + common->reg);
+	writel(reg | cf->enable, common->base + common->reg);
+	spin_unlock_irqrestore(common->lock, flags);
+}
+
+bool ccu_frac_helper_has_rate(struct ccu_common *common,
+			      struct ccu_frac_internal *cf,
+			      unsigned long rate)
+{
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return false;
+
+	return (cf->rates[0] == rate) || (cf->rates[1] == rate);
+}
+
+unsigned long ccu_frac_helper_read_rate(struct ccu_common *common,
+					struct ccu_frac_internal *cf)
+{
+	u32 reg;
+
+	printk("%s: Read fractional\n", clk_hw_get_name(&common->hw));
+
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return 0;
+
+	printk("%s: clock is fractional (rates %lu and %lu)\n",
+	       clk_hw_get_name(&common->hw), cf->rates[0], cf->rates[1]);
+
+	reg = readl(common->base + common->reg);
+
+	printk("%s: clock reg is 0x%x (select is 0x%x)\n",
+	       clk_hw_get_name(&common->hw), reg, cf->select);
+
+	return (reg & cf->select) ? cf->rates[1] : cf->rates[0];
+}
+
+int ccu_frac_helper_set_rate(struct ccu_common *common,
+			     struct ccu_frac_internal *cf,
+			     unsigned long rate)
+{
+	unsigned long flags;
+	u32 reg, sel;
+
+	if (!(common->features & CCU_FEATURE_FRACTIONAL))
+		return -EINVAL;
+
+	if (cf->rates[0] == rate)
+		sel = 0;
+	else if (cf->rates[1] == rate)
+		sel = cf->select;
+	else
+		return -EINVAL;
+
+	spin_lock_irqsave(common->lock, flags);
+	reg = readl(common->base + common->reg);
+	reg &= ~cf->select;
+	writel(reg | sel, common->base + common->reg);
+	spin_unlock_irqrestore(common->lock, flags);
+
+	return 0;
+}
diff --git a/drivers/clk/sunxi/ccu_frac.h b/drivers/clk/sunxi/ccu_frac.h
new file mode 100644
index 0000000..aad82fa
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_frac.h
@@ -0,0 +1,46 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_common.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_FRAC_H_
+#define _CCU_FRAC_H_
+
+#include "ccu_common.h"
+
+struct ccu_frac_internal {
+	u32		enable;
+	u32		select;
+
+	unsigned long	rates[2];
+};
+
+#define _SUNXI_CCU_FRAC(_enable, _select, _rate1, _rate2)		\
+	{								\
+		.enable	= _enable,					\
+		.select	= _select,					\
+		.rates = { _rate1, _rate2 },				\
+	}
+
+bool ccu_frac_helper_is_enabled(struct ccu_common *common,
+				struct ccu_frac_internal *cf);
+void ccu_frac_helper_enable(struct ccu_common *common,
+			    struct ccu_frac_internal *cf);
+void ccu_frac_helper_disable(struct ccu_common *common,
+			     struct ccu_frac_internal *cf);
+
+bool ccu_frac_helper_has_rate(struct ccu_common *common,
+			      struct ccu_frac_internal *cf,
+			      unsigned long rate);
+
+unsigned long ccu_frac_helper_read_rate(struct ccu_common *common,
+					struct ccu_frac_internal *cf);
+
+int ccu_frac_helper_set_rate(struct ccu_common *common,
+			     struct ccu_frac_internal *cf,
+			     unsigned long rate);
+
+#endif /* _CCU_FRAC_H_ */
diff --git a/drivers/clk/sunxi/ccu_gate.c b/drivers/clk/sunxi/ccu_gate.c
new file mode 100644
index 0000000..75d685f
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_gate.c
@@ -0,0 +1,80 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_gate.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+
+void ccu_gate_helper_disable(struct ccu_common *common, u32 gate)
+{
+	unsigned long flags;
+	u32 reg;
+
+	if (!gate)
+		return;
+
+	spin_lock_irqsave(common->lock, flags);
+
+	reg = readl(common->base + common->reg);
+	writel(reg & ~gate, common->base + common->reg);
+
+	spin_unlock_irqrestore(common->lock, flags);
+}
+
+static void ccu_gate_disable(struct clk_hw *hw)
+{
+	struct ccu_gate *cg = hw_to_ccu_gate(hw);
+
+	return ccu_gate_helper_disable(&cg->common, cg->enable);
+}
+
+int ccu_gate_helper_enable(struct ccu_common *common, u32 gate)
+{
+	unsigned long flags;
+	u32 reg;
+
+	if (!gate)
+		return 0;
+
+	spin_lock_irqsave(common->lock, flags);
+
+	reg = readl(common->base + common->reg);
+	writel(reg | gate, common->base + common->reg);
+
+	spin_unlock_irqrestore(common->lock, flags);
+
+	return 0;
+}
+
+static int ccu_gate_enable(struct clk_hw *hw)
+{
+	struct ccu_gate *cg = hw_to_ccu_gate(hw);
+
+	return ccu_gate_helper_enable(&cg->common, cg->enable);
+}
+
+int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate)
+{
+	if (!gate)
+		return 1;
+
+	return readl(common->base + common->reg) & gate;
+}
+
+static int ccu_gate_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_gate *cg = hw_to_ccu_gate(hw);
+
+	return ccu_gate_helper_is_enabled(&cg->common, cg->enable);
+}
+
+const struct sunxi_ccu_clk_ops ccu_gate_ops = {
+	.disable	= ccu_gate_disable,
+	.enable		= ccu_gate_enable,
+	.is_enabled	= ccu_gate_is_enabled,
+};
diff --git a/drivers/clk/sunxi/ccu_gate.h b/drivers/clk/sunxi/ccu_gate.h
new file mode 100644
index 0000000..7b48f13
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_gate.h
@@ -0,0 +1,45 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_gate.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_GATE_H_
+#define _CCU_GATE_H_
+
+#include "ccu_common.h"
+
+struct ccu_gate {
+	u32			enable;
+
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_GATE(_struct, _name, _parent, _reg, _gate, _flags)	\
+	struct ccu_gate _struct = {					\
+		.enable	= _gate,					\
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_gate_ops,	\
+						      _flags),		\
+		}							\
+	}
+
+static inline struct ccu_gate *hw_to_ccu_gate(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_gate, common);
+}
+
+void ccu_gate_helper_disable(struct ccu_common *common, u32 gate);
+int ccu_gate_helper_enable(struct ccu_common *common, u32 gate);
+int ccu_gate_helper_is_enabled(struct ccu_common *common, u32 gate);
+
+extern const struct sunxi_ccu_clk_ops ccu_gate_ops;
+
+#endif /* _CCU_GATE_H_ */
diff --git a/drivers/clk/sunxi/ccu_mp.c b/drivers/clk/sunxi/ccu_mp.c
new file mode 100644
index 0000000..d5410e0
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_mp.c
@@ -0,0 +1,159 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mp.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+#include "ccu_mp.h"
+
+static void ccu_mp_find_best(unsigned long parent, unsigned long rate,
+			     unsigned int max_m, unsigned int max_p,
+			     unsigned int *m, unsigned int *p)
+{
+	unsigned long best_rate = 0;
+	unsigned int best_m = 0, best_p = 0;
+	unsigned int _m, _p;
+
+	for (_p = 1; _p <= max_p; _p <<= 1) {
+		for (_m = 1; _m <= max_m; _m++) {
+			unsigned long tmp_rate = parent / _p / _m;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if ((rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_m = _m;
+				best_p = _p;
+			}
+		}
+	}
+
+	*m = best_m;
+	*p = best_p;
+}
+
+static unsigned long ccu_mp_round_rate(struct ccu_mux_internal *mux,
+				       unsigned long parent_rate,
+				       unsigned long rate,
+				       void *data)
+{
+	struct ccu_mp *cmp = data;
+	unsigned int max_m, max_p;
+	unsigned int m, p;
+
+	max_m = cmp->m.max ?: 1 << cmp->m.width;
+	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
+
+	ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
+
+	return parent_rate / p / m;
+}
+
+static void ccu_mp_disable(struct clk_hw *hw)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_gate_helper_disable(&cmp->common, cmp->enable);
+}
+
+static int ccu_mp_enable(struct clk_hw *hw)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_gate_helper_enable(&cmp->common, cmp->enable);
+}
+
+static int ccu_mp_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_gate_helper_is_enabled(&cmp->common, cmp->enable);
+}
+
+static unsigned long ccu_mp_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+	unsigned int m, p;
+	u32 reg;
+
+	reg = readl(cmp->common.base + cmp->common.reg);
+
+	m = reg >> cmp->m.shift;
+	m &= (1 << cmp->m.width) - 1;
+
+	p = reg >> cmp->p.shift;
+	p &= (1 << cmp->p.width) - 1;
+
+	return (parent_rate >> p) / (m + 1);
+}
+
+static int ccu_mp_determine_rate(struct clk_hw *hw,
+				 struct clk_rate_request *req)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_mux_helper_determine_rate(&cmp->common, &cmp->mux,
+					     req, ccu_mp_round_rate, cmp);
+}
+
+static int ccu_mp_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+	unsigned long flags;
+	unsigned int max_m, max_p;
+	unsigned int m, p;
+	u32 reg;
+
+	max_m = cmp->m.max ?: 1 << cmp->m.width;
+	max_p = cmp->p.max ?: 1 << ((1 << cmp->p.width) - 1);
+
+	ccu_mp_find_best(parent_rate, rate, max_m, max_p, &m, &p);
+
+	spin_lock_irqsave(cmp->common.lock, flags);
+
+	reg = readl(cmp->common.base + cmp->common.reg);
+	reg &= ~GENMASK(cmp->m.width + cmp->m.shift - 1, cmp->m.shift);
+	reg &= ~GENMASK(cmp->p.width + cmp->p.shift - 1, cmp->p.shift);
+
+	writel(reg | (ilog2(p) << cmp->p.shift) | ((m - 1) << cmp->m.shift),
+	       cmp->common.base + cmp->common.reg);
+
+	spin_unlock_irqrestore(cmp->common.lock, flags);
+
+	return 0;
+}
+
+static u8 ccu_mp_get_parent(struct clk_hw *hw)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_mux_helper_get_parent(&cmp->common, &cmp->mux);
+}
+
+static int ccu_mp_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu_mp *cmp = hw_to_ccu_mp(hw);
+
+	return ccu_mux_helper_set_parent(&cmp->common, &cmp->mux, index);
+}
+
+const struct sunxi_ccu_clk_ops ccu_mp_ops = {
+	.disable	= ccu_mp_disable,
+	.enable		= ccu_mp_enable,
+	.is_enabled	= ccu_mp_is_enabled,
+
+	.get_parent	= ccu_mp_get_parent,
+	.set_parent	= ccu_mp_set_parent,
+
+	.determine_rate	= ccu_mp_determine_rate,
+	.recalc_rate	= ccu_mp_recalc_rate,
+	.set_rate	= ccu_mp_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_mp.h b/drivers/clk/sunxi/ccu_mp.h
new file mode 100644
index 0000000..36267cd
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_mp.h
@@ -0,0 +1,70 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mp.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_MP_H_
+#define _CCU_MP_H_
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_mult.h"
+#include "ccu_mux.h"
+
+/*
+ * struct ccu_mp - Definition of an M-P clock
+ *
+ * Clocks based on the formula parent >> P / M
+ */
+struct ccu_mp {
+	u32			enable;
+
+	struct ccu_div_internal		m;
+	struct ccu_div_internal		p;
+	struct ccu_mux_internal	mux;
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg,	\
+				   _mshift, _mwidth,			\
+				   _pshift, _pwidth,			\
+				   _muxshift, _muxwidth,		\
+				   _gate, _flags)			\
+	struct ccu_mp _struct = {					\
+		.enable	= _gate,					\
+		.m	= _SUNXI_CCU_DIV(_mshift, _mwidth),		\
+		.p	= _SUNXI_CCU_DIV(_pshift, _pwidth),		\
+		.mux	= _SUNXI_CCU_MUX(_muxshift, _muxwidth),		\
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT_PARENTS(_name,	\
+							      _parents, \
+							      &ccu_mp_ops, \
+							      _flags),	\
+		}							\
+	}
+
+#define SUNXI_CCU_MP_WITH_MUX(_struct, _name, _parents, _reg,		\
+			      _mshift, _mwidth,				\
+			      _pshift, _pwidth,				\
+			      _muxshift, _muxwidth,			\
+			      _flags)					\
+	SUNXI_CCU_MP_WITH_MUX_GATE(_struct, _name, _parents, _reg,	\
+				   _mshift, _mwidth,			\
+				   _pshift, _pwidth,			\
+				   _muxshift, _muxwidth,		\
+				   0, _flags)
+
+static inline struct ccu_mp *hw_to_ccu_mp(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_mp, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_mp_ops;
+
+#endif /* _CCU_MP_H_ */
diff --git a/drivers/clk/sunxi/ccu_mult.h b/drivers/clk/sunxi/ccu_mult.h
new file mode 100644
index 0000000..2d77fdc
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_mult.h
@@ -0,0 +1,39 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mult.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_MULT_H_
+#define _CCU_MULT_H_
+
+#include "ccu_common.h"
+#include "ccu_mux.h"
+
+struct ccu_mult_internal {
+	u8	shift;
+	u8	width;
+	u8	min;
+};
+
+#define _SUNXI_CCU_MULT_MIN(_shift, _width, _min)	\
+	{						\
+		.shift	= _shift,			\
+		.width	= _width,			\
+		.min	= _min,				\
+	}
+
+#define _SUNXI_CCU_MULT(_shift, _width)		\
+	_SUNXI_CCU_MULT_MIN(_shift, _width, 1)
+
+struct ccu_mult {
+	u32			enable;
+
+	struct ccu_mult_internal	mult;
+	struct ccu_mux_internal	mux;
+	struct ccu_common	common;
+};
+
+#endif /* _CCU_MULT_H_ */
diff --git a/drivers/clk/sunxi/ccu_mux.c b/drivers/clk/sunxi/ccu_mux.c
new file mode 100644
index 0000000..ba055b8
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_mux.c
@@ -0,0 +1,201 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mux.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <linux/delay.h>
+
+#include "ccu_gate.h"
+#include "ccu_mux.h"
+
+void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
+					     struct ccu_mux_internal *cm,
+					     int parent_index,
+					     unsigned long *parent_rate)
+{
+	u16 prediv = 1;
+	u32 reg;
+	int i;
+
+	if (!((common->features & CCU_FEATURE_FIXED_PREDIV) ||
+	      (common->features & CCU_FEATURE_VARIABLE_PREDIV)))
+		return;
+
+	reg = readl(common->base + common->reg);
+	if (parent_index < 0) {
+		parent_index = reg >> cm->shift;
+		parent_index &= (1 << cm->width) - 1;
+	}
+
+	if (common->features & CCU_FEATURE_FIXED_PREDIV)
+		for (i = 0; i < cm->n_predivs; i++)
+			if (parent_index == cm->fixed_predivs[i].index)
+				prediv = cm->fixed_predivs[i].div;
+
+	if (common->features & CCU_FEATURE_VARIABLE_PREDIV)
+		if (parent_index == cm->variable_prediv.index) {
+			u8 div;
+
+			div = reg >> cm->variable_prediv.shift;
+			div &= (1 << cm->variable_prediv.width) - 1;
+			prediv = div + 1;
+		}
+
+	*parent_rate = *parent_rate / prediv;
+}
+
+int ccu_mux_helper_determine_rate(struct ccu_common *common,
+				  struct ccu_mux_internal *cm,
+				  struct clk_rate_request *req,
+				  unsigned long (*round)(struct ccu_mux_internal *,
+							 unsigned long,
+							 unsigned long,
+							 void *),
+				  void *data)
+{
+	unsigned long best_parent_rate = 0, best_rate = 0;
+	struct clk_hw *best_parent, *hw = &common->hw;
+	unsigned int i;
+
+	for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+		unsigned long tmp_rate, parent_rate;
+		struct clk_hw *parent;
+
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		parent_rate = clk_hw_get_rate(parent);
+		ccu_mux_helper_adjust_parent_for_prediv(common, cm, i,
+							&parent_rate);
+
+		tmp_rate = round(cm, clk_hw_get_rate(parent), req->rate, data);
+		if (tmp_rate == req->rate) {
+			best_parent = parent;
+			best_parent_rate = parent_rate;
+			best_rate = tmp_rate;
+			goto out;
+		}
+
+		if ((req->rate - tmp_rate) < (req->rate - best_rate)) {
+			best_rate = tmp_rate;
+			best_parent_rate = parent_rate;
+			best_parent = parent;
+		}
+	}
+
+	if (best_rate == 0)
+		return -EINVAL;
+
+out:
+	req->best_parent_hw = best_parent;
+	req->best_parent_rate = best_parent_rate;
+	req->rate = best_rate;
+	return 0;
+}
+
+u8 ccu_mux_helper_get_parent(struct ccu_common *common,
+			     struct ccu_mux_internal *cm)
+{
+	u32 reg;
+	u8 parent;
+
+	reg = readl(common->base + common->reg);
+	parent = reg >> cm->shift;
+	parent &= (1 << cm->width) - 1;
+
+	if (cm->table) {
+		int num_parents = clk_hw_get_num_parents(&common->hw);
+		int i;
+
+		for (i = 0; i < num_parents; i++)
+			if (cm->table[i] == parent)
+				return i;
+	}
+
+	return parent;
+}
+
+int ccu_mux_helper_set_parent(struct ccu_common *common,
+			      struct ccu_mux_internal *cm,
+			      u8 index)
+{
+	unsigned long flags;
+	u32 reg;
+
+	if (cm->table)
+		index = cm->table[index];
+
+	spin_lock_irqsave(common->lock, flags);
+
+	reg = readl(common->base + common->reg);
+	reg &= ~GENMASK(cm->width + cm->shift - 1, cm->shift);
+	writel(reg | (index << cm->shift), common->base + common->reg);
+
+	spin_unlock_irqrestore(common->lock, flags);
+
+	return 0;
+}
+
+static void ccu_mux_disable(struct clk_hw *hw)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	return ccu_gate_helper_disable(&cm->common, cm->enable);
+}
+
+static int ccu_mux_enable(struct clk_hw *hw)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	return ccu_gate_helper_enable(&cm->common, cm->enable);
+}
+
+static int ccu_mux_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	return ccu_gate_helper_is_enabled(&cm->common, cm->enable);
+}
+
+static u8 ccu_mux_get_parent(struct clk_hw *hw)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	return ccu_mux_helper_get_parent(&cm->common, &cm->mux);
+}
+
+static int ccu_mux_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	return ccu_mux_helper_set_parent(&cm->common, &cm->mux, index);
+}
+
+static unsigned long ccu_mux_recalc_rate(struct clk_hw *hw,
+					 unsigned long parent_rate)
+{
+	struct ccu_mux *cm = hw_to_ccu_mux(hw);
+
+	ccu_mux_helper_adjust_parent_for_prediv(&cm->common, &cm->mux, -1,
+						&parent_rate);
+
+	return parent_rate;
+}
+
+const struct sunxi_ccu_clk_ops ccu_mux_ops = {
+	.disable	= ccu_mux_disable,
+	.enable		= ccu_mux_enable,
+	.is_enabled	= ccu_mux_is_enabled,
+
+	.get_parent	= ccu_mux_get_parent,
+	.set_parent	= ccu_mux_set_parent,
+
+	.determine_rate	= __clk_mux_determine_rate,
+	.recalc_rate	= ccu_mux_recalc_rate,
+};
+
diff --git a/drivers/clk/sunxi/ccu_mux.h b/drivers/clk/sunxi/ccu_mux.h
new file mode 100644
index 0000000..9d094be
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_mux.h
@@ -0,0 +1,105 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mux.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_MUX_H_
+#define _CCU_MUX_H_
+
+#include "ccu_common.h"
+
+struct ccu_mux_fixed_prediv {
+	u8	index;
+	u16	div;
+};
+
+struct ccu_mux_internal {
+	u8		shift;
+	u8		width;
+	const u8	*table;
+
+	const struct ccu_mux_fixed_prediv	*fixed_predivs;
+	u8		n_predivs;
+
+	struct {
+		u8	index;
+		u8	shift;
+		u8	width;
+	} variable_prediv;
+};
+
+#define _SUNXI_CCU_MUX_TABLE(_shift, _width, _table)	\
+	{						\
+		.shift	= _shift,			\
+		.width	= _width,			\
+		.table	= _table,			\
+	}
+
+#define _SUNXI_CCU_MUX(_shift, _width) \
+	_SUNXI_CCU_MUX_TABLE(_shift, _width, NULL)
+
+struct ccu_mux {
+	u16			reg;
+	u32			enable;
+
+	struct ccu_mux_internal	mux;
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, _table,	\
+				     _reg, _shift, _width, _gate,	\
+				     _flags)				\
+	struct ccu_mux _struct = {					\
+		.enable	= _gate,					\
+		.mux	= _SUNXI_CCU_MUX_TABLE(_shift, _width, _table),	\
+		.common	= {						\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT_PARENTS(_name,	\
+							      _parents, \
+							      &ccu_mux_ops, \
+							      _flags),	\
+		}							\
+	}
+
+#define SUNXI_CCU_MUX_WITH_GATE(_struct, _name, _parents, _reg,		\
+				_shift, _width, _gate, _flags)		\
+	SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, NULL,	\
+				      _reg, _shift, _width, _gate,	\
+				      _flags)
+
+#define SUNXI_CCU_MUX(_struct, _name, _parents, _reg, _shift, _width,	\
+		      _flags)						\
+	SUNXI_CCU_MUX_TABLE_WITH_GATE(_struct, _name, _parents, NULL,	\
+				      _reg, _shift, _width, 0, _flags)
+
+static inline struct ccu_mux *hw_to_ccu_mux(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_mux, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_mux_ops;
+
+void ccu_mux_helper_adjust_parent_for_prediv(struct ccu_common *common,
+					     struct ccu_mux_internal *cm,
+					     int parent_index,
+					     unsigned long *parent_rate);
+int ccu_mux_helper_determine_rate(struct ccu_common *common,
+				  struct ccu_mux_internal *cm,
+				  struct clk_rate_request *req,
+				  unsigned long (*round)(struct ccu_mux_internal *,
+							 unsigned long,
+							 unsigned long,
+							 void *),
+				  void *data);
+u8 ccu_mux_helper_get_parent(struct ccu_common *common,
+			     struct ccu_mux_internal *cm);
+int ccu_mux_helper_set_parent(struct ccu_common *common,
+			      struct ccu_mux_internal *cm,
+			      u8 index);
+
+#endif /* _CCU_MUX_H_ */
diff --git a/drivers/clk/sunxi/ccu_nk.c b/drivers/clk/sunxi/ccu_nk.c
new file mode 100644
index 0000000..d841dd2
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nk.c
@@ -0,0 +1,154 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nk.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+#include "ccu_nk.h"
+
+struct _ccu_nk {
+	unsigned long	n, min_n, max_n;
+	unsigned long	k, min_k, max_k;
+};
+
+static void ccu_nk_find_best(unsigned long parent, unsigned long rate,
+			     struct _ccu_nk *nk)
+{
+	unsigned long best_rate = 0;
+	unsigned int best_k = 0, best_n = 0;
+	unsigned int _k, _n;
+
+	for (_k = nk->min_k; _k <= nk->max_k; _k++) {
+		for (_n = nk->min_n; _n <= nk->max_n; _n++) {
+			unsigned long tmp_rate = parent * _n * _k;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if ((rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_k = _k;
+				best_n = _n;
+			}
+		}
+	}
+
+	nk->k = best_k;
+	nk->n = best_n;
+}
+
+static void ccu_nk_disable(struct clk_hw *hw)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+
+	return ccu_gate_helper_disable(&nk->common, nk->enable);
+}
+
+static int ccu_nk_enable(struct clk_hw *hw)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+
+	return ccu_gate_helper_enable(&nk->common, nk->enable);
+}
+
+static int ccu_nk_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+
+	return ccu_gate_helper_is_enabled(&nk->common, nk->enable);
+}
+
+static unsigned long ccu_nk_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+	unsigned long rate, n, k;
+	u32 reg;
+
+	reg = readl(nk->common.base + nk->common.reg);
+
+	n = reg >> nk->n.shift;
+	n &= (1 << nk->n.width) - 1;
+
+	k = reg >> nk->k.shift;
+	k &= (1 << nk->k.width) - 1;
+
+	rate = parent_rate * (n + 1) * (k + 1);
+
+	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
+		rate /= nk->fixed_post_div;
+
+	return rate;
+}
+
+static long ccu_nk_round_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long *parent_rate)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+	struct _ccu_nk _nk;
+
+	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
+		rate *= nk->fixed_post_div;
+
+	_nk.min_n = nk->n.min;
+	_nk.max_n = 1 << nk->n.width;
+	_nk.min_k = nk->k.min;
+	_nk.max_k = 1 << nk->k.width;
+
+	ccu_nk_find_best(*parent_rate, rate, &_nk);
+	rate = *parent_rate * _nk.n * _nk.k;
+
+	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
+		rate = rate / nk->fixed_post_div;
+
+	return rate;
+}
+
+static int ccu_nk_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_nk *nk = hw_to_ccu_nk(hw);
+	unsigned long flags;
+	struct _ccu_nk _nk;
+	u32 reg;
+
+	if (nk->common.features & CCU_FEATURE_FIXED_POSTDIV)
+		rate = rate * nk->fixed_post_div;
+
+	_nk.min_n = nk->n.min;
+	_nk.max_n = 1 << nk->n.width;
+	_nk.min_k = nk->k.min;
+	_nk.max_k = 1 << nk->k.width;
+
+	ccu_nk_find_best(parent_rate, rate, &_nk);
+
+	spin_lock_irqsave(nk->common.lock, flags);
+
+	reg = readl(nk->common.base + nk->common.reg);
+	reg &= ~GENMASK(nk->n.width + nk->n.shift - 1, nk->n.shift);
+	reg &= ~GENMASK(nk->k.width + nk->k.shift - 1, nk->k.shift);
+
+	writel(reg | ((_nk.k - 1) << nk->k.shift) | ((_nk.n - 1) << nk->n.shift),
+	       nk->common.base + nk->common.reg);
+
+	spin_unlock_irqrestore(nk->common.lock, flags);
+
+	ccu_helper_wait_for_lock(&nk->common, nk->lock);
+
+	return 0;
+}
+
+const struct sunxi_ccu_clk_ops ccu_nk_ops = {
+	.disable	= ccu_nk_disable,
+	.enable		= ccu_nk_enable,
+	.is_enabled	= ccu_nk_is_enabled,
+
+	.recalc_rate	= ccu_nk_recalc_rate,
+	.round_rate	= ccu_nk_round_rate,
+	.set_rate	= ccu_nk_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_nk.h b/drivers/clk/sunxi/ccu_nk.h
new file mode 100644
index 0000000..c0de93f
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nk.h
@@ -0,0 +1,64 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nk.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_NK_H_
+#define _CCU_NK_H_
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_mult.h"
+
+/*
+ * struct ccu_nk - Definition of an N-K clock
+ *
+ * Clocks based on the formula parent * N * K
+ */
+struct ccu_nk {
+	u16			reg;
+	u32			enable;
+	u32			lock;
+
+	struct ccu_mult_internal	n;
+	struct ccu_mult_internal	k;
+
+	unsigned int		fixed_post_div;
+
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_NK_WITH_GATE_LOCK_POSTDIV(_struct, _name, _parent, _reg, \
+					    _nshift, _nwidth,		\
+					    _kshift, _kwidth,		\
+					    _gate, _lock, _postdiv,	\
+					    _flags)			\
+	struct ccu_nk _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.k		= _SUNXI_CCU_MULT(_kshift, _kwidth),	\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.fixed_post_div	= _postdiv,				\
+		.common		= {					\
+			.reg		= _reg,				\
+			.features	= CCU_FEATURE_FIXED_POSTDIV,	\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_nk_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+static inline struct ccu_nk *hw_to_ccu_nk(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_nk, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_nk_ops;
+
+#endif /* _CCU_NK_H_ */
diff --git a/drivers/clk/sunxi/ccu_nkm.c b/drivers/clk/sunxi/ccu_nkm.c
new file mode 100644
index 0000000..03835a2
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nkm.c
@@ -0,0 +1,184 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nkm.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+#include "ccu_nkm.h"
+
+struct _ccu_nkm {
+	unsigned long	n, min_n, max_n;
+	unsigned long	k, min_k, max_k;
+	unsigned long	m, min_m, max_m;
+};
+
+static void ccu_nkm_find_best(unsigned long parent, unsigned long rate,
+			      struct _ccu_nkm *nkm)
+{
+	unsigned long best_rate = 0;
+	unsigned long best_n = 0, best_k = 0, best_m = 0;
+	unsigned long _n, _k, _m;
+
+	for (_k = nkm->min_k; _k <= nkm->max_k; _k++) {
+		for (_n = nkm->min_n; _n <= nkm->max_n; _n++) {
+			for (_m = nkm->min_m; _m <= nkm->max_m; _m++) {
+				unsigned long tmp_rate;
+
+				tmp_rate = parent * _n * _k / _m;
+
+				if (tmp_rate > rate)
+					continue;
+				if ((rate - tmp_rate) < (rate - best_rate)) {
+					best_rate = tmp_rate;
+					best_n = _n;
+					best_k = _k;
+					best_m = _m;
+				}
+			}
+		}
+	}
+
+	nkm->n = best_n;
+	nkm->k = best_k;
+	nkm->m = best_m;
+}
+
+static void ccu_nkm_disable(struct clk_hw *hw)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_gate_helper_disable(&nkm->common, nkm->enable);
+}
+
+static int ccu_nkm_enable(struct clk_hw *hw)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_gate_helper_enable(&nkm->common, nkm->enable);
+}
+
+static int ccu_nkm_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_gate_helper_is_enabled(&nkm->common, nkm->enable);
+}
+
+static unsigned long ccu_nkm_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+	unsigned long n, m, k;
+	u32 reg;
+
+	reg = readl(nkm->common.base + nkm->common.reg);
+
+	n = reg >> nkm->n.shift;
+	n &= (1 << nkm->n.width) - 1;
+
+	k = reg >> nkm->k.shift;
+	k &= (1 << nkm->k.width) - 1;
+
+	m = reg >> nkm->m.shift;
+	m &= (1 << nkm->m.width) - 1;
+
+	return parent_rate * (n + 1) * (k + 1) / (m + 1);
+}
+
+static unsigned long ccu_nkm_round_rate(struct ccu_mux_internal *mux,
+					unsigned long parent_rate,
+					unsigned long rate,
+					void *data)
+{
+	struct ccu_nkm *nkm = data;
+	struct _ccu_nkm _nkm;
+
+	_nkm.min_n = nkm->n.min;
+	_nkm.max_n = 1 << nkm->n.width;
+	_nkm.min_k = nkm->k.min;
+	_nkm.max_k = 1 << nkm->k.width;
+	_nkm.min_m = 1;
+	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
+
+	ccu_nkm_find_best(parent_rate, rate, &_nkm);
+
+	return parent_rate * _nkm.n * _nkm.k / _nkm.m;
+}
+
+static int ccu_nkm_determine_rate(struct clk_hw *hw,
+				  struct clk_rate_request *req)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_mux_helper_determine_rate(&nkm->common, &nkm->mux,
+					     req, ccu_nkm_round_rate, nkm);
+}
+
+static int ccu_nkm_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+	struct _ccu_nkm _nkm;
+	unsigned long flags;
+	u32 reg;
+
+	_nkm.min_n = nkm->n.min;
+	_nkm.max_n = 1 << nkm->n.width;
+	_nkm.min_k = nkm->k.min;
+	_nkm.max_k = 1 << nkm->k.width;
+	_nkm.min_m = 1;
+	_nkm.max_m = nkm->m.max ?: 1 << nkm->m.width;
+
+	ccu_nkm_find_best(parent_rate, rate, &_nkm);
+
+	spin_lock_irqsave(nkm->common.lock, flags);
+
+	reg = readl(nkm->common.base + nkm->common.reg);
+	reg &= ~GENMASK(nkm->n.width + nkm->n.shift - 1, nkm->n.shift);
+	reg &= ~GENMASK(nkm->k.width + nkm->k.shift - 1, nkm->k.shift);
+	reg &= ~GENMASK(nkm->m.width + nkm->m.shift - 1, nkm->m.shift);
+
+	reg |= (_nkm.n - 1) << nkm->n.shift;
+	reg |= (_nkm.k - 1) << nkm->k.shift;
+	reg |= (_nkm.m - 1) << nkm->m.shift;
+
+	writel(reg, nkm->common.base + nkm->common.reg);
+
+	spin_unlock_irqrestore(nkm->common.lock, flags);
+
+	ccu_helper_wait_for_lock(&nkm->common, nkm->lock);
+
+	return 0;
+}
+
+static u8 ccu_nkm_get_parent(struct clk_hw *hw)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_mux_helper_get_parent(&nkm->common, &nkm->mux);
+}
+
+static int ccu_nkm_set_parent(struct clk_hw *hw, u8 index)
+{
+	struct ccu_nkm *nkm = hw_to_ccu_nkm(hw);
+
+	return ccu_mux_helper_set_parent(&nkm->common, &nkm->mux, index);
+}
+
+const struct sunxi_ccu_clk_ops ccu_nkm_ops = {
+	.disable	= ccu_nkm_disable,
+	.enable		= ccu_nkm_enable,
+	.is_enabled	= ccu_nkm_is_enabled,
+
+	.get_parent	= ccu_nkm_get_parent,
+	.set_parent	= ccu_nkm_set_parent,
+
+	.determine_rate	= ccu_nkm_determine_rate,
+	.recalc_rate	= ccu_nkm_recalc_rate,
+	.set_rate	= ccu_nkm_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_nkm.h b/drivers/clk/sunxi/ccu_nkm.h
new file mode 100644
index 0000000..403fb3e
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nkm.h
@@ -0,0 +1,84 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nkm.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_NKM_H_
+#define _CCU_NKM_H_
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_mult.h"
+
+/*
+ * struct ccu_nkm - Definition of an N-K-M clock
+ *
+ * Clocks based on the formula parent * N * K / M
+ */
+struct ccu_nkm {
+	u32			enable;
+	u32			lock;
+
+	struct ccu_mult_internal	n;
+	struct ccu_mult_internal	k;
+	struct ccu_div_internal		m;
+	struct ccu_mux_internal	mux;
+
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_NKM_WITH_MUX_GATE_LOCK(_struct, _name, _parents, _reg, \
+					 _nshift, _nwidth,		\
+					 _kshift, _kwidth,		\
+					 _mshift, _mwidth,		\
+					 _muxshift, _muxwidth,		\
+					 _gate, _lock, _flags)		\
+	struct ccu_nkm _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.k		= _SUNXI_CCU_MULT(_kshift, _kwidth),	\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.m		= _SUNXI_CCU_DIV(_mshift, _mwidth),	\
+		.mux		= _SUNXI_CCU_MUX(_muxshift, _muxwidth),	\
+		.common		= {					\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT_PARENTS(_name,	\
+						      _parents,		\
+						      &ccu_nkm_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+#define SUNXI_CCU_NKM_WITH_GATE_LOCK(_struct, _name, _parent, _reg,	\
+				     _nshift, _nwidth,			\
+				     _kshift, _kwidth,			\
+				     _mshift, _mwidth,			\
+				     _gate, _lock, _flags)		\
+	struct ccu_nkm _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.k		= _SUNXI_CCU_MULT(_kshift, _kwidth),	\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.m		= _SUNXI_CCU_DIV(_mshift, _mwidth),	\
+		.common		= {					\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_nkm_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+static inline struct ccu_nkm *hw_to_ccu_nkm(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_nkm, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_nkm_ops;
+
+#endif /* _CCU_NKM_H_ */
diff --git a/drivers/clk/sunxi/ccu_nkmp.c b/drivers/clk/sunxi/ccu_nkmp.c
new file mode 100644
index 0000000..1ea3058
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nkmp.c
@@ -0,0 +1,171 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nkmp.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_gate.h"
+#include "ccu_nkmp.h"
+
+struct _ccu_nkmp {
+	unsigned long	n, min_n, max_n;
+	unsigned long	k, min_k, max_k;
+	unsigned long	m, min_m, max_m;
+	unsigned long	p, min_p, max_p;
+};
+
+static void ccu_nkmp_find_best(unsigned long parent, unsigned long rate,
+			       struct _ccu_nkmp *nkmp)
+{
+	unsigned long best_rate = 0;
+	unsigned long best_n = 0, best_k = 0, best_m = 0, best_p = 0;
+	unsigned long _n, _k, _m, _p;
+
+	for (_k = nkmp->min_k; _k <= nkmp->max_k; _k++) {
+		for (_n = nkmp->min_n; _n <= nkmp->max_n; _n++) {
+			for (_m = nkmp->min_m; _m <= nkmp->max_m; _m++) {
+				for (_p = nkmp->min_p; _p <= nkmp->max_p; _p <<= 1) {
+					unsigned long tmp_rate;
+
+					tmp_rate = parent * _n * _k / (_m * _p);
+
+					if (tmp_rate > rate)
+						continue;
+
+					if ((rate - tmp_rate) < (rate - best_rate)) {
+						best_rate = tmp_rate;
+						best_n = _n;
+						best_k = _k;
+						best_m = _m;
+						best_p = _p;
+					}
+				}
+			}
+		}
+	}
+
+	nkmp->n = best_n;
+	nkmp->k = best_k;
+	nkmp->m = best_m;
+	nkmp->p = best_p;
+}
+
+static void ccu_nkmp_disable(struct clk_hw *hw)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+
+	return ccu_gate_helper_disable(&nkmp->common, nkmp->enable);
+}
+
+static int ccu_nkmp_enable(struct clk_hw *hw)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+
+	return ccu_gate_helper_enable(&nkmp->common, nkmp->enable);
+}
+
+static int ccu_nkmp_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+
+	return ccu_gate_helper_is_enabled(&nkmp->common, nkmp->enable);
+}
+
+static unsigned long ccu_nkmp_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+	unsigned long n, m, k, p;
+	u32 reg;
+
+	reg = readl(nkmp->common.base + nkmp->common.reg);
+
+	n = reg >> nkmp->n.shift;
+	n &= (1 << nkmp->n.width) - 1;
+
+	k = reg >> nkmp->k.shift;
+	k &= (1 << nkmp->k.width) - 1;
+
+	m = reg >> nkmp->m.shift;
+	m &= (1 << nkmp->m.width) - 1;
+
+	p = reg >> nkmp->p.shift;
+	p &= (1 << nkmp->p.width) - 1;
+
+	return (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1);
+}
+
+static long ccu_nkmp_round_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long *parent_rate)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+	struct _ccu_nkmp _nkmp;
+
+	_nkmp.min_n = nkmp->n.min;
+	_nkmp.max_n = 1 << nkmp->n.width;
+	_nkmp.min_k = nkmp->k.min;
+	_nkmp.max_k = 1 << nkmp->k.width;
+	_nkmp.min_m = 1;
+	_nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width;
+	_nkmp.min_p = 1;
+	_nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1);
+
+	ccu_nkmp_find_best(*parent_rate, rate, &_nkmp);
+
+	return *parent_rate * _nkmp.n * _nkmp.k / (_nkmp.m * _nkmp.p);
+}
+
+static int ccu_nkmp_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_nkmp *nkmp = hw_to_ccu_nkmp(hw);
+	struct _ccu_nkmp _nkmp;
+	unsigned long flags = 0;
+	u32 reg;
+
+	_nkmp.min_n = 1;
+	_nkmp.max_n = 1 << nkmp->n.width;
+	_nkmp.min_k = 1;
+	_nkmp.max_k = 1 << nkmp->k.width;
+	_nkmp.min_m = 1;
+	_nkmp.max_m = nkmp->m.max ?: 1 << nkmp->m.width;
+	_nkmp.min_p = 1;
+	_nkmp.max_p = nkmp->p.max ?: 1 << ((1 << nkmp->p.width) - 1);
+
+	ccu_nkmp_find_best(parent_rate, rate, &_nkmp);
+
+	spin_lock_irqsave(nkmp->common.lock, flags);
+
+	reg = readl(nkmp->common.base + nkmp->common.reg);
+	reg &= ~GENMASK(nkmp->n.width + nkmp->n.shift - 1, nkmp->n.shift);
+	reg &= ~GENMASK(nkmp->k.width + nkmp->k.shift - 1, nkmp->k.shift);
+	reg &= ~GENMASK(nkmp->m.width + nkmp->m.shift - 1, nkmp->m.shift);
+	reg &= ~GENMASK(nkmp->p.width + nkmp->p.shift - 1, nkmp->p.shift);
+
+	reg |= (_nkmp.n - 1) << nkmp->n.shift;
+	reg |= (_nkmp.k - 1) << nkmp->k.shift;
+	reg |= (_nkmp.m - 1) << nkmp->m.shift;
+	reg |= ilog2(_nkmp.p) << nkmp->p.shift;
+
+	writel(reg, nkmp->common.base + nkmp->common.reg);
+
+	spin_unlock_irqrestore(nkmp->common.lock, flags);
+
+	ccu_helper_wait_for_lock(&nkmp->common, nkmp->lock);
+
+	return 0;
+}
+
+const struct sunxi_ccu_clk_ops ccu_nkmp_ops = {
+	.disable	= ccu_nkmp_disable,
+	.enable		= ccu_nkmp_enable,
+	.is_enabled	= ccu_nkmp_is_enabled,
+
+	.recalc_rate	= ccu_nkmp_recalc_rate,
+	.round_rate	= ccu_nkmp_round_rate,
+	.set_rate	= ccu_nkmp_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_nkmp.h b/drivers/clk/sunxi/ccu_nkmp.h
new file mode 100644
index 0000000..701e4f5
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nkmp.h
@@ -0,0 +1,64 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nkmp.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_NKMP_H_
+#define _CCU_NKMP_H_
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_mult.h"
+
+/*
+ * struct ccu_nkmp - Definition of an N-K-M-P clock
+ *
+ * Clocks based on the formula parent * N * K >> P / M
+ */
+struct ccu_nkmp {
+	u32			enable;
+	u32			lock;
+
+	struct ccu_mult_internal	n;
+	struct ccu_mult_internal	k;
+	struct ccu_div_internal		m;
+	struct ccu_div_internal		p;
+
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_NKMP_WITH_GATE_LOCK(_struct, _name, _parent, _reg,	\
+				      _nshift, _nwidth,			\
+				      _kshift, _kwidth,			\
+				      _mshift, _mwidth,			\
+				      _pshift, _pwidth,			\
+				      _gate, _lock, _flags)		\
+	struct ccu_nkmp _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.k		= _SUNXI_CCU_MULT(_kshift, _kwidth),	\
+		.m		= _SUNXI_CCU_DIV(_mshift, _mwidth),	\
+		.p		= _SUNXI_CCU_DIV(_pshift, _pwidth),	\
+		.common		= {					\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_nkmp_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+static inline struct ccu_nkmp *hw_to_ccu_nkmp(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_nkmp, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_nkmp_ops;
+
+#endif /* _CCU_NKMP_H_ */
diff --git a/drivers/clk/sunxi/ccu_nm.c b/drivers/clk/sunxi/ccu_nm.c
new file mode 100644
index 0000000..0293f56
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nm.c
@@ -0,0 +1,148 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_mp.c in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+
+#include "ccu_frac.h"
+#include "ccu_gate.h"
+#include "ccu_nm.h"
+
+struct _ccu_nm {
+	unsigned long	n, min_n, max_n;
+	unsigned long	m, min_m, max_m;
+};
+
+static void ccu_nm_find_best(unsigned long parent, unsigned long rate,
+			     struct _ccu_nm *nm)
+{
+	unsigned long best_rate = 0;
+	unsigned long best_n = 0, best_m = 0;
+	unsigned long _n, _m;
+
+	for (_n = nm->min_n; _n <= nm->max_n; _n++) {
+		for (_m = nm->min_m; _m <= nm->max_m; _m++) {
+			unsigned long tmp_rate = parent * _n  / _m;
+
+			if (tmp_rate > rate)
+				continue;
+
+			if ((rate - tmp_rate) < (rate - best_rate)) {
+				best_rate = tmp_rate;
+				best_n = _n;
+				best_m = _m;
+			}
+		}
+	}
+
+	nm->n = best_n;
+	nm->m = best_m;
+}
+
+static void ccu_nm_disable(struct clk_hw *hw)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+
+	return ccu_gate_helper_disable(&nm->common, nm->enable);
+}
+
+static int ccu_nm_enable(struct clk_hw *hw)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+
+	return ccu_gate_helper_enable(&nm->common, nm->enable);
+}
+
+static int ccu_nm_is_enabled(struct clk_hw *hw)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+
+	return ccu_gate_helper_is_enabled(&nm->common, nm->enable);
+}
+
+static unsigned long ccu_nm_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+	unsigned long n, m;
+	u32 reg;
+
+	if (ccu_frac_helper_is_enabled(&nm->common, &nm->frac))
+		return ccu_frac_helper_read_rate(&nm->common, &nm->frac);
+
+	reg = readl(nm->common.base + nm->common.reg);
+
+	n = reg >> nm->n.shift;
+	n &= (1 << nm->n.width) - 1;
+
+	m = reg >> nm->m.shift;
+	m &= (1 << nm->m.width) - 1;
+
+	return parent_rate * (n + 1) / (m + 1);
+}
+
+static long ccu_nm_round_rate(struct clk_hw *hw, unsigned long rate,
+			      unsigned long *parent_rate)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+	struct _ccu_nm _nm;
+
+	_nm.min_n = nm->n.min;
+	_nm.max_n = 1 << nm->n.width;
+	_nm.min_m = 1;
+	_nm.max_m = nm->m.max ?: 1 << nm->m.width;
+
+	ccu_nm_find_best(*parent_rate, rate, &_nm);
+
+	return *parent_rate * _nm.n / _nm.m;
+}
+
+static int ccu_nm_set_rate(struct clk_hw *hw, unsigned long rate,
+			   unsigned long parent_rate)
+{
+	struct ccu_nm *nm = hw_to_ccu_nm(hw);
+	struct _ccu_nm _nm;
+	unsigned long flags;
+	u32 reg;
+
+	if (ccu_frac_helper_has_rate(&nm->common, &nm->frac, rate))
+		return ccu_frac_helper_set_rate(&nm->common, &nm->frac, rate);
+	else
+		ccu_frac_helper_disable(&nm->common, &nm->frac);
+
+	_nm.min_n = 1;
+	_nm.max_n = 1 << nm->n.width;
+	_nm.min_m = 1;
+	_nm.max_m = nm->m.max ?: 1 << nm->m.width;
+
+	ccu_nm_find_best(parent_rate, rate, &_nm);
+
+	spin_lock_irqsave(nm->common.lock, flags);
+
+	reg = readl(nm->common.base + nm->common.reg);
+	reg &= ~GENMASK(nm->n.width + nm->n.shift - 1, nm->n.shift);
+	reg &= ~GENMASK(nm->m.width + nm->m.shift - 1, nm->m.shift);
+
+	writel(reg | ((_nm.m - 1) << nm->m.shift) | ((_nm.n - 1) << nm->n.shift),
+	       nm->common.base + nm->common.reg);
+
+	spin_unlock_irqrestore(nm->common.lock, flags);
+
+	ccu_helper_wait_for_lock(&nm->common, nm->lock);
+
+	return 0;
+}
+
+const struct sunxi_ccu_clk_ops ccu_nm_ops = {
+	.disable	= ccu_nm_disable,
+	.enable		= ccu_nm_enable,
+	.is_enabled	= ccu_nm_is_enabled,
+
+	.recalc_rate	= ccu_nm_recalc_rate,
+	.round_rate	= ccu_nm_round_rate,
+	.set_rate	= ccu_nm_set_rate,
+};
diff --git a/drivers/clk/sunxi/ccu_nm.h b/drivers/clk/sunxi/ccu_nm.h
new file mode 100644
index 0000000..b9187e0
--- /dev/null
+++ b/drivers/clk/sunxi/ccu_nm.h
@@ -0,0 +1,84 @@
+/*
+ * Derived from drivers/clk/sunxi-ng/ccu_nm.h in Linux, which is
+ *   Copyright (C) 2016 Maxime Ripard
+ *   Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#ifndef _CCU_NM_H_
+#define _CCU_NM_H_
+
+#include "ccu_common.h"
+#include "ccu_div.h"
+#include "ccu_frac.h"
+#include "ccu_mult.h"
+
+/*
+ * struct ccu_nm - Definition of an N-M clock
+ *
+ * Clocks based on the formula parent * N / M
+ */
+struct ccu_nm {
+	u32			enable;
+	u32			lock;
+
+	struct ccu_mult_internal	n;
+	struct ccu_div_internal		m;
+	struct ccu_frac_internal	frac;
+
+	struct ccu_common	common;
+};
+
+#define SUNXI_CCU_NM_WITH_FRAC_GATE_LOCK(_struct, _name, _parent, _reg,	\
+					 _nshift, _nwidth,		\
+					 _mshift, _mwidth,		\
+					 _frac_en, _frac_sel,		\
+					 _frac_rate_0, _frac_rate_1,	\
+					 _gate, _lock, _flags)		\
+	struct ccu_nm _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.m		= _SUNXI_CCU_DIV(_mshift, _mwidth),	\
+		.frac		= _SUNXI_CCU_FRAC(_frac_en, _frac_sel,	\
+						  _frac_rate_0,		\
+						  _frac_rate_1),	\
+		.common		= {					\
+			.reg		= _reg,				\
+			.features	= CCU_FEATURE_FRACTIONAL,	\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_nm_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+#define SUNXI_CCU_NM_WITH_GATE_LOCK(_struct, _name, _parent, _reg,	\
+				    _nshift, _nwidth,			\
+				    _mshift, _mwidth,			\
+				    _gate, _lock, _flags)		\
+	struct ccu_nm _struct = {					\
+		.enable		= _gate,				\
+		.lock		= _lock,				\
+		.n		= _SUNXI_CCU_MULT(_nshift, _nwidth),	\
+		.m		= _SUNXI_CCU_DIV(_mshift, _mwidth),	\
+		.common		= {					\
+			.reg		= _reg,				\
+			.hw.init	= CLK_HW_INIT(_name,		\
+						      _parent,		\
+						      &ccu_nm_ops,	\
+						      _flags),		\
+		},							\
+	}
+
+static inline struct ccu_nm *hw_to_ccu_nm(struct clk_hw *hw)
+{
+	struct ccu_common *common = hw_to_ccu_common(hw);
+
+	return container_of(common, struct ccu_nm, common);
+}
+
+extern const struct sunxi_ccu_clk_ops ccu_nm_ops;
+
+#endif /* _CCU_NM_H_ */
diff --git a/drivers/clk/sunxi/clk-sunxi-ccu.c b/drivers/clk/sunxi/clk-sunxi-ccu.c
new file mode 100644
index 0000000..8a26dd4
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-ccu.c
@@ -0,0 +1,550 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+
+#include "ccu_common.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_priv {
+	uint32_t *base;
+	size_t  size;
+	struct clk hosc_clk;
+	struct clk losc_clk;
+
+	struct clk_hw hosc, losc;
+};
+
+static ulong ccu_parent_recalc_rate(struct clk_hw *hw, ulong parent_rate)
+{
+	struct clk clk;
+
+	if (clk_get_by_output_name(clk_hw_get_name(hw), &clk) < 0) {
+		error("%s: unknown clk %s\n", __func__, clk_hw_get_name(hw));
+		return -ENOENT;
+	}
+
+	return clk_get_rate(&clk);
+}
+
+static struct sunxi_ccu_clk_ops parent_clk_ops = {
+	.recalc_rate = ccu_parent_recalc_rate,
+};
+
+static struct clk_init_data hosc_init_data = {
+	.name = "osc24M",
+	.ops = &parent_clk_ops,
+};
+
+static struct clk_init_data losc_init_data = {
+	.name = "osc32k",
+	.ops = &parent_clk_ops,
+};
+
+static inline
+const struct sunxi_ccu_clk_ops *clk_hw_get_ops(const struct clk_hw *hw_clk)
+{
+	return hw_clk->init->ops;
+}
+
+static inline
+const char *clk_hw_get_parent_name_by_index(const struct clk_hw *hw_clk,
+					    unsigned int index)
+{
+	return hw_clk->init->parent_names[index];
+}
+
+struct clk_hw *clk_hw_get_parent_by_index(const struct clk_hw *hw,
+					  unsigned int index)
+{
+	if (!hw->parents)
+		return NULL;
+
+	return hw->parents[index];
+}
+
+struct clk_hw *clk_hw_get_parent(const struct clk_hw *hw)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	u8 idx = 0;
+
+	if (ops->get_parent)
+		idx = ops->get_parent((struct clk_hw *)hw);
+
+	return clk_hw_get_parent_by_index(hw, idx);
+}
+
+static int clk_hw_get_index_by_parent(struct clk_hw *hw,
+				      struct clk_hw *parent)
+{
+	int i;
+
+	for (i = 0; i < clk_hw_get_num_parents(hw); ++i)
+		if (!strcmp(clk_hw_get_parent_name_by_index(hw, i),
+			    clk_hw_get_name(parent)))
+			return i;
+
+	return -ENOENT;
+}
+
+const char *clk_hw_get_name(const struct clk_hw *hw_clk)
+{
+	return hw_clk->init->name;
+}
+
+unsigned int clk_hw_get_num_parents(const struct clk_hw *hw_clk)
+{
+	return hw_clk->init->num_parents;
+}
+
+unsigned long clk_hw_get_flags(const struct clk_hw *hw_clk)
+{
+	return hw_clk->init->flags;
+}
+
+bool clk_hw_is_enabled(const struct clk_hw *hw)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	struct clk_hw *parent = clk_hw_get_parent(hw);
+
+	if (parent)
+		if (!clk_hw_is_enabled(parent))
+			return false;
+
+	if (ops->is_enabled)
+		return ops->is_enabled((struct clk_hw *)hw);
+
+	return false;
+}
+
+static int clk_hw_enable(struct clk_hw *hw)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	struct clk_hw *parent = clk_hw_get_parent(hw);
+
+	debug("%s: clk name %s\n", __func__, clk_hw_get_name(hw));
+
+	/*
+	 * If this clock is already enabled, we assume that its parent
+	 * clocks are also enabled.
+	 */
+	if (ops->is_enabled && ops->is_enabled(hw))
+		return 0;
+
+	/* Walk the tree and enable the parents */
+	if (parent)
+		clk_hw_enable(parent);
+
+	if (ops->enable)
+		ops->enable(hw);
+
+	return 0;
+}
+
+static int clk_hw_disable(struct clk_hw *hw_clk)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw_clk);
+
+	debug("%s: clk name %s\n", __func__, clk_hw_get_name(hw_clk));
+
+	/* Don't try to disable it, if this clock is not enabled. */
+	if (ops->is_enabled && !ops->is_enabled(hw_clk))
+		return 0;
+
+	if (ops->disable)
+		ops->disable(hw_clk);
+
+	return 0;
+}
+
+static int clk_hw_determine_rate(struct clk_hw *hw,
+				 struct clk_rate_request *req)
+{
+	struct clk_hw *parent;
+	const struct sunxi_ccu_clk_ops *ops;
+
+	if (!hw)
+		return 0;
+
+	parent = clk_hw_get_parent(hw);
+
+	req->best_parent_hw = NULL;
+	req->best_parent_rate = 0;
+	if (parent) {
+		req->best_parent_hw = parent;
+		req->best_parent_rate = clk_hw_get_rate(parent);
+	}
+
+	ops = clk_hw_get_ops(hw);
+	if (ops->determine_rate) {
+		return ops->determine_rate(hw, req);
+	} else if (ops->round_rate) {
+		long rate = ops->round_rate(hw, req->rate,
+					    &req->best_parent_rate);
+
+		if (rate < 0)
+			return rate;
+
+		req->rate = rate;
+	} else if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+		return clk_hw_determine_rate(parent, req);
+	} else {
+		req->rate = clk_hw_get_rate(hw);
+	}
+
+	return 0;
+}
+
+int __clk_mux_determine_rate(struct clk_hw *hw,
+			     struct clk_rate_request *req)
+{
+	struct clk_hw *parent, *best_parent = NULL;
+	int i, num_parents, ret;
+	unsigned long best = 0;
+	struct clk_rate_request parent_req = *req;
+
+	/* if NO_REPARENT flag set, pass through to current parent */
+	if (clk_hw_get_flags(hw) & CLK_SET_RATE_NO_REPARENT) {
+		parent = clk_hw_get_parent(hw);
+
+		if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+			ret = clk_hw_determine_rate(parent, &parent_req);
+			if (ret)
+				return ret;
+
+			best = parent_req.rate;
+		} else if (parent) {
+			best = clk_hw_get_rate(parent);
+		} else {
+			best = clk_hw_get_rate(hw);
+		}
+
+		goto out;
+	}
+
+	/* find the parent that can provide the fastest rate <= rate */
+	num_parents = clk_hw_get_num_parents(hw);
+	for (i = 0; i < num_parents; i++) {
+		parent = clk_hw_get_parent_by_index(hw, i);
+		if (!parent)
+			continue;
+
+		if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) {
+			parent_req = *req;
+			ret = clk_hw_determine_rate(parent, &parent_req);
+			if (ret)
+				continue;
+		} else {
+			parent_req.rate = clk_hw_get_rate(parent);
+		}
+
+		if (parent_req.rate <= req->rate && parent_req.rate > best) {
+			best_parent = parent;
+			best = parent_req.rate;
+		}
+	}
+
+	if (!best_parent)
+		return -EINVAL;
+
+out:
+	if (best_parent)
+		req->best_parent_hw = best_parent;
+	req->best_parent_rate = best;
+	req->rate = best;
+
+	return 0;
+}
+
+void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *parent)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	int parent_idx = clk_hw_get_index_by_parent(hw, parent);
+
+	if (!ops->set_parent)
+		return;
+
+	ops->set_parent(hw, parent_idx);
+}
+
+unsigned long clk_hw_get_rate(const struct clk_hw *hw)
+{
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	struct clk_hw *parent = clk_hw_get_parent(hw);
+	ulong parent_rate = 0;
+	ulong rate = 0;
+
+	debug("%s(%s)\n", __func__, clk_hw_get_name(hw));
+
+	if (parent)
+		parent_rate = clk_hw_get_rate(parent);
+
+	rate = parent_rate;
+	if (ops->recalc_rate)
+		rate = ops->recalc_rate((struct clk_hw *)hw, parent_rate);
+
+	return rate;
+}
+
+static struct clk_hw *ccu_parent_by_name(struct udevice *dev, const char *name)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(dev);
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	int i;
+
+	debug("%s(%s)\n", __func__, name);
+
+	for (i = 0; i < desc->hw_clks->num ; i++) {
+		struct clk_hw *hw = desc->hw_clks->hws[i];
+
+		if (!hw)
+			continue;
+
+		if (!strcmp(name, clk_hw_get_name(hw)))
+			return hw;
+	}
+
+	if (!strcmp(name, clk_hw_get_name(&priv->hosc)))
+		return &priv->hosc;
+
+	if (!strcmp(name, clk_hw_get_name(&priv->losc)))
+		return &priv->losc;
+
+	debug("%s: - not found => returning NULL\n", __func__);
+	return NULL;
+}
+
+static ulong sunxi_clk_ccu_set_rate(struct clk *clk, ulong rate)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev);
+	struct clk_hw *hw = desc->hw_clks->hws[clk->id];
+	const struct sunxi_ccu_clk_ops *ops = clk_hw_get_ops(hw);
+	struct clk_rate_request req = { .rate = rate };
+
+	debug("%s (%s): id %ld rate %ld\n",
+	      clk->dev->name, __func__, clk->id, rate);
+
+	if (!hw)
+		return -ENOENT;
+
+	/* bail early, if nothing to do */
+	if (rate == clk_hw_get_rate(hw))
+		return rate;
+
+	/* enable the clock implicitly if CLK_SET_RATE_UNGATE is set */
+	if (clk_hw_get_flags(hw) & CLK_SET_RATE_UNGATE)
+		clk_hw_enable(hw);
+
+	/* Find the best clock rate (and possibly parent) */
+	if (clk_hw_determine_rate(hw, &req) < 0)
+		return -EINVAL;
+
+	/* Change the parent, if necessary */
+	if (req.best_parent_hw &&
+	    req.best_parent_hw != clk_hw_get_parent(hw))
+		clk_hw_reparent(hw, req.best_parent_hw);
+
+	/* Now set the new rate */
+	if (ops->set_rate)
+		ops->set_rate(hw, req.rate, req.best_parent_rate);
+	else
+		return -ENOSYS;
+
+	/* Finally recalculate the rate and return it */
+	return clk_hw_get_rate(hw);
+}
+
+static ulong sunxi_clk_ccu_get_rate(struct clk *clk)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev);
+	struct clk_hw *hw_clk = desc->hw_clks->hws[clk->id];
+	ulong rate;
+
+	if (!hw_clk)
+		return -ENOENT;
+
+	rate = clk_hw_get_rate(hw_clk);
+
+	debug("%s(%s): hw_clk name %s rate %ld\n",
+	      clk->dev->name, __func__, clk_hw_get_name(hw_clk), rate);
+
+	return rate;
+}
+
+static int sunxi_clk_ccu_enable(struct clk *clk)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev);
+	struct clk_hw *hw = desc->hw_clks->hws[clk->id];
+
+	return clk_hw_enable(hw);
+}
+
+static int sunxi_clk_ccu_disable(struct clk *clk)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(clk->dev);
+	struct clk_hw *hw = desc->hw_clks->hws[clk->id];
+
+	return clk_hw_disable(hw);
+}
+
+static struct clk_ops sunxi_clk_ccu_ops = {
+	.enable = sunxi_clk_ccu_enable,
+	.disable = sunxi_clk_ccu_disable,
+	.set_rate = sunxi_clk_ccu_set_rate,
+	.get_rate = sunxi_clk_ccu_get_rate,
+};
+
+static int sunxi_clk_ccu_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+	int i;
+
+	debug("%s: %s\n", dev->name, __func__);
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: could not get addr\n", dev->name);
+		return -EINVAL;
+	}
+
+	priv->base = (void *)addr;
+	priv->size = size;
+
+	/*
+	 * Get any parent clocks defined, so searching their output
+	 * names can work later on...
+	 */
+	clk_get_by_name(dev, "hosc", &priv->hosc_clk);
+	clk_get_by_name(dev, "losc", &priv->losc_clk);
+
+	priv->hosc.dev = NULL;
+	priv->hosc.init = &hosc_init_data;
+	priv->losc.dev = NULL;
+	priv->losc.init = &losc_init_data;
+
+	/*
+	 * All clocks in the ccu_clks array of the descriptor have a
+	 * ccu_common structure; propagate our base address into these.
+	 */
+	for (i = 0; i < desc->num_ccu_clks; i++) {
+		struct ccu_common *cclk = desc->ccu_clks[i];
+
+		if (!cclk)
+			continue;
+
+		cclk->base = (void *)addr;
+	}
+
+	/*
+	 * We need to walk both lists, as they may not have the same
+	 * entries (i.e. only nodes that have a ccu_common are present
+	 * in the ccu_clks).
+	 */
+	for (i = 0; i < desc->hw_clks->num ; i++) {
+		struct clk_hw *hw = desc->hw_clks->hws[i];
+
+		if (!hw)
+			continue;
+
+		hw->dev = dev;
+		if (clk_hw_get_num_parents(hw))
+			hw->parents = calloc(hw->init->num_parents,
+					     sizeof(struct clk_hw *));
+
+		debug("%s: init clk %s\n", __func__, clk_hw_get_name(hw));
+
+		for (int j = 0; j < clk_hw_get_num_parents(hw); ++j) {
+			const char *parent_name = hw->init->parent_names[j];
+			hw->parents[j] = ccu_parent_by_name(dev, parent_name);
+		}
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_CMD_CLK)
+static int sunxi_clk_ccu_dump(struct udevice *dev)
+{
+	const struct sunxi_ccu_desc *desc =
+		(const struct sunxi_ccu_desc *)dev_get_driver_data(dev);
+	int i;
+
+	printf("%3s %20s %5s %12s %20s\n",
+	       "id", "clk", "on?", "freq", "selected parent");
+	for (i = 0; i < desc->hw_clks->num ; i++) {
+		struct clk_hw *hw = desc->hw_clks->hws[i];
+		struct clk_hw *parent;
+
+		if (!hw)
+			continue;
+
+		parent = clk_hw_get_parent(hw);
+		printf("%3d %20s %5s %12lu %20s\n", i,
+		       clk_hw_get_name(hw),
+		       clk_hw_is_enabled(hw) ? "YES" : "---",
+		       clk_hw_get_rate(hw),
+		       parent ? clk_hw_get_name(parent) : "");
+	}
+
+	return 0;
+}
+
+/**
+ * soc_clk_dump() - Print clock frequencies
+ * Returns zero on success
+ *
+ * Implementation for the clk dump command.
+ */
+int soc_clk_dump(void)
+{
+	struct udevice *dev;
+
+	for (uclass_first_device(UCLASS_CLK, &dev);
+	     dev;
+	     uclass_next_device(&dev)) {
+		/* if this is not this driver, skip */
+		if (strcmp(dev->driver->name, "sunxi_clk_ccu") != 0)
+			continue;
+
+		sunxi_clk_ccu_dump(dev);
+	}
+
+	return 0;
+}
+#endif
+
+#if defined(CONFIG_MACH_SUN50I)
+extern const struct sunxi_ccu_desc sun50i_a64_ccu_desc;
+#endif
+
+static const struct udevice_id sunxi_clk_ccu_ids[] = {
+#if defined(CONFIG_MACH_SUN50I)
+	{ .compatible = "allwinner,sun50i-a64-ccu",
+	  .data = (ulong)&sun50i_a64_ccu_desc },
+#endif
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_ccu) = {
+	.name		= "sunxi_clk_ccu",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_ccu_ids,
+	.ops		= &sunxi_clk_ccu_ops,
+	.probe		= sunxi_clk_ccu_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
diff --git a/drivers/clk/sunxi/clk-sunxi-gate.c b/drivers/clk/sunxi/clk-sunxi-gate.c
new file mode 100644
index 0000000..d6333cc
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-gate.c
@@ -0,0 +1,92 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <wait_bit.h>
+#include <dm/lists.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_priv {
+	uint32_t *base;
+	size_t  size;
+};
+
+static int sunxi_gate_update(struct clk *clk, bool enable)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	unsigned long id = clk->id;
+	uint32_t offset = id / 32;
+	uint32_t bit = id % 32;
+
+	debug("%s (%s): id %ld base %p offset %x bit %d enable %d\n",
+	      clk->dev->name, __func__, id, priv->base, offset,
+	      bit, enable);
+
+	if (enable)
+		setbits_le32(priv->base + offset, BIT(bit));
+	else
+		clrbits_le32(priv->base + offset, BIT(bit));
+
+	return -EINVAL;
+}
+
+static int sunxi_gate_enable(struct clk *clk)
+{
+	return sunxi_gate_update(clk, true);
+}
+
+static int sunxi_gate_disable(struct clk *clk)
+{
+	return sunxi_gate_update(clk, false);
+}
+
+static struct clk_ops sunxi_clk_gate_ops = {
+	.enable = sunxi_gate_enable,
+	.disable = sunxi_gate_disable,
+};
+
+static int sunxi_clk_gate_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+
+	debug("%s: %s\n", dev->name, __func__);
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: could not get addr\n", dev->name);
+		return -EINVAL;
+	}
+
+	priv->base = (void *)addr;
+	priv->size = size;
+
+	return 0;
+}
+
+static const struct udevice_id sunxi_clk_gate_ids[] = {
+	{ .compatible = "allwinner,sunxi-multi-bus-gates-clk" },
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_gate) = {
+	.name		= "sunxi_clk_gate",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_gate_ids,
+	.ops		= &sunxi_clk_gate_ops,
+	.probe		= sunxi_clk_gate_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
+
+
diff --git a/drivers/clk/sunxi/clk-sunxi-mod.c b/drivers/clk/sunxi/clk-sunxi-mod.c
new file mode 100644
index 0000000..4e70cc9
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi-mod.c
@@ -0,0 +1,241 @@
+/*
+ * (C) 2017 Theobroma Systems Design und Consulting GmbH
+ *
+ * With sun4i_a10_get_mod0_factors(...) adapted from
+ *    linux/drivers/clk/sunxi/clk-mod0.c
+ * which is
+ *    Copyright 2013 Emilio López
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ *
+ */
+
+#include <common.h>
+#include <clk-uclass.h>
+#include <dm.h>
+#include <div64.h>
+#include <wait_bit.h>
+#include <dm/lists.h>
+#include <asm/io.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct sunxi_clk_priv {
+	void         *reg;
+	int           num_parents;
+	struct clk    parent[4];
+};
+
+#define SRCSHIFT    (24)
+#define SRCMASK     (0x3 << SRCSHIFT)
+#define SRC(n)      (n << SRCSHIFT)
+#define PREDIVMASK  (0x3 << 16)
+#define PREDIV(n)   (n << 16)
+#define DIVMASK     (0xf << 0)
+#define DIV(n)      (n)
+
+static ulong sunxi_mod_get_rate(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	u32 active_parent;
+	ulong rate = -EINVAL;
+	u32 regval = readl(priv->reg);
+
+	/* if not enabled, return 0 */
+	if (regval & BIT(31))
+		return 0;
+
+	active_parent = (readl(priv->reg) >> 24) & 0x3;
+	if (active_parent < priv->num_parents)
+		rate = clk_get_rate(&priv->parent[active_parent]);
+
+	return rate;
+}
+
+/**
+ * sun4i_a10_get_mod0_factors()
+ *  - calculates m, n factors for MOD0-style clocks
+ *
+ * MOD0 rate is calculated as follows:
+ *    rate = (parent_rate >> p) / (m + 1);
+ */
+
+struct factors_request {
+	unsigned long rate;
+	unsigned long parent_rate;
+	u8 parent_index;
+	u8 n;
+	u8 k;
+	u8 m;
+	u8 p;
+};
+
+static void sun4i_a10_get_mod0_factors(struct factors_request *req)
+{
+	u8 div, calcm, calcp;
+
+	/* These clocks can only divide, so we will never be able to
+	 * achieve frequencies higher than the parent frequency */
+	if (req->rate >= req->parent_rate) {
+		req->rate = req->parent_rate;
+		req->m = 0;
+		req->p = 0;
+	}
+
+	div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+	if (div < 16)
+		calcp = 0;
+	else if (div / 2 < 16)
+		calcp = 1;
+	else if (div / 4 < 16)
+		calcp = 2;
+	else
+		calcp = 3;
+
+	calcm = DIV_ROUND_UP(div, 1 << calcp);
+	/* clamp calcm to 16, as that is the largest possible divider */
+	if (calcm > 16)
+		calcm = 16;
+
+	req->rate = (req->parent_rate >> calcp) / calcm;
+	req->m = calcm - 1;
+	req->p = calcp;
+}
+
+static ulong sunxi_mod_set_rate(struct clk *clk, ulong rate)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+	ulong best_rate = 0;
+	int i;
+
+	debug("%s (%s): id %ld rate %ld base %p\n",
+	      clk->dev->name, __func__, clk->id, rate, priv->reg);
+
+	/* check if the current rate is already the target rate */
+	if (sunxi_mod_get_rate(clk) == rate)
+		return rate;
+
+	/* find the parent (iterate through) which allows us to have:
+	 *     fastest rate <= rate
+	 */
+	for (i = 0; i < priv->num_parents; ++i) {
+		ulong parent_rate = clk_get_rate(&priv->parent[i]);
+		struct factors_request  req = {
+			.rate = rate,
+			.parent_rate = parent_rate,
+			.parent_index = i
+		};
+
+		debug("%s (%s): parent %d rate %ld\n",
+		      clk->dev->name, __func__, i, parent_rate);
+
+		if (parent_rate == -ENOSYS) {
+			debug("%s: parent %d does not support get_rate\n",
+			      clk->dev->name, i);
+			continue;
+		}
+
+		if (parent_rate == 0) {
+			debug("%s: parent %d seems disabled (rate == 0)\n",
+			      clk->dev->name, i);
+			continue;
+		}
+
+		/* We recalculate the dividers, even if the parent's
+		 * rate is less than the requested rate
+		 */
+		sun4i_a10_get_mod0_factors(&req);
+
+		if (req.rate > rate) {
+			debug("%s: rate %ld for parent %i exceeds rate\n",
+			      clk->dev->name, req.rate, i);
+			continue;
+		}
+
+		if (req.rate > best_rate) {
+			debug("%s: new best => parent %d P %d M %d rate %ld\n",
+			      clk->dev->name, i, req.p, req.m, req.rate);
+
+			clrsetbits_le32(priv->reg,
+					SRCMASK | PREDIVMASK | DIVMASK,
+					SRC(i) | PREDIV(req.p) | DIV(req.m));
+			best_rate = req.rate;
+
+			/* don't continue, if this is the requested rate */
+			if (best_rate == rate)
+				break;
+		}
+	}
+
+	return best_rate;
+}
+
+static int sunxi_mod_enable(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+
+	setbits_le32(priv->reg, BIT(31));
+	return 0;
+}
+
+static int sunxi_mod_disable(struct clk *clk)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(clk->dev);
+
+	clrbits_le32(priv->reg, BIT(31));
+	return 0;
+}
+
+static struct clk_ops sunxi_clk_mod_ops = {
+	.set_rate = sunxi_mod_set_rate,
+	.get_rate = sunxi_mod_get_rate,
+	.enable = sunxi_mod_enable,
+	.disable = sunxi_mod_disable,
+};
+
+static int sunxi_clk_mod_probe(struct udevice *dev)
+{
+	struct sunxi_clk_priv *priv = dev_get_priv(dev);
+	fdt_addr_t addr;
+	fdt_size_t size;
+	int i;
+
+	debug("%s: %s\n", dev->name, __func__);
+
+	addr = fdtdec_get_addr_size_auto_noparent(gd->fdt_blob, dev->of_offset,
+						  "reg", 0, &size, false);
+	if (addr == FDT_ADDR_T_NONE) {
+		debug("%s: could not get addr\n", dev->name);
+		return -EINVAL;
+	}
+
+	priv->reg = (void *)addr;
+
+	for (i = 0; i < 4; ++i) {
+		int ret = clk_get_by_index(dev, i, &priv->parent[i]);
+		if (ret != 0)
+			break;
+	};
+	priv->num_parents = i;
+
+	debug("%s: reg %p num-parents %d\n",
+	      dev->name, priv->reg, priv->num_parents);
+	return 0;
+}
+
+static const struct udevice_id sunxi_clk_mod_ids[] = {
+	{ .compatible = "allwinner,sun4i-a10-mod0-clk" },
+	{}
+};
+
+U_BOOT_DRIVER(sunxi_clk_mod) = {
+	.name		= "sunxi_clk_mod",
+	.id		= UCLASS_CLK,
+	.of_match	= sunxi_clk_mod_ids,
+	.ops		= &sunxi_clk_mod_ops,
+	.probe		= sunxi_clk_mod_probe,
+	.priv_auto_alloc_size = sizeof(struct sunxi_clk_priv),
+};
+
+
diff --git a/include/dt-bindings/clock/sun50i-a64-ccu.h b/include/dt-bindings/clock/sun50i-a64-ccu.h
new file mode 100644
index 0000000..370c0a0
--- /dev/null
+++ b/include/dt-bindings/clock/sun50i-a64-ccu.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
+ *
+ * This file is dual-licensed: you can use it either under the terms
+ * of the GPL or the X11 license, at your option. Note that this dual
+ * licensing only applies to this file, and not this project as a
+ * whole.
+ *
+ *  a) This file is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License as
+ *     published by the Free Software Foundation; either version 2 of the
+ *     License, or (at your option) any later version.
+ *
+ *     This file is distributed in the hope that it will be useful,
+ *     but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *     GNU General Public License for more details.
+ *
+ * Or, alternatively,
+ *
+ *  b) Permission is hereby granted, free of charge, to any person
+ *     obtaining a copy of this software and associated documentation
+ *     files (the "Software"), to deal in the Software without
+ *     restriction, including without limitation the rights to use,
+ *     copy, modify, merge, publish, distribute, sublicense, and/or
+ *     sell copies of the Software, and to permit persons to whom the
+ *     Software is furnished to do so, subject to the following
+ *     conditions:
+ *
+ *     The above copyright notice and this permission notice shall be
+ *     included in all copies or substantial portions of the Software.
+ *
+ *     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *     OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *     HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *     WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *     FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *     OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _DT_BINDINGS_CLK_SUN50I_A64_H_
+#define _DT_BINDINGS_CLK_SUN50I_A64_H_
+
+#define CLK_BUS_MIPI_DSI	28
+#define CLK_BUS_CE		29
+#define CLK_BUS_DMA		30
+#define CLK_BUS_MMC0		31
+#define CLK_BUS_MMC1		32
+#define CLK_BUS_MMC2		33
+#define CLK_BUS_NAND		34
+#define CLK_BUS_DRAM		35
+#define CLK_BUS_EMAC		36
+#define CLK_BUS_TS		37
+#define CLK_BUS_HSTIMER		38
+#define CLK_BUS_SPI0		39
+#define CLK_BUS_SPI1		40
+#define CLK_BUS_OTG		41
+#define CLK_BUS_EHCI0		42
+#define CLK_BUS_EHCI1		43
+#define CLK_BUS_OHCI0		44
+#define CLK_BUS_OHCI1		45
+#define CLK_BUS_VE		46
+#define CLK_BUS_TCON0		47
+#define CLK_BUS_TCON1		48
+#define CLK_BUS_DEINTERLACE	49
+#define CLK_BUS_CSI		50
+#define CLK_BUS_HDMI		51
+#define CLK_BUS_DE		52
+#define CLK_BUS_GPU		53
+#define CLK_BUS_MSGBOX		54
+#define CLK_BUS_SPINLOCK	55
+#define CLK_BUS_CODEC		56
+#define CLK_BUS_SPDIF		57
+#define CLK_BUS_PIO		58
+#define CLK_BUS_THS		59
+#define CLK_BUS_I2S0		60
+#define CLK_BUS_I2S1		61
+#define CLK_BUS_I2S2		62
+#define CLK_BUS_I2C0		63
+#define CLK_BUS_I2C1		64
+#define CLK_BUS_I2C2		65
+#define CLK_BUS_SCR		66
+#define CLK_BUS_UART0		67
+#define CLK_BUS_UART1		68
+#define CLK_BUS_UART2		69
+#define CLK_BUS_UART3		70
+#define CLK_BUS_UART4		71
+#define CLK_BUS_DBG		72
+#define CLK_THS			73
+#define CLK_NAND		74
+#define CLK_MMC0		75
+#define CLK_MMC1		76
+#define CLK_MMC2		77
+#define CLK_TS			78
+#define CLK_CE			79
+#define CLK_SPI0		80
+#define CLK_SPI1		81
+#define CLK_I2S0		82
+#define CLK_I2S1		83
+#define CLK_I2S2		84
+#define CLK_SPDIF		85
+#define CLK_USB_PHY0		86
+#define CLK_USB_PHY1		87
+#define CLK_USB_HSIC		88
+#define CLK_USB_HSIC_12M	89
+
+#define CLK_USB_OHCI0		91
+
+#define CLK_USB_OHCI1		93
+
+#define CLK_DRAM_VE		95
+#define CLK_DRAM_CSI		96
+#define CLK_DRAM_DEINTERLACE	97
+#define CLK_DRAM_TS		98
+#define CLK_DE			99
+#define CLK_TCON0		100
+#define CLK_TCON1		101
+#define CLK_DEINTERLACE		102
+#define CLK_CSI_MISC		103
+#define CLK_CSI_SCLK		104
+#define CLK_CSI_MCLK		105
+#define CLK_VE			106
+#define CLK_AC_DIG		107
+#define CLK_AC_DIG_4X		108
+#define CLK_AVS			109
+#define CLK_HDMI		110
+#define CLK_HDMI_DDC		111
+
+#define CLK_DSI_DPHY		113
+#define CLK_GPU			114
+
+#endif /* _DT_BINDINGS_CLK_SUN50I_H_ */
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 9/9] cmd: move CONFIG_CMD_CLK to Kconfig
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (7 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi Philipp Tomsich
@ 2017-03-01 21:20 ` Philipp Tomsich
  2017-03-06  2:08 ` [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL André Przywara
  9 siblings, 0 replies; 12+ messages in thread
From: Philipp Tomsich @ 2017-03-01 21:20 UTC (permalink / raw)
  To: u-boot

The CMD_CLK configuration allows the clk-command today (having only
the 'clk dump' action for printing the clock configuration using a
per-SoC weak function).

It is currently only selected by ZYNC and PIC32. To make it easier
to use this command for other platforms and to select it via defconfig,
we are moving it into Kconfig.

Given that this is a debug/maintainer command, the default was chosen
as 'n' and thus should cause no warning messages to board that still
enable it in their respective header files.

Signed-off-by: Philipp Tomsich <philipp.tomsich@theobroma-systems.com>
---
 cmd/Kconfig | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/cmd/Kconfig b/cmd/Kconfig
index ef53156..119a600 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -144,6 +144,16 @@ config CMD_CONSOLE
 	help
 	  Print console devices and information.
 
+config CMD_CLK
+        bool "clk"
+	default n
+	help
+	  Print information about clocks.
+
+	  This normally shows a list of clocks and their current
+	  configuration.  The implementation and details of the output
+	  are manufacturer specific.
+
 config CMD_CPU
 	bool "cpu"
 	help
-- 
1.9.1

^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL
  2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
                   ` (8 preceding siblings ...)
  2017-03-01 21:20 ` [U-Boot] [PATCH v3 9/9] cmd: move CONFIG_CMD_CLK to Kconfig Philipp Tomsich
@ 2017-03-06  2:08 ` André Przywara
  2017-03-06  9:43   ` Dr. Philipp Tomsich
  9 siblings, 1 reply; 12+ messages in thread
From: André Przywara @ 2017-03-06  2:08 UTC (permalink / raw)
  To: u-boot

On 01/03/17 21:19, Philipp Tomsich wrote:
> Hi everyone,
> 
> here's the the new version of CLK, RESET and PINCTRL drivers to
> configure sunxi from the device-tree.  This adds support for the
> upstream CCU node (for reset and pinctrl) and tries to address the
> various concerns people had.
> 
> Note that (to stay in sync with the Linux files), some of the
> patches may not fully adhere to the style (e.g. some of the code
> reused verbatim from Linux and in the config tables).
> 
> This has been tested with Ethernet, I2C, SPI and MMC on the A64-uQ7.
> See my separate patchsets for the conversion of these drivers over
> to support DM-based CLK, RESET and PINCTRL configuration.
> 
> Changes is v3:
>  * add support for the 'new-style' clock subsystem (CCU) by porting
>    from Linux and adding the necessary glue implementation

Gah, is this really necessary? Do we really need to pull in the whole of
the complex Linux clock driver?
In the end all that U-Boot needs to do is to program a few simple
clocks, a task which it easily did so far with just some register
writes. I don't think it's appropriate for U-Boot to get the whole
complex clock driver from Linux for just that purpose.
One indication of this being too much is that the mailing list seemed to
have blocked patch 8/9, probably because it's too big ;-)
I am especially scared when it comes to adding all of the other SoC's
clock drivers to the code base as well.

So can't we do a much simpler (because limited) implementation?
If a peripheral driver gets the clock index from the DT and asks the
clock driver to program (or just enable) clock "75", can't we just have
a simple function which redirects this clock number to our already
existing code?
Something like (rough sketch):
int sun50i_clk_set_freq(int nr, int freq)
{
	int idx;

	switch (nr) {
	case CLK_MMC0 ... CLK_MMC2:
		idx = nr - CLK_MMC0;
		return mmc_set_mod_clk(idx,
				       clkreg_addr[MMC_CLK + idx],
				       freq);
	}
}
... with mmc_set_mod_clk() being a slightly changed version of the
existing implementation?

I think that would seriously reduce the bloat and be a better fit for
U-Boot. And it might even allow the SPL to reuse this by just calling
into the dispatch function with hardcoded parameters.

Cheers,
Andre.

>    - add a CCU reset-driver (in addition to the legacy driver, which
>      will still be required for the R_* device nodes), which reuses
>      the reset-table from ccu-<SOC>.c
>    - add a clk-sunxi-ccu.c implementation based on Linux and reusing
>      both the low-level clock implementation (ccu_*.h) and the config
>      table (ccu-<SOC>.c) from Linux
>  * reuse the 'allwinner,pinctrl.txt' documentation for the pinctrl
>    binding from Linux
>  * adds the includes for the CCU dt-bindings from Linux
>  * adds support for printing the CCU subsystem via 'clk dump'
> 
> 
> Philipp Tomsich (9):
>   sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi
>   dm: core: Allow multiple drivers to bind for a single node
>   sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding
>   Kconfig: sunxi: Select new option for allow multiple drivers to bind
>   sunxi: add module reset (UCLASS_RESET) support for sunxi
>   linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel
>   clk: clk-uclass: add clk_get_by_output_name
>   sunxi: add clock driver (UCLASS_CLK) support for sunxi
>   cmd: move CONFIG_CMD_CLK to Kconfig
> 
>  arch/arm/Kconfig                                   |   1 +
>  arch/arm/include/asm/arch-sunxi/gpio-internal.h    |  19 +
>  cmd/Kconfig                                        |  10 +
>  .../pinctrl/allwinner,pinctrl.txt                  | 142 ++++
>  drivers/clk/Makefile                               |   1 +
>  drivers/clk/clk-uclass.c                           |  30 +
>  drivers/clk/sunxi/Makefile                         |  30 +
>  drivers/clk/sunxi/ccu-compatibility.h              | 232 ++++++
>  drivers/clk/sunxi/ccu-runtime-divider.c            | 115 +++
>  drivers/clk/sunxi/ccu-runtime-fixedfactor.c        |  17 +
>  drivers/clk/sunxi/ccu-sun50i-a64.c                 | 810 +++++++++++++++++++++
>  drivers/clk/sunxi/ccu-sun50i-a64.h                 |  64 ++
>  drivers/clk/sunxi/ccu_common.c                     |  27 +
>  drivers/clk/sunxi/ccu_common.h                     |  67 ++
>  drivers/clk/sunxi/ccu_div.c                        | 134 ++++
>  drivers/clk/sunxi/ccu_div.h                        | 170 +++++
>  drivers/clk/sunxi/ccu_frac.c                       | 106 +++
>  drivers/clk/sunxi/ccu_frac.h                       |  46 ++
>  drivers/clk/sunxi/ccu_gate.c                       |  80 ++
>  drivers/clk/sunxi/ccu_gate.h                       |  45 ++
>  drivers/clk/sunxi/ccu_mp.c                         | 159 ++++
>  drivers/clk/sunxi/ccu_mp.h                         |  70 ++
>  drivers/clk/sunxi/ccu_mult.h                       |  39 +
>  drivers/clk/sunxi/ccu_mux.c                        | 201 +++++
>  drivers/clk/sunxi/ccu_mux.h                        | 105 +++
>  drivers/clk/sunxi/ccu_nk.c                         | 154 ++++
>  drivers/clk/sunxi/ccu_nk.h                         |  64 ++
>  drivers/clk/sunxi/ccu_nkm.c                        | 184 +++++
>  drivers/clk/sunxi/ccu_nkm.h                        |  84 +++
>  drivers/clk/sunxi/ccu_nkmp.c                       | 171 +++++
>  drivers/clk/sunxi/ccu_nkmp.h                       |  64 ++
>  drivers/clk/sunxi/ccu_nm.c                         | 148 ++++
>  drivers/clk/sunxi/ccu_nm.h                         |  84 +++
>  drivers/clk/sunxi/clk-sunxi-ccu.c                  | 550 ++++++++++++++
>  drivers/clk/sunxi/clk-sunxi-gate.c                 |  92 +++
>  drivers/clk/sunxi/clk-sunxi-mod.c                  | 241 ++++++
>  drivers/core/Kconfig                               |  14 +
>  drivers/core/lists.c                               |  12 +-
>  drivers/gpio/sunxi_gpio.c                          |  15 +-
>  drivers/pinctrl/Kconfig                            |  10 +
>  drivers/pinctrl/Makefile                           |   2 +
>  drivers/pinctrl/sunxi/Makefile                     |  10 +
>  drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c       |  92 +++
>  drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c         | 577 +++++++++++++++
>  drivers/pinctrl/sunxi/pinctrl-sunxi.c              | 355 +++++++++
>  drivers/pinctrl/sunxi/pinctrl-sunxi.h              | 311 ++++++++
>  drivers/reset/Kconfig                              |   9 +
>  drivers/reset/Makefile                             |   1 +
>  drivers/reset/sunxi/Makefile                       |   6 +
>  drivers/reset/sunxi/ccu-sun50i-a64.c               |  75 ++
>  drivers/reset/sunxi/ccu_reset.h                    |   9 +
>  drivers/reset/sunxi/reset-sunxi.c                  | 168 +++++
>  include/clk.h                                      |  22 +
>  include/dt-bindings/clock/sun50i-a64-ccu.h         | 134 ++++
>  include/dt-bindings/reset/sun50i-a64-ccu.h         |  98 +++
>  include/linux/kernel.h                             |   2 +
>  56 files changed, 6470 insertions(+), 8 deletions(-)
>  create mode 100644 arch/arm/include/asm/arch-sunxi/gpio-internal.h
>  create mode 100644 doc/device-tree-bindings/pinctrl/allwinner,pinctrl.txt
>  create mode 100644 drivers/clk/sunxi/Makefile
>  create mode 100644 drivers/clk/sunxi/ccu-compatibility.h
>  create mode 100644 drivers/clk/sunxi/ccu-runtime-divider.c
>  create mode 100644 drivers/clk/sunxi/ccu-runtime-fixedfactor.c
>  create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.c
>  create mode 100644 drivers/clk/sunxi/ccu-sun50i-a64.h
>  create mode 100644 drivers/clk/sunxi/ccu_common.c
>  create mode 100644 drivers/clk/sunxi/ccu_common.h
>  create mode 100644 drivers/clk/sunxi/ccu_div.c
>  create mode 100644 drivers/clk/sunxi/ccu_div.h
>  create mode 100644 drivers/clk/sunxi/ccu_frac.c
>  create mode 100644 drivers/clk/sunxi/ccu_frac.h
>  create mode 100644 drivers/clk/sunxi/ccu_gate.c
>  create mode 100644 drivers/clk/sunxi/ccu_gate.h
>  create mode 100644 drivers/clk/sunxi/ccu_mp.c
>  create mode 100644 drivers/clk/sunxi/ccu_mp.h
>  create mode 100644 drivers/clk/sunxi/ccu_mult.h
>  create mode 100644 drivers/clk/sunxi/ccu_mux.c
>  create mode 100644 drivers/clk/sunxi/ccu_mux.h
>  create mode 100644 drivers/clk/sunxi/ccu_nk.c
>  create mode 100644 drivers/clk/sunxi/ccu_nk.h
>  create mode 100644 drivers/clk/sunxi/ccu_nkm.c
>  create mode 100644 drivers/clk/sunxi/ccu_nkm.h
>  create mode 100644 drivers/clk/sunxi/ccu_nkmp.c
>  create mode 100644 drivers/clk/sunxi/ccu_nkmp.h
>  create mode 100644 drivers/clk/sunxi/ccu_nm.c
>  create mode 100644 drivers/clk/sunxi/ccu_nm.h
>  create mode 100644 drivers/clk/sunxi/clk-sunxi-ccu.c
>  create mode 100644 drivers/clk/sunxi/clk-sunxi-gate.c
>  create mode 100644 drivers/clk/sunxi/clk-sunxi-mod.c
>  create mode 100644 drivers/pinctrl/sunxi/Makefile
>  create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64-r.c
>  create mode 100644 drivers/pinctrl/sunxi/pinctrl-sun50i-a64.c
>  create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.c
>  create mode 100644 drivers/pinctrl/sunxi/pinctrl-sunxi.h
>  create mode 100644 drivers/reset/sunxi/Makefile
>  create mode 100644 drivers/reset/sunxi/ccu-sun50i-a64.c
>  create mode 100644 drivers/reset/sunxi/ccu_reset.h
>  create mode 100644 drivers/reset/sunxi/reset-sunxi.c
>  create mode 100644 include/dt-bindings/clock/sun50i-a64-ccu.h
>  create mode 100644 include/dt-bindings/reset/sun50i-a64-ccu.h
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL
  2017-03-06  2:08 ` [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL André Przywara
@ 2017-03-06  9:43   ` Dr. Philipp Tomsich
  0 siblings, 0 replies; 12+ messages in thread
From: Dr. Philipp Tomsich @ 2017-03-06  9:43 UTC (permalink / raw)
  To: u-boot

Andre,

> On 06 Mar 2017, at 03:08, André Przywara <andre.przywara@arm.com> wrote:
> 
> On 01/03/17 21:19, Philipp Tomsich wrote:
>> Hi everyone,
>> 
>> here's the the new version of CLK, RESET and PINCTRL drivers to
>> configure sunxi from the device-tree.  This adds support for the
>> upstream CCU node (for reset and pinctrl) and tries to address the
>> various concerns people had.
>> 
>> Note that (to stay in sync with the Linux files), some of the
>> patches may not fully adhere to the style (e.g. some of the code
>> reused verbatim from Linux and in the config tables).
>> 
>> This has been tested with Ethernet, I2C, SPI and MMC on the A64-uQ7.
>> See my separate patchsets for the conversion of these drivers over
>> to support DM-based CLK, RESET and PINCTRL configuration.
>> 
>> Changes is v3:
>> * add support for the 'new-style' clock subsystem (CCU) by porting
>>   from Linux and adding the necessary glue implementation
> 
> Gah, is this really necessary? Do we really need to pull in the whole of
> the complex Linux clock driver?

It’s not necessary and I’d be more than happy to reduce the list of the
clocks in the table, but reusing the Linux driver (and it’s infrastructure)
allows us to add new SoCs by reusing the table from Linux.  However,
reducing the number of table entries will not reduce the “bulk” much,
as these are only config entries (and the code to support the various
types of clocks should be present nonetheless).

Given that these tables are fully tested on the Linux side, I believe this
to be of more value than to keep this small for U-Boot… after all, the
full CCU-support will only be used for the final U-Boot stage (which runs
from DRAM) and never considered for SPL/TPL.

> In the end all that U-Boot needs to do is to program a few simple
> clocks, a task which it easily did so far with just some register
> writes. I don't think it's appropriate for U-Boot to get the whole
> complex clock driver from Linux for just that purpose.

I beg to differ on the “a few simple clocks” aspect of this.
A full-featured U-Boot should require (including reparenting) clocks for
SPI, I2C, MMC, EMAC, USB (incl. PHYs) and video (on the SoCs where
simplefb is supported).

It’s best to keep all of this in sync with Linux, as this will simplify the
long-term maintenance.

> One indication of this being too much is that the mailing list seemed to
> have blocked patch 8/9, probably because it's too big ;-)
> I am especially scared when it comes to adding all of the other SoC's
> clock drivers to the code base as well.

It was just a couple bytes over the limit to require moderation ;-)

The benefit of this approach is that all the ccu_*.c clock implementations
and the configuration table can be reused almost verbatim from Linux.
The only change necessary will be in the list of headers included and
the change of the _ops datatype.

> So can't we do a much simpler (because limited) implementation?
> If a peripheral driver gets the clock index from the DT and asks the
> clock driver to program (or just enable) clock "75", can't we just have
> a simple function which redirects this clock number to our already
> existing code?
> Something like (rough sketch):
> int sun50i_clk_set_freq(int nr, int freq)
> {
> 	int idx;
> 
> 	switch (nr) {
> 	case CLK_MMC0 ... CLK_MMC2:
> 		idx = nr - CLK_MMC0;
> 		return mmc_set_mod_clk(idx,
> 				       clkreg_addr[MMC_CLK + idx],
> 				       freq);
> 	}
> }
> ... with mmc_set_mod_clk() being a slightly changed version of the
> existing implementation?

This will not help with many of the use-cases, such as reparenting clocks
dynamically as we change clocks on SPI.

It seems to be a good alternative implementation for the SPL use-case,
though and I’d be happy to add this in as the SPL-alternative.

> I think that would seriously reduce the bloat and be a better fit for
> U-Boot. And it might even allow the SPL to reuse this by just calling
> into the dispatch function with hardcoded parameters.

The total bulk added is not trivial, but appears to be be reasonable (for the
final-stage U-Boot):
1. code to support the various clocks: +33KB
2. worst-case config-table (i.e. what we have on the sun50iw1p1): +15KB

And the config-table can easily be reduced to a more manageable size
to removing clocks that we’ll never need (e.g. GPU, audio, …).

Cheers,
Philipp.

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2017-03-06  9:43 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-03-01 21:19 [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 1/9] sunxi: add pinctrl (UCLASS_PINCTRL) support for sunxi Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 2/9] dm: core: Allow multiple drivers to bind for a single node Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 3/9] sunxi: CONFIG_DM_ALLOW_MULTIPLE_DRIVERS for gpio/pinctrl binding Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 4/9] Kconfig: sunxi: Select new option for allow multiple drivers to bind Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 5/9] sunxi: add module reset (UCLASS_RESET) support for sunxi Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 6/9] linux/kernel.h: sync DIV_ROUND_UP_ULL from kernel Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 7/9] clk: clk-uclass: add clk_get_by_output_name Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi Philipp Tomsich
2017-03-01 21:20 ` [U-Boot] [PATCH v3 9/9] cmd: move CONFIG_CMD_CLK to Kconfig Philipp Tomsich
2017-03-06  2:08 ` [U-Boot] [PATCH v3 0/9] sunxi: DM-based CLK, RESET and PINCTRL André Przywara
2017-03-06  9:43   ` Dr. Philipp Tomsich

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.