[U-Boot] [PATCH v3 8/9] sunxi: add clock driver (UCLASS_CLK) support for sunxi

Philipp Tomsich philipp.tomsich at theobroma-systems.com
Wed Mar 1 21:20:07 UTC 2017


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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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 at 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



More information about the U-Boot mailing list