summaryrefslogtreecommitdiffstats
path: root/drivers/clk/sunxi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/clk/sunxi')
-rw-r--r--drivers/clk/sunxi/Kconfig42
-rw-r--r--drivers/clk/sunxi/Makefile34
-rw-r--r--drivers/clk/sunxi/clk-a10-codec.c35
-rw-r--r--drivers/clk/sunxi/clk-a10-hosc.c62
-rw-r--r--drivers/clk/sunxi/clk-a10-mod1.c73
-rw-r--r--drivers/clk/sunxi/clk-a10-pll2.c195
-rw-r--r--drivers/clk/sunxi/clk-a10-ve.c163
-rw-r--r--drivers/clk/sunxi/clk-a20-gmac.c112
-rw-r--r--drivers/clk/sunxi/clk-factors.c304
-rw-r--r--drivers/clk/sunxi/clk-factors.h65
-rw-r--r--drivers/clk/sunxi/clk-mod0.c374
-rw-r--r--drivers/clk/sunxi/clk-simple-gates.c170
-rw-r--r--drivers/clk/sunxi/clk-sun4i-display.c256
-rw-r--r--drivers/clk/sunxi/clk-sun4i-pll3.c90
-rw-r--r--drivers/clk/sunxi/clk-sun4i-tcon-ch1.c288
-rw-r--r--drivers/clk/sunxi/clk-sun6i-apb0-gates.c99
-rw-r--r--drivers/clk/sunxi/clk-sun6i-apb0.c69
-rw-r--r--drivers/clk/sunxi/clk-sun6i-ar100.c104
-rw-r--r--drivers/clk/sunxi/clk-sun8i-apb0.c113
-rw-r--r--drivers/clk/sunxi/clk-sun8i-bus-gates.c109
-rw-r--r--drivers/clk/sunxi/clk-sun8i-mbus.c109
-rw-r--r--drivers/clk/sunxi/clk-sun9i-core.c282
-rw-r--r--drivers/clk/sunxi/clk-sun9i-cpus.c242
-rw-r--r--drivers/clk/sunxi/clk-sun9i-mmc.c205
-rw-r--r--drivers/clk/sunxi/clk-sunxi.c1167
-rw-r--r--drivers/clk/sunxi/clk-usb.c249
26 files changed, 5011 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig
new file mode 100644
index 000000000..1c4e54336
--- /dev/null
+++ b/drivers/clk/sunxi/Kconfig
@@ -0,0 +1,42 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig CLK_SUNXI
+ bool "Legacy clock support for Allwinner SoCs"
+ depends on (ARM && ARCH_SUNXI) || COMPILE_TEST
+ default y
+
+if CLK_SUNXI
+
+config CLK_SUNXI_CLOCKS
+ bool "Legacy clock drivers"
+ default y
+ help
+ Legacy clock drivers being used on older (A10, A13, A20,
+ A23, A31, A80) SoCs. These drivers are kept around for
+ Device Tree backward compatibility issues, in case one would
+ still use a Device Tree with one clock provider by
+ node. Newer Device Trees and newer SoCs use the drivers
+ controlled by CONFIG_SUNXI_CCU.
+
+config CLK_SUNXI_PRCM_SUN6I
+ bool "Legacy A31 PRCM driver"
+ default y
+ help
+ Legacy clock driver for the A31 PRCM clocks. Those are
+ usually needed for the PMIC communication, mostly.
+
+config CLK_SUNXI_PRCM_SUN8I
+ bool "Legacy sun8i PRCM driver"
+ default y
+ help
+ Legacy clock driver for the sun8i family PRCM clocks.
+ Those are usually needed for the PMIC communication,
+ mostly.
+
+config CLK_SUNXI_PRCM_SUN9I
+ bool "Legacy A80 PRCM driver"
+ default y
+ help
+ Legacy clock driver for the A80 PRCM clocks. Those are
+ usually needed for the PMIC communication, mostly.
+
+endif
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
new file mode 100644
index 000000000..e10824c76
--- /dev/null
+++ b/drivers/clk/sunxi/Makefile
@@ -0,0 +1,34 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for sunxi specific clk
+#
+
+obj-$(CONFIG_CLK_SUNXI) += clk-factors.o
+
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sunxi.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a10-codec.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a10-hosc.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a10-mod1.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a10-pll2.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a10-ve.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-a20-gmac.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-mod0.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-simple-gates.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun4i-display.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun4i-pll3.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun4i-tcon-ch1.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun8i-bus-gates.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun8i-mbus.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun9i-core.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun9i-mmc.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-usb.o
+
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun8i-apb0.o
+obj-$(CONFIG_CLK_SUNXI_CLOCKS) += clk-sun9i-cpus.o
+
+obj-$(CONFIG_CLK_SUNXI_PRCM_SUN6I) += clk-sun6i-apb0.o
+obj-$(CONFIG_CLK_SUNXI_PRCM_SUN6I) += clk-sun6i-apb0-gates.o
+obj-$(CONFIG_CLK_SUNXI_PRCM_SUN6I) += clk-sun6i-ar100.o
+
+obj-$(CONFIG_CLK_SUNXI_PRCM_SUN8I) += clk-sun8i-apb0.o
+obj-$(CONFIG_CLK_SUNXI_PRCM_SUN8I) += clk-sun6i-apb0-gates.o
diff --git a/drivers/clk/sunxi/clk-a10-codec.c b/drivers/clk/sunxi/clk-a10-codec.c
new file mode 100644
index 000000000..6fc6de044
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-codec.c
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#define SUN4I_CODEC_GATE 31
+
+static void __init sun4i_codec_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ const char *clk_name = node->name, *parent_name;
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+ parent_name = of_clk_get_parent_name(node, 0);
+
+ clk = clk_register_gate(NULL, clk_name, parent_name,
+ CLK_SET_RATE_PARENT, reg,
+ SUN4I_CODEC_GATE, 0, NULL);
+
+ if (!IS_ERR(clk))
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+}
+CLK_OF_DECLARE(sun4i_codec, "allwinner,sun4i-a10-codec-clk",
+ sun4i_codec_clk_setup);
diff --git a/drivers/clk/sunxi/clk-a10-hosc.c b/drivers/clk/sunxi/clk-a10-hosc.c
new file mode 100644
index 000000000..f07e97683
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-hosc.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#define SUNXI_OSC24M_GATE 0
+
+static DEFINE_SPINLOCK(hosc_lock);
+
+static void __init sun4i_osc_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct clk_fixed_rate *fixed;
+ struct clk_gate *gate;
+ const char *clk_name = node->name;
+ u32 rate;
+
+ if (of_property_read_u32(node, "clock-frequency", &rate))
+ return;
+
+ /* allocate fixed-rate and gate clock structs */
+ fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL);
+ if (!fixed)
+ return;
+ gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
+ if (!gate)
+ goto err_free_fixed;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ /* set up gate and fixed rate properties */
+ gate->reg = of_iomap(node, 0);
+ gate->bit_idx = SUNXI_OSC24M_GATE;
+ gate->lock = &hosc_lock;
+ fixed->fixed_rate = rate;
+
+ clk = clk_register_composite(NULL, clk_name,
+ NULL, 0,
+ NULL, NULL,
+ &fixed->hw, &clk_fixed_rate_ops,
+ &gate->hw, &clk_gate_ops, 0);
+
+ if (IS_ERR(clk))
+ goto err_free_gate;
+
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+ return;
+
+err_free_gate:
+ kfree(gate);
+err_free_fixed:
+ kfree(fixed);
+}
+CLK_OF_DECLARE(sun4i_osc, "allwinner,sun4i-a10-osc-clk", sun4i_osc_clk_setup);
diff --git a/drivers/clk/sunxi/clk-a10-mod1.c b/drivers/clk/sunxi/clk-a10-mod1.c
new file mode 100644
index 000000000..39ad56d75
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-mod1.c
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+static DEFINE_SPINLOCK(mod1_lock);
+
+#define SUN4I_MOD1_ENABLE 31
+#define SUN4I_MOD1_MUX 16
+#define SUN4I_MOD1_MUX_WIDTH 2
+#define SUN4I_MOD1_MAX_PARENTS 4
+
+static void __init sun4i_mod1_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct clk_mux *mux;
+ struct clk_gate *gate;
+ const char *parents[4];
+ const char *clk_name = node->name;
+ void __iomem *reg;
+ int i;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ goto err_unmap;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto err_free_mux;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+ i = of_clk_parent_fill(node, parents, SUN4I_MOD1_MAX_PARENTS);
+
+ gate->reg = reg;
+ gate->bit_idx = SUN4I_MOD1_ENABLE;
+ gate->lock = &mod1_lock;
+ mux->reg = reg;
+ mux->shift = SUN4I_MOD1_MUX;
+ mux->mask = BIT(SUN4I_MOD1_MUX_WIDTH) - 1;
+ mux->lock = &mod1_lock;
+
+ clk = clk_register_composite(NULL, clk_name, parents, i,
+ &mux->hw, &clk_mux_ops,
+ NULL, NULL,
+ &gate->hw, &clk_gate_ops, CLK_SET_RATE_PARENT);
+ if (IS_ERR(clk))
+ goto err_free_gate;
+
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+ return;
+
+err_free_gate:
+ kfree(gate);
+err_free_mux:
+ kfree(mux);
+err_unmap:
+ iounmap(reg);
+}
+CLK_OF_DECLARE(sun4i_mod1, "allwinner,sun4i-a10-mod1-clk",
+ sun4i_mod1_clk_setup);
diff --git a/drivers/clk/sunxi/clk-a10-pll2.c b/drivers/clk/sunxi/clk-a10-pll2.c
new file mode 100644
index 000000000..2ea3b4232
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-pll2.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ * Emilio López <emilio@elopez.com.ar>
+ *
+ * Copyright 2015 Maxime Ripard
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+#include <dt-bindings/clock/sun4i-a10-pll2.h>
+
+#define SUN4I_PLL2_ENABLE 31
+
+#define SUN4I_PLL2_PRE_DIV_SHIFT 0
+#define SUN4I_PLL2_PRE_DIV_WIDTH 5
+#define SUN4I_PLL2_PRE_DIV_MASK GENMASK(SUN4I_PLL2_PRE_DIV_WIDTH - 1, 0)
+
+#define SUN4I_PLL2_N_SHIFT 8
+#define SUN4I_PLL2_N_WIDTH 7
+#define SUN4I_PLL2_N_MASK GENMASK(SUN4I_PLL2_N_WIDTH - 1, 0)
+
+#define SUN4I_PLL2_POST_DIV_SHIFT 26
+#define SUN4I_PLL2_POST_DIV_WIDTH 4
+#define SUN4I_PLL2_POST_DIV_MASK GENMASK(SUN4I_PLL2_POST_DIV_WIDTH - 1, 0)
+
+#define SUN4I_PLL2_POST_DIV_VALUE 4
+
+#define SUN4I_PLL2_OUTPUTS 4
+
+static DEFINE_SPINLOCK(sun4i_a10_pll2_lock);
+
+static void __init sun4i_pll2_setup(struct device_node *node,
+ int post_div_offset)
+{
+ const char *clk_name = node->name, *parent;
+ struct clk **clks, *base_clk, *prediv_clk;
+ struct clk_onecell_data *clk_data;
+ struct clk_multiplier *mult;
+ struct clk_gate *gate;
+ void __iomem *reg;
+ u32 val;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ clk_data = kzalloc(sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ goto err_unmap;
+
+ clks = kcalloc(SUN4I_PLL2_OUTPUTS, sizeof(struct clk *), GFP_KERNEL);
+ if (!clks)
+ goto err_free_data;
+
+ parent = of_clk_get_parent_name(node, 0);
+ prediv_clk = clk_register_divider(NULL, "pll2-prediv",
+ parent, 0, reg,
+ SUN4I_PLL2_PRE_DIV_SHIFT,
+ SUN4I_PLL2_PRE_DIV_WIDTH,
+ CLK_DIVIDER_ONE_BASED | CLK_DIVIDER_ALLOW_ZERO,
+ &sun4i_a10_pll2_lock);
+ if (IS_ERR(prediv_clk)) {
+ pr_err("Couldn't register the prediv clock\n");
+ goto err_free_array;
+ }
+
+ /* Setup the gate part of the PLL2 */
+ gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
+ if (!gate)
+ goto err_unregister_prediv;
+
+ gate->reg = reg;
+ gate->bit_idx = SUN4I_PLL2_ENABLE;
+ gate->lock = &sun4i_a10_pll2_lock;
+
+ /* Setup the multiplier part of the PLL2 */
+ mult = kzalloc(sizeof(struct clk_multiplier), GFP_KERNEL);
+ if (!mult)
+ goto err_free_gate;
+
+ mult->reg = reg;
+ mult->shift = SUN4I_PLL2_N_SHIFT;
+ mult->width = 7;
+ mult->flags = CLK_MULTIPLIER_ZERO_BYPASS |
+ CLK_MULTIPLIER_ROUND_CLOSEST;
+ mult->lock = &sun4i_a10_pll2_lock;
+
+ parent = __clk_get_name(prediv_clk);
+ base_clk = clk_register_composite(NULL, "pll2-base",
+ &parent, 1,
+ NULL, NULL,
+ &mult->hw, &clk_multiplier_ops,
+ &gate->hw, &clk_gate_ops,
+ CLK_SET_RATE_PARENT);
+ if (IS_ERR(base_clk)) {
+ pr_err("Couldn't register the base multiplier clock\n");
+ goto err_free_multiplier;
+ }
+
+ parent = __clk_get_name(base_clk);
+
+ /*
+ * PLL2-1x
+ *
+ * This is supposed to have a post divider, but we won't need
+ * to use it, we just need to initialise it to 4, and use a
+ * fixed divider.
+ */
+ val = readl(reg);
+ val &= ~(SUN4I_PLL2_POST_DIV_MASK << SUN4I_PLL2_POST_DIV_SHIFT);
+ val |= (SUN4I_PLL2_POST_DIV_VALUE - post_div_offset) << SUN4I_PLL2_POST_DIV_SHIFT;
+ writel(val, reg);
+
+ of_property_read_string_index(node, "clock-output-names",
+ SUN4I_A10_PLL2_1X, &clk_name);
+ clks[SUN4I_A10_PLL2_1X] = clk_register_fixed_factor(NULL, clk_name,
+ parent,
+ CLK_SET_RATE_PARENT,
+ 1,
+ SUN4I_PLL2_POST_DIV_VALUE);
+ WARN_ON(IS_ERR(clks[SUN4I_A10_PLL2_1X]));
+
+ /*
+ * PLL2-2x
+ *
+ * This clock doesn't use the post divider, and really is just
+ * a fixed divider from the PLL2 base clock.
+ */
+ of_property_read_string_index(node, "clock-output-names",
+ SUN4I_A10_PLL2_2X, &clk_name);
+ clks[SUN4I_A10_PLL2_2X] = clk_register_fixed_factor(NULL, clk_name,
+ parent,
+ CLK_SET_RATE_PARENT,
+ 1, 2);
+ WARN_ON(IS_ERR(clks[SUN4I_A10_PLL2_2X]));
+
+ /* PLL2-4x */
+ of_property_read_string_index(node, "clock-output-names",
+ SUN4I_A10_PLL2_4X, &clk_name);
+ clks[SUN4I_A10_PLL2_4X] = clk_register_fixed_factor(NULL, clk_name,
+ parent,
+ CLK_SET_RATE_PARENT,
+ 1, 1);
+ WARN_ON(IS_ERR(clks[SUN4I_A10_PLL2_4X]));
+
+ /* PLL2-8x */
+ of_property_read_string_index(node, "clock-output-names",
+ SUN4I_A10_PLL2_8X, &clk_name);
+ clks[SUN4I_A10_PLL2_8X] = clk_register_fixed_factor(NULL, clk_name,
+ parent,
+ CLK_SET_RATE_PARENT,
+ 2, 1);
+ WARN_ON(IS_ERR(clks[SUN4I_A10_PLL2_8X]));
+
+ clk_data->clks = clks;
+ clk_data->clk_num = SUN4I_PLL2_OUTPUTS;
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+ return;
+
+err_free_multiplier:
+ kfree(mult);
+err_free_gate:
+ kfree(gate);
+err_unregister_prediv:
+ clk_unregister_divider(prediv_clk);
+err_free_array:
+ kfree(clks);
+err_free_data:
+ kfree(clk_data);
+err_unmap:
+ iounmap(reg);
+}
+
+static void __init sun4i_a10_pll2_setup(struct device_node *node)
+{
+ sun4i_pll2_setup(node, 0);
+}
+
+CLK_OF_DECLARE(sun4i_a10_pll2, "allwinner,sun4i-a10-pll2-clk",
+ sun4i_a10_pll2_setup);
+
+static void __init sun5i_a13_pll2_setup(struct device_node *node)
+{
+ sun4i_pll2_setup(node, 1);
+}
+
+CLK_OF_DECLARE(sun5i_a13_pll2, "allwinner,sun5i-a13-pll2-clk",
+ sun5i_a13_pll2_setup);
diff --git a/drivers/clk/sunxi/clk-a10-ve.c b/drivers/clk/sunxi/clk-a10-ve.c
new file mode 100644
index 000000000..65810937a
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-ve.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+static DEFINE_SPINLOCK(ve_lock);
+
+#define SUN4I_VE_ENABLE 31
+#define SUN4I_VE_DIVIDER_SHIFT 16
+#define SUN4I_VE_DIVIDER_WIDTH 3
+#define SUN4I_VE_RESET 0
+
+/*
+ * sunxi_ve_reset... - reset bit in ve clk registers handling
+ */
+
+struct ve_reset_data {
+ void __iomem *reg;
+ spinlock_t *lock;
+ struct reset_controller_dev rcdev;
+};
+
+static int sunxi_ve_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct ve_reset_data *data = container_of(rcdev,
+ struct ve_reset_data,
+ rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg & ~BIT(SUN4I_VE_RESET), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+
+ return 0;
+}
+
+static int sunxi_ve_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct ve_reset_data *data = container_of(rcdev,
+ struct ve_reset_data,
+ rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg | BIT(SUN4I_VE_RESET), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+
+ return 0;
+}
+
+static int sunxi_ve_of_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *reset_spec)
+{
+ if (WARN_ON(reset_spec->args_count != 0))
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct reset_control_ops sunxi_ve_reset_ops = {
+ .assert = sunxi_ve_reset_assert,
+ .deassert = sunxi_ve_reset_deassert,
+};
+
+static void __init sun4i_ve_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct clk_divider *div;
+ struct clk_gate *gate;
+ struct ve_reset_data *reset_data;
+ const char *parent;
+ const char *clk_name = node->name;
+ void __iomem *reg;
+ int err;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ goto err_unmap;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto err_free_div;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+ parent = of_clk_get_parent_name(node, 0);
+
+ gate->reg = reg;
+ gate->bit_idx = SUN4I_VE_ENABLE;
+ gate->lock = &ve_lock;
+
+ div->reg = reg;
+ div->shift = SUN4I_VE_DIVIDER_SHIFT;
+ div->width = SUN4I_VE_DIVIDER_WIDTH;
+ div->lock = &ve_lock;
+
+ clk = clk_register_composite(NULL, clk_name, &parent, 1,
+ NULL, NULL,
+ &div->hw, &clk_divider_ops,
+ &gate->hw, &clk_gate_ops,
+ CLK_SET_RATE_PARENT);
+ if (IS_ERR(clk))
+ goto err_free_gate;
+
+ err = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (err)
+ goto err_unregister_clk;
+
+ reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
+ if (!reset_data)
+ goto err_del_provider;
+
+ reset_data->reg = reg;
+ reset_data->lock = &ve_lock;
+ reset_data->rcdev.nr_resets = 1;
+ reset_data->rcdev.ops = &sunxi_ve_reset_ops;
+ reset_data->rcdev.of_node = node;
+ reset_data->rcdev.of_xlate = sunxi_ve_of_xlate;
+ reset_data->rcdev.of_reset_n_cells = 0;
+ err = reset_controller_register(&reset_data->rcdev);
+ if (err)
+ goto err_free_reset;
+
+ return;
+
+err_free_reset:
+ kfree(reset_data);
+err_del_provider:
+ of_clk_del_provider(node);
+err_unregister_clk:
+ clk_unregister(clk);
+err_free_gate:
+ kfree(gate);
+err_free_div:
+ kfree(div);
+err_unmap:
+ iounmap(reg);
+}
+CLK_OF_DECLARE(sun4i_ve, "allwinner,sun4i-a10-ve-clk",
+ sun4i_ve_clk_setup);
diff --git a/drivers/clk/sunxi/clk-a20-gmac.c b/drivers/clk/sunxi/clk-a20-gmac.c
new file mode 100644
index 000000000..0b09230a0
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a20-gmac.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ * Emilio López <emilio@elopez.com.ar>
+ *
+ * Copyright 2013 Chen-Yu Tsai
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+
+static DEFINE_SPINLOCK(gmac_lock);
+
+/**
+ * sun7i_a20_gmac_clk_setup - Setup function for A20/A31 GMAC clock module
+ *
+ * This clock looks something like this
+ * ________________________
+ * MII TX clock from PHY >-----|___________ _________|----> to GMAC core
+ * GMAC Int. RGMII TX clk >----|___________\__/__gate---|----> to PHY
+ * Ext. 125MHz RGMII TX clk >--|__divider__/ |
+ * |________________________|
+ *
+ * The external 125 MHz reference is optional, i.e. GMAC can use its
+ * internal TX clock just fine. The A31 GMAC clock module does not have
+ * the divider controls for the external reference.
+ *
+ * To keep it simple, let the GMAC use either the MII TX clock for MII mode,
+ * and its internal TX clock for GMII and RGMII modes. The GMAC driver should
+ * select the appropriate source and gate/ungate the output to the PHY.
+ *
+ * Only the GMAC should use this clock. Altering the clock so that it doesn't
+ * match the GMAC's operation parameters will result in the GMAC not being
+ * able to send traffic out. The GMAC driver should set the clock rate and
+ * enable/disable this clock to configure the required state. The clock
+ * driver then responds by auto-reparenting the clock.
+ */
+
+#define SUN7I_A20_GMAC_GPIT 2
+#define SUN7I_A20_GMAC_MASK 0x3
+#define SUN7I_A20_GMAC_PARENTS 2
+
+static u32 sun7i_a20_gmac_mux_table[SUN7I_A20_GMAC_PARENTS] = {
+ 0x00, /* Select mii_phy_tx_clk */
+ 0x02, /* Select gmac_int_tx_clk */
+};
+
+static void __init sun7i_a20_gmac_clk_setup(struct device_node *node)
+{
+ struct clk *clk;
+ struct clk_mux *mux;
+ struct clk_gate *gate;
+ const char *clk_name = node->name;
+ const char *parents[SUN7I_A20_GMAC_PARENTS];
+ void __iomem *reg;
+
+ if (of_property_read_string(node, "clock-output-names", &clk_name))
+ return;
+
+ /* allocate mux and gate clock structs */
+ mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
+ if (!mux)
+ return;
+
+ gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
+ if (!gate)
+ goto free_mux;
+
+ /* gmac clock requires exactly 2 parents */
+ if (of_clk_parent_fill(node, parents, 2) != 2)
+ goto free_gate;
+
+ reg = of_iomap(node, 0);
+ if (!reg)
+ goto free_gate;
+
+ /* set up gate and fixed rate properties */
+ gate->reg = reg;
+ gate->bit_idx = SUN7I_A20_GMAC_GPIT;
+ gate->lock = &gmac_lock;
+ mux->reg = reg;
+ mux->mask = SUN7I_A20_GMAC_MASK;
+ mux->table = sun7i_a20_gmac_mux_table;
+ mux->lock = &gmac_lock;
+
+ clk = clk_register_composite(NULL, clk_name,
+ parents, SUN7I_A20_GMAC_PARENTS,
+ &mux->hw, &clk_mux_ops,
+ NULL, NULL,
+ &gate->hw, &clk_gate_ops,
+ 0);
+
+ if (IS_ERR(clk))
+ goto iounmap_reg;
+
+ of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+ return;
+
+iounmap_reg:
+ iounmap(reg);
+free_gate:
+ kfree(gate);
+free_mux:
+ kfree(mux);
+}
+CLK_OF_DECLARE(sun7i_a20_gmac, "allwinner,sun7i-a20-gmac-clk",
+ sun7i_a20_gmac_clk_setup);
diff --git a/drivers/clk/sunxi/clk-factors.c b/drivers/clk/sunxi/clk-factors.c
new file mode 100644
index 000000000..4d8f0422b
--- /dev/null
+++ b/drivers/clk/sunxi/clk-factors.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2013 Emilio López <emilio@elopez.com.ar>
+ *
+ * Adjustable factor-based clock implementation
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "clk-factors.h"
+
+/*
+ * DOC: basic adjustable factor-based clock
+ *
+ * Traits of this clock:
+ * prepare - clk_prepare only ensures that parents are prepared
+ * enable - clk_enable only ensures that parents are enabled
+ * rate - rate is adjustable.
+ * clk->rate = (parent->rate * N * (K + 1) >> P) / (M + 1)
+ * parent - fixed parent. No clk_set_parent support
+ */
+
+#define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw)
+
+#define FACTORS_MAX_PARENTS 5
+
+#define SETMASK(len, pos) (((1U << (len)) - 1) << (pos))
+#define CLRMASK(len, pos) (~(SETMASK(len, pos)))
+#define FACTOR_GET(bit, len, reg) (((reg) & SETMASK(len, bit)) >> (bit))
+
+#define FACTOR_SET(bit, len, reg, val) \
+ (((reg) & CLRMASK(len, bit)) | (val << (bit)))
+
+static unsigned long clk_factors_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ u8 n = 1, k = 0, p = 0, m = 0;
+ u32 reg;
+ unsigned long rate;
+ struct clk_factors *factors = to_clk_factors(hw);
+ const struct clk_factors_config *config = factors->config;
+
+ /* Fetch the register value */
+ reg = readl(factors->reg);
+
+ /* Get each individual factor if applicable */
+ if (config->nwidth != SUNXI_FACTORS_NOT_APPLICABLE)
+ n = FACTOR_GET(config->nshift, config->nwidth, reg);
+ if (config->kwidth != SUNXI_FACTORS_NOT_APPLICABLE)
+ k = FACTOR_GET(config->kshift, config->kwidth, reg);
+ if (config->mwidth != SUNXI_FACTORS_NOT_APPLICABLE)
+ m = FACTOR_GET(config->mshift, config->mwidth, reg);
+ if (config->pwidth != SUNXI_FACTORS_NOT_APPLICABLE)
+ p = FACTOR_GET(config->pshift, config->pwidth, reg);
+
+ if (factors->recalc) {
+ struct factors_request factors_req = {
+ .parent_rate = parent_rate,
+ .n = n,
+ .k = k,
+ .m = m,
+ .p = p,
+ };
+
+ /* get mux details from mux clk structure */
+ if (factors->mux)
+ factors_req.parent_index =
+ (reg >> factors->mux->shift) &
+ factors->mux->mask;
+
+ factors->recalc(&factors_req);
+
+ return factors_req.rate;
+ }
+
+ /* Calculate the rate */
+ rate = (parent_rate * (n + config->n_start) * (k + 1) >> p) / (m + 1);
+
+ return rate;
+}
+
+static int clk_factors_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ struct clk_factors *factors = to_clk_factors(hw);
+ struct clk_hw *parent, *best_parent = NULL;
+ int i, num_parents;
+ unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
+
+ /* find the parent that can help provide the fastest rate <= rate */
+ num_parents = clk_hw_get_num_parents(hw);
+ for (i = 0; i < num_parents; i++) {
+ struct factors_request factors_req = {
+ .rate = req->rate,
+ .parent_index = i,
+ };
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+ if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)
+ parent_rate = clk_hw_round_rate(parent, req->rate);
+ else
+ parent_rate = clk_hw_get_rate(parent);
+
+ factors_req.parent_rate = parent_rate;
+ factors->get_factors(&factors_req);
+ child_rate = factors_req.rate;
+
+ if (child_rate <= req->rate && child_rate > best_child_rate) {
+ best_parent = parent;
+ best = parent_rate;
+ best_child_rate = child_rate;
+ }
+ }
+
+ if (!best_parent)
+ return -EINVAL;
+
+ req->best_parent_hw = best_parent;
+ req->best_parent_rate = best;
+ req->rate = best_child_rate;
+
+ return 0;
+}
+
+static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct factors_request req = {
+ .rate = rate,
+ .parent_rate = parent_rate,
+ };
+ u32 reg;
+ struct clk_factors *factors = to_clk_factors(hw);
+ const struct clk_factors_config *config = factors->config;
+ unsigned long flags = 0;
+
+ factors->get_factors(&req);
+
+ if (factors->lock)
+ spin_lock_irqsave(factors->lock, flags);
+
+ /* Fetch the register value */
+ reg = readl(factors->reg);
+
+ /* Set up the new factors - macros do not do anything if width is 0 */
+ reg = FACTOR_SET(config->nshift, config->nwidth, reg, req.n);
+ reg = FACTOR_SET(config->kshift, config->kwidth, reg, req.k);
+ reg = FACTOR_SET(config->mshift, config->mwidth, reg, req.m);
+ reg = FACTOR_SET(config->pshift, config->pwidth, reg, req.p);
+
+ /* Apply them now */
+ writel(reg, factors->reg);
+
+ /* delay 500us so pll stabilizes */
+ __delay((rate >> 20) * 500 / 2);
+
+ if (factors->lock)
+ spin_unlock_irqrestore(factors->lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops clk_factors_ops = {
+ .determine_rate = clk_factors_determine_rate,
+ .recalc_rate = clk_factors_recalc_rate,
+ .set_rate = clk_factors_set_rate,
+};
+
+static struct clk *__sunxi_factors_register(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock, void __iomem *reg,
+ unsigned long flags)
+{
+ struct clk *clk;
+ struct clk_factors *factors;
+ struct clk_gate *gate = NULL;
+ struct clk_mux *mux = NULL;
+ struct clk_hw *gate_hw = NULL;
+ struct clk_hw *mux_hw = NULL;
+ const char *clk_name = node->name;
+ const char *parents[FACTORS_MAX_PARENTS];
+ int ret, i = 0;
+
+ /* if we have a mux, we will have >1 parents */
+ i = of_clk_parent_fill(node, parents, FACTORS_MAX_PARENTS);
+
+ /*
+ * some factor clocks, such as pll5 and pll6, may have multiple
+ * outputs, and have their name designated in factors_data
+ */
+ if (data->name)
+ clk_name = data->name;
+ else
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL);
+ if (!factors)
+ goto err_factors;
+
+ /* set up factors properties */
+ factors->reg = reg;
+ factors->config = data->table;
+ factors->get_factors = data->getter;
+ factors->recalc = data->recalc;
+ factors->lock = lock;
+
+ /* Add a gate if this factor clock can be gated */
+ if (data->enable) {
+ gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL);
+ if (!gate)
+ goto err_gate;
+
+ factors->gate = gate;
+
+ /* set up gate properties */
+ gate->reg = reg;
+ gate->bit_idx = data->enable;
+ gate->lock = factors->lock;
+ gate_hw = &gate->hw;
+ }
+
+ /* Add a mux if this factor clock can be muxed */
+ if (data->mux) {
+ mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL);
+ if (!mux)
+ goto err_mux;
+
+ factors->mux = mux;
+
+ /* set up gate properties */
+ mux->reg = reg;
+ mux->shift = data->mux;
+ mux->mask = data->muxmask;
+ mux->lock = factors->lock;
+ mux_hw = &mux->hw;
+ }
+
+ clk = clk_register_composite(NULL, clk_name,
+ parents, i,
+ mux_hw, &clk_mux_ops,
+ &factors->hw, &clk_factors_ops,
+ gate_hw, &clk_gate_ops, CLK_IS_CRITICAL);
+ if (IS_ERR(clk))
+ goto err_register;
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret)
+ goto err_provider;
+
+ return clk;
+
+err_provider:
+ /* TODO: The composite clock stuff will leak a bit here. */
+ clk_unregister(clk);
+err_register:
+ kfree(mux);
+err_mux:
+ kfree(gate);
+err_gate:
+ kfree(factors);
+err_factors:
+ return NULL;
+}
+
+struct clk *sunxi_factors_register(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock,
+ void __iomem *reg)
+{
+ return __sunxi_factors_register(node, data, lock, reg, 0);
+}
+
+struct clk *sunxi_factors_register_critical(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock,
+ void __iomem *reg)
+{
+ return __sunxi_factors_register(node, data, lock, reg, CLK_IS_CRITICAL);
+}
+
+void sunxi_factors_unregister(struct device_node *node, struct clk *clk)
+{
+ struct clk_hw *hw = __clk_get_hw(clk);
+ struct clk_factors *factors;
+
+ if (!hw)
+ return;
+
+ factors = to_clk_factors(hw);
+
+ of_clk_del_provider(node);
+ /* TODO: The composite clock stuff will leak a bit here. */
+ clk_unregister(clk);
+ kfree(factors->mux);
+ kfree(factors->gate);
+ kfree(factors);
+}
diff --git a/drivers/clk/sunxi/clk-factors.h b/drivers/clk/sunxi/clk-factors.h
new file mode 100644
index 000000000..7ad2ca924
--- /dev/null
+++ b/drivers/clk/sunxi/clk-factors.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __MACH_SUNXI_CLK_FACTORS_H
+#define __MACH_SUNXI_CLK_FACTORS_H
+
+#include <linux/clk-provider.h>
+#include <linux/spinlock.h>
+
+#define SUNXI_FACTORS_NOT_APPLICABLE (0)
+
+struct clk_factors_config {
+ u8 nshift;
+ u8 nwidth;
+ u8 kshift;
+ u8 kwidth;
+ u8 mshift;
+ u8 mwidth;
+ u8 pshift;
+ u8 pwidth;
+ u8 n_start;
+};
+
+struct factors_request {
+ unsigned long rate;
+ unsigned long parent_rate;
+ u8 parent_index;
+ u8 n;
+ u8 k;
+ u8 m;
+ u8 p;
+};
+
+struct factors_data {
+ int enable;
+ int mux;
+ int muxmask;
+ const struct clk_factors_config *table;
+ void (*getter)(struct factors_request *req);
+ void (*recalc)(struct factors_request *req);
+ const char *name;
+};
+
+struct clk_factors {
+ struct clk_hw hw;
+ void __iomem *reg;
+ const struct clk_factors_config *config;
+ void (*get_factors)(struct factors_request *req);
+ void (*recalc)(struct factors_request *req);
+ spinlock_t *lock;
+ /* for cleanup */
+ struct clk_mux *mux;
+ struct clk_gate *gate;
+};
+
+struct clk *sunxi_factors_register(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock,
+ void __iomem *reg);
+struct clk *sunxi_factors_register_critical(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock,
+ void __iomem *reg);
+
+void sunxi_factors_unregister(struct device_node *node, struct clk *clk);
+
+#endif
diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c
new file mode 100644
index 000000000..51800289a
--- /dev/null
+++ b/drivers/clk/sunxi/clk-mod0.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include "clk-factors.h"
+
+/*
+ * 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);
+ */
+
+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;
+
+ 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);
+
+ req->rate = (req->parent_rate >> calcp) / calcm;
+ req->m = calcm - 1;
+ req->p = calcp;
+}
+
+/* user manual says "n" but it's really "p" */
+static const struct clk_factors_config sun4i_a10_mod0_config = {
+ .mshift = 0,
+ .mwidth = 4,
+ .pshift = 16,
+ .pwidth = 2,
+};
+
+static const struct factors_data sun4i_a10_mod0_data = {
+ .enable = 31,
+ .mux = 24,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun4i_a10_mod0_config,
+ .getter = sun4i_a10_get_mod0_factors,
+};
+
+static DEFINE_SPINLOCK(sun4i_a10_mod0_lock);
+
+static void __init sun4i_a10_mod0_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ /*
+ * This happens with mod0 clk nodes instantiated through
+ * mfd, as those do not have their resources assigned at
+ * CLK_OF_DECLARE time yet, so do not print an error.
+ */
+ return;
+ }
+
+ sunxi_factors_register(node, &sun4i_a10_mod0_data,
+ &sun4i_a10_mod0_lock, reg);
+}
+CLK_OF_DECLARE_DRIVER(sun4i_a10_mod0, "allwinner,sun4i-a10-mod0-clk",
+ sun4i_a10_mod0_setup);
+
+static int sun4i_a10_mod0_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ void __iomem *reg;
+
+ if (!np)
+ return -ENODEV;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ sunxi_factors_register(np, &sun4i_a10_mod0_data,
+ &sun4i_a10_mod0_lock, reg);
+ return 0;
+}
+
+static const struct of_device_id sun4i_a10_mod0_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun4i-a10-mod0-clk" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sun4i_a10_mod0_clk_driver = {
+ .driver = {
+ .name = "sun4i-a10-mod0-clk",
+ .of_match_table = sun4i_a10_mod0_clk_dt_ids,
+ },
+ .probe = sun4i_a10_mod0_clk_probe,
+};
+builtin_platform_driver(sun4i_a10_mod0_clk_driver);
+
+static const struct factors_data sun9i_a80_mod0_data __initconst = {
+ .enable = 31,
+ .mux = 24,
+ .muxmask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
+ .table = &sun4i_a10_mod0_config,
+ .getter = sun4i_a10_get_mod0_factors,
+};
+
+static void __init sun9i_a80_mod0_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for mod0-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ sunxi_factors_register(node, &sun9i_a80_mod0_data,
+ &sun4i_a10_mod0_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_mod0, "allwinner,sun9i-a80-mod0-clk", sun9i_a80_mod0_setup);
+
+static DEFINE_SPINLOCK(sun5i_a13_mbus_lock);
+
+static void __init sun5i_a13_mbus_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not get registers for a13-mbus-clk\n");
+ return;
+ }
+
+ /* The MBUS clocks needs to be always enabled */
+ sunxi_factors_register_critical(node, &sun4i_a10_mod0_data,
+ &sun5i_a13_mbus_lock, reg);
+}
+CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup);
+
+struct mmc_phase {
+ struct clk_hw hw;
+ u8 offset;
+ void __iomem *reg;
+ spinlock_t *lock;
+};
+
+#define to_mmc_phase(_hw) container_of(_hw, struct mmc_phase, hw)
+
+static int mmc_get_phase(struct clk_hw *hw)
+{
+ struct clk *mmc, *mmc_parent, *clk = hw->clk;
+ struct mmc_phase *phase = to_mmc_phase(hw);
+ unsigned int mmc_rate, mmc_parent_rate;
+ u16 step, mmc_div;
+ u32 value;
+ u8 delay;
+
+ value = readl(phase->reg);
+ delay = (value >> phase->offset) & 0x3;
+
+ if (!delay)
+ return 180;
+
+ /* Get the main MMC clock */
+ mmc = clk_get_parent(clk);
+ if (!mmc)
+ return -EINVAL;
+
+ /* And its rate */
+ mmc_rate = clk_get_rate(mmc);
+ if (!mmc_rate)
+ return -EINVAL;
+
+ /* Now, get the MMC parent (most likely some PLL) */
+ mmc_parent = clk_get_parent(mmc);
+ if (!mmc_parent)
+ return -EINVAL;
+
+ /* And its rate */
+ mmc_parent_rate = clk_get_rate(mmc_parent);
+ if (!mmc_parent_rate)
+ return -EINVAL;
+
+ /* Get MMC clock divider */
+ mmc_div = mmc_parent_rate / mmc_rate;
+
+ step = DIV_ROUND_CLOSEST(360, mmc_div);
+ return delay * step;
+}
+
+static int mmc_set_phase(struct clk_hw *hw, int degrees)
+{
+ struct clk *mmc, *mmc_parent, *clk = hw->clk;
+ struct mmc_phase *phase = to_mmc_phase(hw);
+ unsigned int mmc_rate, mmc_parent_rate;
+ unsigned long flags;
+ u32 value;
+ u8 delay;
+
+ /* Get the main MMC clock */
+ mmc = clk_get_parent(clk);
+ if (!mmc)
+ return -EINVAL;
+
+ /* And its rate */
+ mmc_rate = clk_get_rate(mmc);
+ if (!mmc_rate)
+ return -EINVAL;
+
+ /* Now, get the MMC parent (most likely some PLL) */
+ mmc_parent = clk_get_parent(mmc);
+ if (!mmc_parent)
+ return -EINVAL;
+
+ /* And its rate */
+ mmc_parent_rate = clk_get_rate(mmc_parent);
+ if (!mmc_parent_rate)
+ return -EINVAL;
+
+ if (degrees != 180) {
+ u16 step, mmc_div;
+
+ /* Get MMC clock divider */
+ mmc_div = mmc_parent_rate / mmc_rate;
+
+ /*
+ * We can only outphase the clocks by multiple of the
+ * PLL's period.
+ *
+ * Since the MMC clock in only a divider, and the
+ * formula to get the outphasing in degrees is deg =
+ * 360 * delta / period
+ *
+ * If we simplify this formula, we can see that the
+ * only thing that we're concerned about is the number
+ * of period we want to outphase our clock from, and
+ * the divider set by the MMC clock.
+ */
+ step = DIV_ROUND_CLOSEST(360, mmc_div);
+ delay = DIV_ROUND_CLOSEST(degrees, step);
+ } else {
+ delay = 0;
+ }
+
+ spin_lock_irqsave(phase->lock, flags);
+ value = readl(phase->reg);
+ value &= ~GENMASK(phase->offset + 3, phase->offset);
+ value |= delay << phase->offset;
+ writel(value, phase->reg);
+ spin_unlock_irqrestore(phase->lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops mmc_clk_ops = {
+ .get_phase = mmc_get_phase,
+ .set_phase = mmc_set_phase,
+};
+
+/*
+ * sunxi_mmc_setup - Common setup function for mmc module clocks
+ *
+ * The only difference between module clocks on different platforms is the
+ * width of the mux register bits and the valid values, which are passed in
+ * through struct factors_data. The phase clocks parts are identical.
+ */
+static void __init sunxi_mmc_setup(struct device_node *node,
+ const struct factors_data *data,
+ spinlock_t *lock)
+{
+ struct clk_onecell_data *clk_data;
+ const char *parent;
+ void __iomem *reg;
+ int i;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Couldn't map the %pOFn clock registers\n", node);
+ return;
+ }
+
+ clk_data = kmalloc(sizeof(*clk_data), GFP_KERNEL);
+ if (!clk_data)
+ return;
+
+ clk_data->clks = kcalloc(3, sizeof(*clk_data->clks), GFP_KERNEL);
+ if (!clk_data->clks)
+ goto err_free_data;
+
+ clk_data->clk_num = 3;
+ clk_data->clks[0] = sunxi_factors_register(node, data, lock, reg);
+ if (!clk_data->clks[0])
+ goto err_free_clks;
+
+ parent = __clk_get_name(clk_data->clks[0]);
+
+ for (i = 1; i < 3; i++) {
+ struct clk_init_data init = {
+ .num_parents = 1,
+ .parent_names = &parent,
+ .ops = &mmc_clk_ops,
+ };
+ struct mmc_phase *phase;
+
+ phase = kmalloc(sizeof(*phase), GFP_KERNEL);
+ if (!phase)
+ continue;
+
+ phase->hw.init = &init;
+ phase->reg = reg;
+ phase->lock = lock;
+
+ if (i == 1)
+ phase->offset = 8;
+ else
+ phase->offset = 20;
+
+ if (of_property_read_string_index(node, "clock-output-names",
+ i, &init.name))
+ init.name = node->name;
+
+ clk_data->clks[i] = clk_register(NULL, &phase->hw);
+ if (IS_ERR(clk_data->clks[i])) {
+ kfree(phase);
+ continue;
+ }
+ }
+
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+ return;
+
+err_free_clks:
+ kfree(clk_data->clks);
+err_free_data:
+ kfree(clk_data);
+}
+
+static DEFINE_SPINLOCK(sun4i_a10_mmc_lock);
+
+static void __init sun4i_a10_mmc_setup(struct device_node *node)
+{
+ sunxi_mmc_setup(node, &sun4i_a10_mod0_data, &sun4i_a10_mmc_lock);
+}
+CLK_OF_DECLARE(sun4i_a10_mmc, "allwinner,sun4i-a10-mmc-clk", sun4i_a10_mmc_setup);
+
+static DEFINE_SPINLOCK(sun9i_a80_mmc_lock);
+
+static void __init sun9i_a80_mmc_setup(struct device_node *node)
+{
+ sunxi_mmc_setup(node, &sun9i_a80_mod0_data, &sun9i_a80_mmc_lock);
+}
+CLK_OF_DECLARE(sun9i_a80_mmc, "allwinner,sun9i-a80-mmc-clk", sun9i_a80_mmc_setup);
diff --git a/drivers/clk/sunxi/clk-simple-gates.c b/drivers/clk/sunxi/clk-simple-gates.c
new file mode 100644
index 000000000..0399627c2
--- /dev/null
+++ b/drivers/clk/sunxi/clk-simple-gates.c
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+static DEFINE_SPINLOCK(gates_lock);
+
+static void __init sunxi_simple_gates_setup(struct device_node *node,
+ const int protected[],
+ int nprotected)
+{
+ struct clk_onecell_data *clk_data;
+ const char *clk_parent, *clk_name;
+ struct property *prop;
+ struct resource res;
+ void __iomem *clk_reg;
+ void __iomem *reg;
+ const __be32 *p;
+ int number, i = 0, j;
+ u8 clk_bit;
+ u32 index;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ clk_parent = of_clk_get_parent_name(node, 0);
+
+ clk_data = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
+ if (!clk_data)
+ goto err_unmap;
+
+ number = of_property_count_u32_elems(node, "clock-indices");
+ of_property_read_u32_index(node, "clock-indices", number - 1, &number);
+
+ clk_data->clks = kcalloc(number + 1, sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clks)
+ goto err_free_data;
+
+ of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+ of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name);
+
+ clk_reg = reg + 4 * (index / 32);
+ clk_bit = index % 32;
+
+ clk_data->clks[index] = clk_register_gate(NULL, clk_name,
+ clk_parent, 0,
+ clk_reg,
+ clk_bit,
+ 0, &gates_lock);
+ i++;
+
+ if (IS_ERR(clk_data->clks[index])) {
+ WARN_ON(true);
+ continue;
+ }
+
+ for (j = 0; j < nprotected; j++)
+ if (protected[j] == index)
+ clk_prepare_enable(clk_data->clks[index]);
+
+ }
+
+ clk_data->clk_num = number + 1;
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+ return;
+
+err_free_data:
+ kfree(clk_data);
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+
+static void __init sunxi_simple_gates_init(struct device_node *node)
+{
+ sunxi_simple_gates_setup(node, NULL, 0);
+}
+
+CLK_OF_DECLARE(sun4i_a10_gates, "allwinner,sun4i-a10-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun4i_a10_apb0, "allwinner,sun4i-a10-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun4i_a10_apb1, "allwinner,sun4i-a10-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun4i_a10_axi, "allwinner,sun4i-a10-axi-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun5i_a10s_apb0, "allwinner,sun5i-a10s-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun5i_a10s_apb1, "allwinner,sun5i-a10s-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun5i_a13_apb0, "allwinner,sun5i-a13-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun5i_a13_apb1, "allwinner,sun5i-a13-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun6i_a31_apb1, "allwinner,sun6i-a31-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun6i_a31_apb2, "allwinner,sun6i-a31-apb2-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun7i_a20_apb0, "allwinner,sun7i-a20-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun7i_a20_apb1, "allwinner,sun7i-a20-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun8i_a23_ahb1, "allwinner,sun8i-a23-ahb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun8i_a23_apb1, "allwinner,sun8i-a23-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun8i_a23_apb2, "allwinner,sun8i-a23-apb2-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun8i_a33_ahb1, "allwinner,sun8i-a33-ahb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun8i_a83t_apb0, "allwinner,sun8i-a83t-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_ahb0, "allwinner,sun9i-a80-ahb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_ahb1, "allwinner,sun9i-a80-ahb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_ahb2, "allwinner,sun9i-a80-ahb2-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_apb0, "allwinner,sun9i-a80-apb0-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_apb1, "allwinner,sun9i-a80-apb1-gates-clk",
+ sunxi_simple_gates_init);
+CLK_OF_DECLARE(sun9i_a80_apbs, "allwinner,sun9i-a80-apbs-gates-clk",
+ sunxi_simple_gates_init);
+
+static const int sun4i_a10_ahb_critical_clocks[] __initconst = {
+ 14, /* ahb_sdram */
+};
+
+static void __init sun4i_a10_ahb_init(struct device_node *node)
+{
+ sunxi_simple_gates_setup(node, sun4i_a10_ahb_critical_clocks,
+ ARRAY_SIZE(sun4i_a10_ahb_critical_clocks));
+}
+CLK_OF_DECLARE(sun4i_a10_ahb, "allwinner,sun4i-a10-ahb-gates-clk",
+ sun4i_a10_ahb_init);
+CLK_OF_DECLARE(sun5i_a10s_ahb, "allwinner,sun5i-a10s-ahb-gates-clk",
+ sun4i_a10_ahb_init);
+CLK_OF_DECLARE(sun5i_a13_ahb, "allwinner,sun5i-a13-ahb-gates-clk",
+ sun4i_a10_ahb_init);
+CLK_OF_DECLARE(sun7i_a20_ahb, "allwinner,sun7i-a20-ahb-gates-clk",
+ sun4i_a10_ahb_init);
+
+static const int sun4i_a10_dram_critical_clocks[] __initconst = {
+ 15, /* dram_output */
+};
+
+static void __init sun4i_a10_dram_init(struct device_node *node)
+{
+ sunxi_simple_gates_setup(node, sun4i_a10_dram_critical_clocks,
+ ARRAY_SIZE(sun4i_a10_dram_critical_clocks));
+}
+CLK_OF_DECLARE(sun4i_a10_dram, "allwinner,sun4i-a10-dram-gates-clk",
+ sun4i_a10_dram_init);
diff --git a/drivers/clk/sunxi/clk-sun4i-display.c b/drivers/clk/sunxi/clk-sun4i-display.c
new file mode 100644
index 000000000..35d1541be
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun4i-display.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/of_address.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+struct sun4i_a10_display_clk_data {
+ bool has_div;
+ u8 num_rst;
+ u8 parents;
+
+ u8 offset_en;
+ u8 offset_div;
+ u8 offset_mux;
+ u8 offset_rst;
+
+ u8 width_div;
+ u8 width_mux;
+
+ u32 flags;
+};
+
+struct reset_data {
+ void __iomem *reg;
+ spinlock_t *lock;
+ struct reset_controller_dev rcdev;
+ u8 offset;
+};
+
+static DEFINE_SPINLOCK(sun4i_a10_display_lock);
+
+static inline struct reset_data *rcdev_to_reset_data(struct reset_controller_dev *rcdev)
+{
+ return container_of(rcdev, struct reset_data, rcdev);
+};
+
+static int sun4i_a10_display_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_data *data = rcdev_to_reset_data(rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg & ~BIT(data->offset + id), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+
+ return 0;
+}
+
+static int sun4i_a10_display_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_data *data = rcdev_to_reset_data(rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg | BIT(data->offset + id), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+
+ return 0;
+}
+
+static int sun4i_a10_display_status(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct reset_data *data = rcdev_to_reset_data(rcdev);
+
+ return !(readl(data->reg) & BIT(data->offset + id));
+}
+
+static const struct reset_control_ops sun4i_a10_display_reset_ops = {
+ .assert = sun4i_a10_display_assert,
+ .deassert = sun4i_a10_display_deassert,
+ .status = sun4i_a10_display_status,
+};
+
+static int sun4i_a10_display_reset_xlate(struct reset_controller_dev *rcdev,
+ const struct of_phandle_args *spec)
+{
+ /* We only have a single reset signal */
+ return 0;
+}
+
+static void __init sun4i_a10_display_init(struct device_node *node,
+ const struct sun4i_a10_display_clk_data *data)
+{
+ const char *parents[4];
+ const char *clk_name = node->name;
+ struct reset_data *reset_data;
+ struct clk_divider *div = NULL;
+ struct clk_gate *gate;
+ struct resource res;
+ struct clk_mux *mux;
+ void __iomem *reg;
+ struct clk *clk;
+ int ret;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("%s: Could not map the clock registers\n", clk_name);
+ return;
+ }
+
+ ret = of_clk_parent_fill(node, parents, data->parents);
+ if (ret != data->parents) {
+ pr_err("%s: Could not retrieve the parents\n", clk_name);
+ goto unmap;
+ }
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ goto unmap;
+
+ mux->reg = reg;
+ mux->shift = data->offset_mux;
+ mux->mask = (1 << data->width_mux) - 1;
+ mux->lock = &sun4i_a10_display_lock;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto free_mux;
+
+ gate->reg = reg;
+ gate->bit_idx = data->offset_en;
+ gate->lock = &sun4i_a10_display_lock;
+
+ if (data->has_div) {
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ goto free_gate;
+
+ div->reg = reg;
+ div->shift = data->offset_div;
+ div->width = data->width_div;
+ div->lock = &sun4i_a10_display_lock;
+ }
+
+ clk = clk_register_composite(NULL, clk_name,
+ parents, data->parents,
+ &mux->hw, &clk_mux_ops,
+ data->has_div ? &div->hw : NULL,
+ data->has_div ? &clk_divider_ops : NULL,
+ &gate->hw, &clk_gate_ops,
+ data->flags);
+ if (IS_ERR(clk)) {
+ pr_err("%s: Couldn't register the clock\n", clk_name);
+ goto free_div;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret) {
+ pr_err("%s: Couldn't register DT provider\n", clk_name);
+ goto free_clk;
+ }
+
+ if (!data->num_rst)
+ return;
+
+ reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
+ if (!reset_data)
+ goto free_of_clk;
+
+ reset_data->reg = reg;
+ reset_data->offset = data->offset_rst;
+ reset_data->lock = &sun4i_a10_display_lock;
+ reset_data->rcdev.nr_resets = data->num_rst;
+ reset_data->rcdev.ops = &sun4i_a10_display_reset_ops;
+ reset_data->rcdev.of_node = node;
+
+ if (data->num_rst == 1) {
+ reset_data->rcdev.of_reset_n_cells = 0;
+ reset_data->rcdev.of_xlate = &sun4i_a10_display_reset_xlate;
+ } else {
+ reset_data->rcdev.of_reset_n_cells = 1;
+ }
+
+ if (reset_controller_register(&reset_data->rcdev)) {
+ pr_err("%s: Couldn't register the reset controller\n",
+ clk_name);
+ goto free_reset;
+ }
+
+ return;
+
+free_reset:
+ kfree(reset_data);
+free_of_clk:
+ of_clk_del_provider(node);
+free_clk:
+ clk_unregister_composite(clk);
+free_div:
+ kfree(div);
+free_gate:
+ kfree(gate);
+free_mux:
+ kfree(mux);
+unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+
+static const struct sun4i_a10_display_clk_data sun4i_a10_tcon_ch0_data __initconst = {
+ .num_rst = 2,
+ .parents = 4,
+ .offset_en = 31,
+ .offset_rst = 29,
+ .offset_mux = 24,
+ .width_mux = 2,
+ .flags = CLK_SET_RATE_PARENT,
+};
+
+static void __init sun4i_a10_tcon_ch0_setup(struct device_node *node)
+{
+ sun4i_a10_display_init(node, &sun4i_a10_tcon_ch0_data);
+}
+CLK_OF_DECLARE(sun4i_a10_tcon_ch0, "allwinner,sun4i-a10-tcon-ch0-clk",
+ sun4i_a10_tcon_ch0_setup);
+
+static const struct sun4i_a10_display_clk_data sun4i_a10_display_data __initconst = {
+ .has_div = true,
+ .num_rst = 1,
+ .parents = 3,
+ .offset_en = 31,
+ .offset_rst = 30,
+ .offset_mux = 24,
+ .offset_div = 0,
+ .width_mux = 2,
+ .width_div = 4,
+};
+
+static void __init sun4i_a10_display_setup(struct device_node *node)
+{
+ sun4i_a10_display_init(node, &sun4i_a10_display_data);
+}
+CLK_OF_DECLARE(sun4i_a10_display, "allwinner,sun4i-a10-display-clk",
+ sun4i_a10_display_setup);
diff --git a/drivers/clk/sunxi/clk-sun4i-pll3.c b/drivers/clk/sunxi/clk-sun4i-pll3.c
new file mode 100644
index 000000000..5328588fa
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun4i-pll3.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define SUN4I_A10_PLL3_GATE_BIT 31
+#define SUN4I_A10_PLL3_DIV_WIDTH 7
+#define SUN4I_A10_PLL3_DIV_SHIFT 0
+
+static DEFINE_SPINLOCK(sun4i_a10_pll3_lock);
+
+static void __init sun4i_a10_pll3_setup(struct device_node *node)
+{
+ const char *clk_name = node->name, *parent;
+ struct clk_multiplier *mult;
+ struct clk_gate *gate;
+ struct resource res;
+ void __iomem *reg;
+ struct clk *clk;
+ int ret;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+ parent = of_clk_get_parent_name(node, 0);
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("%s: Could not map the clock registers\n", clk_name);
+ return;
+ }
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto err_unmap;
+
+ gate->reg = reg;
+ gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT;
+ gate->lock = &sun4i_a10_pll3_lock;
+
+ mult = kzalloc(sizeof(*mult), GFP_KERNEL);
+ if (!mult)
+ goto err_free_gate;
+
+ mult->reg = reg;
+ mult->shift = SUN4I_A10_PLL3_DIV_SHIFT;
+ mult->width = SUN4I_A10_PLL3_DIV_WIDTH;
+ mult->lock = &sun4i_a10_pll3_lock;
+
+ clk = clk_register_composite(NULL, clk_name,
+ &parent, 1,
+ NULL, NULL,
+ &mult->hw, &clk_multiplier_ops,
+ &gate->hw, &clk_gate_ops,
+ 0);
+ if (IS_ERR(clk)) {
+ pr_err("%s: Couldn't register the clock\n", clk_name);
+ goto err_free_mult;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret) {
+ pr_err("%s: Couldn't register DT provider\n",
+ clk_name);
+ goto err_clk_unregister;
+ }
+
+ return;
+
+err_clk_unregister:
+ clk_unregister_composite(clk);
+err_free_mult:
+ kfree(mult);
+err_free_gate:
+ kfree(gate);
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+
+CLK_OF_DECLARE(sun4i_a10_pll3, "allwinner,sun4i-a10-pll3-clk",
+ sun4i_a10_pll3_setup);
diff --git a/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
new file mode 100644
index 000000000..277a240b6
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun4i-tcon-ch1.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define TCON_CH1_SCLK2_PARENTS 4
+
+#define TCON_CH1_SCLK2_GATE_BIT BIT(31)
+#define TCON_CH1_SCLK2_MUX_MASK 3
+#define TCON_CH1_SCLK2_MUX_SHIFT 24
+#define TCON_CH1_SCLK2_DIV_MASK 0xf
+#define TCON_CH1_SCLK2_DIV_SHIFT 0
+
+#define TCON_CH1_SCLK1_GATE_BIT BIT(15)
+#define TCON_CH1_SCLK1_HALF_BIT BIT(11)
+
+struct tcon_ch1_clk {
+ struct clk_hw hw;
+ spinlock_t lock;
+ void __iomem *reg;
+};
+
+#define hw_to_tclk(hw) container_of(hw, struct tcon_ch1_clk, hw)
+
+static void tcon_ch1_disable(struct clk_hw *hw)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&tclk->lock, flags);
+ reg = readl(tclk->reg);
+ reg &= ~(TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
+ writel(reg, tclk->reg);
+ spin_unlock_irqrestore(&tclk->lock, flags);
+}
+
+static int tcon_ch1_enable(struct clk_hw *hw)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&tclk->lock, flags);
+ reg = readl(tclk->reg);
+ reg |= TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT;
+ writel(reg, tclk->reg);
+ spin_unlock_irqrestore(&tclk->lock, flags);
+
+ return 0;
+}
+
+static int tcon_ch1_is_enabled(struct clk_hw *hw)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ u32 reg;
+
+ reg = readl(tclk->reg);
+ return reg & (TCON_CH1_SCLK2_GATE_BIT | TCON_CH1_SCLK1_GATE_BIT);
+}
+
+static u8 tcon_ch1_get_parent(struct clk_hw *hw)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ u32 reg;
+
+ reg = readl(tclk->reg) >> TCON_CH1_SCLK2_MUX_SHIFT;
+ reg &= reg >> TCON_CH1_SCLK2_MUX_MASK;
+
+ return reg;
+}
+
+static int tcon_ch1_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ unsigned long flags;
+ u32 reg;
+
+ spin_lock_irqsave(&tclk->lock, flags);
+ reg = readl(tclk->reg);
+ reg &= ~(TCON_CH1_SCLK2_MUX_MASK << TCON_CH1_SCLK2_MUX_SHIFT);
+ reg |= index << TCON_CH1_SCLK2_MUX_SHIFT;
+ writel(reg, tclk->reg);
+ spin_unlock_irqrestore(&tclk->lock, flags);
+
+ return 0;
+};
+
+static unsigned long tcon_ch1_calc_divider(unsigned long rate,
+ unsigned long parent_rate,
+ u8 *div,
+ bool *half)
+{
+ unsigned long best_rate = 0;
+ u8 best_m = 0, m;
+ bool is_double;
+
+ for (m = 1; m < 16; m++) {
+ u8 d;
+
+ for (d = 1; d < 3; d++) {
+ unsigned long tmp_rate;
+
+ tmp_rate = parent_rate / m / d;
+
+ if (tmp_rate > rate)
+ continue;
+
+ if (!best_rate ||
+ (rate - tmp_rate) < (rate - best_rate)) {
+ best_rate = tmp_rate;
+ best_m = m;
+ is_double = d;
+ }
+ }
+ }
+
+ if (div && half) {
+ *div = best_m;
+ *half = is_double;
+ }
+
+ return best_rate;
+}
+
+static int tcon_ch1_determine_rate(struct clk_hw *hw,
+ struct clk_rate_request *req)
+{
+ long best_rate = -EINVAL;
+ int i;
+
+ for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
+ unsigned long parent_rate;
+ unsigned long tmp_rate;
+ struct clk_hw *parent;
+
+ parent = clk_hw_get_parent_by_index(hw, i);
+ if (!parent)
+ continue;
+
+ parent_rate = clk_hw_get_rate(parent);
+
+ tmp_rate = tcon_ch1_calc_divider(req->rate, parent_rate,
+ NULL, NULL);
+
+ if (best_rate < 0 ||
+ (req->rate - tmp_rate) < (req->rate - best_rate)) {
+ best_rate = tmp_rate;
+ req->best_parent_rate = parent_rate;
+ req->best_parent_hw = parent;
+ }
+ }
+
+ if (best_rate < 0)
+ return best_rate;
+
+ req->rate = best_rate;
+ return 0;
+}
+
+static unsigned long tcon_ch1_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ u32 reg;
+
+ reg = readl(tclk->reg);
+
+ parent_rate /= (reg & TCON_CH1_SCLK2_DIV_MASK) + 1;
+
+ if (reg & TCON_CH1_SCLK1_HALF_BIT)
+ parent_rate /= 2;
+
+ return parent_rate;
+}
+
+static int tcon_ch1_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct tcon_ch1_clk *tclk = hw_to_tclk(hw);
+ unsigned long flags;
+ bool half;
+ u8 div_m;
+ u32 reg;
+
+ tcon_ch1_calc_divider(rate, parent_rate, &div_m, &half);
+
+ spin_lock_irqsave(&tclk->lock, flags);
+ reg = readl(tclk->reg);
+ reg &= ~(TCON_CH1_SCLK2_DIV_MASK | TCON_CH1_SCLK1_HALF_BIT);
+ reg |= (div_m - 1) & TCON_CH1_SCLK2_DIV_MASK;
+
+ if (half)
+ reg |= TCON_CH1_SCLK1_HALF_BIT;
+
+ writel(reg, tclk->reg);
+ spin_unlock_irqrestore(&tclk->lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops tcon_ch1_ops = {
+ .disable = tcon_ch1_disable,
+ .enable = tcon_ch1_enable,
+ .is_enabled = tcon_ch1_is_enabled,
+
+ .get_parent = tcon_ch1_get_parent,
+ .set_parent = tcon_ch1_set_parent,
+
+ .determine_rate = tcon_ch1_determine_rate,
+ .recalc_rate = tcon_ch1_recalc_rate,
+ .set_rate = tcon_ch1_set_rate,
+};
+
+static void __init tcon_ch1_setup(struct device_node *node)
+{
+ const char *parents[TCON_CH1_SCLK2_PARENTS];
+ const char *clk_name = node->name;
+ struct clk_init_data init;
+ struct tcon_ch1_clk *tclk;
+ struct resource res;
+ struct clk *clk;
+ void __iomem *reg;
+ int ret;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("%s: Could not map the clock registers\n", clk_name);
+ return;
+ }
+
+ ret = of_clk_parent_fill(node, parents, TCON_CH1_SCLK2_PARENTS);
+ if (ret != TCON_CH1_SCLK2_PARENTS) {
+ pr_err("%s Could not retrieve the parents\n", clk_name);
+ goto err_unmap;
+ }
+
+ tclk = kzalloc(sizeof(*tclk), GFP_KERNEL);
+ if (!tclk)
+ goto err_unmap;
+
+ init.name = clk_name;
+ init.ops = &tcon_ch1_ops;
+ init.parent_names = parents;
+ init.num_parents = TCON_CH1_SCLK2_PARENTS;
+ init.flags = CLK_SET_RATE_PARENT;
+
+ tclk->reg = reg;
+ tclk->hw.init = &init;
+ spin_lock_init(&tclk->lock);
+
+ clk = clk_register(NULL, &tclk->hw);
+ if (IS_ERR(clk)) {
+ pr_err("%s: Couldn't register the clock\n", clk_name);
+ goto err_free_data;
+ }
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret) {
+ pr_err("%s: Couldn't register our clock provider\n", clk_name);
+ goto err_unregister_clk;
+ }
+
+ return;
+
+err_unregister_clk:
+ clk_unregister(clk);
+err_free_data:
+ kfree(tclk);
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+
+CLK_OF_DECLARE(tcon_ch1, "allwinner,sun4i-a10-tcon-ch1-clk",
+ tcon_ch1_setup);
diff --git a/drivers/clk/sunxi/clk-sun6i-apb0-gates.c b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c
new file mode 100644
index 000000000..e4cf1180b
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ *
+ * Allwinner A31 APB0 clock gates driver
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define SUN6I_APB0_GATES_MAX_SIZE 32
+
+struct gates_data {
+ DECLARE_BITMAP(mask, SUN6I_APB0_GATES_MAX_SIZE);
+};
+
+static const struct gates_data sun6i_a31_apb0_gates __initconst = {
+ .mask = {0x7F},
+};
+
+static const struct gates_data sun8i_a23_apb0_gates __initconst = {
+ .mask = {0x5D},
+};
+
+static const struct of_device_id sun6i_a31_apb0_gates_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun6i-a31-apb0-gates-clk", .data = &sun6i_a31_apb0_gates },
+ { .compatible = "allwinner,sun8i-a23-apb0-gates-clk", .data = &sun8i_a23_apb0_gates },
+ { /* sentinel */ }
+};
+
+static int sun6i_a31_apb0_gates_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct clk_onecell_data *clk_data;
+ const struct gates_data *data;
+ const char *clk_parent;
+ const char *clk_name;
+ void __iomem *reg;
+ int ngates;
+ int i;
+ int j = 0;
+
+ if (!np)
+ return -ENODEV;
+
+ data = of_device_get_match_data(&pdev->dev);
+ if (!data)
+ return -ENODEV;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ clk_parent = of_clk_get_parent_name(np, 0);
+ if (!clk_parent)
+ return -EINVAL;
+
+ clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data),
+ GFP_KERNEL);
+ if (!clk_data)
+ return -ENOMEM;
+
+ /* Worst-case size approximation and memory allocation */
+ ngates = find_last_bit(data->mask, SUN6I_APB0_GATES_MAX_SIZE);
+ clk_data->clks = devm_kcalloc(&pdev->dev, (ngates + 1),
+ sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clks)
+ return -ENOMEM;
+
+ for_each_set_bit(i, data->mask, SUN6I_APB0_GATES_MAX_SIZE) {
+ of_property_read_string_index(np, "clock-output-names",
+ j, &clk_name);
+
+ clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name,
+ clk_parent, 0, reg, i,
+ 0, NULL);
+ WARN_ON(IS_ERR(clk_data->clks[i]));
+
+ j++;
+ }
+
+ clk_data->clk_num = ngates + 1;
+
+ return of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
+}
+
+static struct platform_driver sun6i_a31_apb0_gates_clk_driver = {
+ .driver = {
+ .name = "sun6i-a31-apb0-gates-clk",
+ .of_match_table = sun6i_a31_apb0_gates_clk_dt_ids,
+ },
+ .probe = sun6i_a31_apb0_gates_clk_probe,
+};
+builtin_platform_driver(sun6i_a31_apb0_gates_clk_driver);
diff --git a/drivers/clk/sunxi/clk-sun6i-apb0.c b/drivers/clk/sunxi/clk-sun6i-apb0.c
new file mode 100644
index 000000000..f80c67baf
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-apb0.c
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ *
+ * Allwinner A31 APB0 clock driver
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+/*
+ * The APB0 clk has a configurable divisor.
+ *
+ * We must use a clk_div_table and not a regular power of 2
+ * divisor here, because the first 2 values divide the clock
+ * by 2.
+ */
+static const struct clk_div_table sun6i_a31_apb0_divs[] = {
+ { .val = 0, .div = 2, },
+ { .val = 1, .div = 2, },
+ { .val = 2, .div = 4, },
+ { .val = 3, .div = 8, },
+ { /* sentinel */ },
+};
+
+static int sun6i_a31_apb0_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ const char *clk_name = np->name;
+ const char *clk_parent;
+ void __iomem *reg;
+ struct clk *clk;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ clk_parent = of_clk_get_parent_name(np, 0);
+ if (!clk_parent)
+ return -EINVAL;
+
+ of_property_read_string(np, "clock-output-names", &clk_name);
+
+ clk = clk_register_divider_table(&pdev->dev, clk_name, clk_parent,
+ 0, reg, 0, 2, 0, sun6i_a31_apb0_divs,
+ NULL);
+ if (IS_ERR(clk))
+ return PTR_ERR(clk);
+
+ return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+}
+
+static const struct of_device_id sun6i_a31_apb0_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun6i-a31-apb0-clk" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sun6i_a31_apb0_clk_driver = {
+ .driver = {
+ .name = "sun6i-a31-apb0-clk",
+ .of_match_table = sun6i_a31_apb0_clk_dt_ids,
+ },
+ .probe = sun6i_a31_apb0_clk_probe,
+};
+builtin_platform_driver(sun6i_a31_apb0_clk_driver);
diff --git a/drivers/clk/sunxi/clk-sun6i-ar100.c b/drivers/clk/sunxi/clk-sun6i-ar100.c
new file mode 100644
index 000000000..9f9a2cf54
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun6i-ar100.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Free Electrons
+ *
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ *
+ * Allwinner A31 AR100 clock driver
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#include "clk-factors.h"
+
+/*
+ * sun6i_get_ar100_factors - Calculates factors p, m for AR100
+ *
+ * AR100 rate is calculated as follows
+ * rate = (parent_rate >> p) / (m + 1);
+ */
+static void sun6i_get_ar100_factors(struct factors_request *req)
+{
+ unsigned long div;
+ int shift;
+
+ /* clock only divides */
+ if (req->rate > req->parent_rate)
+ req->rate = req->parent_rate;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ if (div < 32)
+ shift = 0;
+ else if (div >> 1 < 32)
+ shift = 1;
+ else if (div >> 2 < 32)
+ shift = 2;
+ else
+ shift = 3;
+
+ div >>= shift;
+
+ if (div > 32)
+ div = 32;
+
+ req->rate = (req->parent_rate >> shift) / div;
+ req->m = div - 1;
+ req->p = shift;
+}
+
+static const struct clk_factors_config sun6i_ar100_config = {
+ .mwidth = 5,
+ .mshift = 8,
+ .pwidth = 2,
+ .pshift = 4,
+};
+
+static const struct factors_data sun6i_ar100_data = {
+ .mux = 16,
+ .muxmask = GENMASK(1, 0),
+ .table = &sun6i_ar100_config,
+ .getter = sun6i_get_ar100_factors,
+};
+
+static DEFINE_SPINLOCK(sun6i_ar100_lock);
+
+static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ void __iomem *reg;
+ struct clk *clk;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ clk = sunxi_factors_register(np, &sun6i_ar100_data, &sun6i_ar100_lock,
+ reg);
+ if (!clk)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, clk);
+
+ return 0;
+}
+
+static const struct of_device_id sun6i_a31_ar100_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun6i-a31-ar100-clk" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sun6i_a31_ar100_clk_driver = {
+ .driver = {
+ .name = "sun6i-a31-ar100-clk",
+ .of_match_table = sun6i_a31_ar100_clk_dt_ids,
+ .suppress_bind_attrs = true,
+ },
+ .probe = sun6i_a31_ar100_clk_probe,
+};
+builtin_platform_driver(sun6i_a31_ar100_clk_driver);
diff --git a/drivers/clk/sunxi/clk-sun8i-apb0.c b/drivers/clk/sunxi/clk-sun8i-apb0.c
new file mode 100644
index 000000000..f605ecca8
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-apb0.c
@@ -0,0 +1,113 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2014 Chen-Yu Tsai
+ * Author: Chen-Yu Tsai <wens@csie.org>
+ *
+ * Allwinner A23 APB0 clock driver
+ *
+ * Based on clk-sun6i-apb0.c
+ * Allwinner A31 APB0 clock driver
+ *
+ * Copyright (C) 2014 Free Electrons
+ * Author: Boris BREZILLON <boris.brezillon@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+
+static struct clk *sun8i_a23_apb0_register(struct device_node *node,
+ void __iomem *reg)
+{
+ const char *clk_name = node->name;
+ const char *clk_parent;
+ struct clk *clk;
+ int ret;
+
+ clk_parent = of_clk_get_parent_name(node, 0);
+ if (!clk_parent)
+ return ERR_PTR(-EINVAL);
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ /* The A23 APB0 clock is a standard 2 bit wide divider clock */
+ clk = clk_register_divider(NULL, clk_name, clk_parent, 0, reg,
+ 0, 2, 0, NULL);
+ if (IS_ERR(clk))
+ return clk;
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret)
+ goto err_unregister;
+
+ return clk;
+
+err_unregister:
+ clk_unregister_divider(clk);
+
+ return ERR_PTR(ret);
+}
+
+static void sun8i_a23_apb0_setup(struct device_node *node)
+{
+ void __iomem *reg;
+ struct resource res;
+ struct clk *clk;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ /*
+ * This happens with clk nodes instantiated through mfd,
+ * as those do not have their resources assigned in the
+ * device tree. Do not print an error in this case.
+ */
+ if (PTR_ERR(reg) != -EINVAL)
+ pr_err("Could not get registers for a23-apb0-clk\n");
+
+ return;
+ }
+
+ clk = sun8i_a23_apb0_register(node, reg);
+ if (IS_ERR(clk))
+ goto err_unmap;
+
+ return;
+
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+CLK_OF_DECLARE_DRIVER(sun8i_a23_apb0, "allwinner,sun8i-a23-apb0-clk",
+ sun8i_a23_apb0_setup);
+
+static int sun8i_a23_apb0_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ void __iomem *reg;
+ struct clk *clk;
+
+ reg = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(reg))
+ return PTR_ERR(reg);
+
+ clk = sun8i_a23_apb0_register(np, reg);
+ return PTR_ERR_OR_ZERO(clk);
+}
+
+static const struct of_device_id sun8i_a23_apb0_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun8i-a23-apb0-clk" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sun8i_a23_apb0_clk_driver = {
+ .driver = {
+ .name = "sun8i-a23-apb0-clk",
+ .of_match_table = sun8i_a23_apb0_clk_dt_ids,
+ },
+ .probe = sun8i_a23_apb0_clk_probe,
+};
+builtin_platform_driver(sun8i_a23_apb0_clk_driver);
diff --git a/drivers/clk/sunxi/clk-sun8i-bus-gates.c b/drivers/clk/sunxi/clk-sun8i-bus-gates.c
new file mode 100644
index 000000000..b87f331f6
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-bus-gates.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2015 Jens Kuske <jenskuske@gmail.com>
+ *
+ * Based on clk-simple-gates.c, which is:
+ * Copyright 2015 Maxime Ripard
+ *
+ * Maxime Ripard <maxime.ripard@free-electrons.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+static DEFINE_SPINLOCK(gates_lock);
+
+static void __init sun8i_h3_bus_gates_init(struct device_node *node)
+{
+ static const char * const names[] = { "ahb1", "ahb2", "apb1", "apb2" };
+ enum { AHB1, AHB2, APB1, APB2, PARENT_MAX } clk_parent;
+ const char *parents[PARENT_MAX];
+ struct clk_onecell_data *clk_data;
+ const char *clk_name;
+ struct property *prop;
+ struct resource res;
+ void __iomem *clk_reg;
+ void __iomem *reg;
+ const __be32 *p;
+ int number, i;
+ u8 clk_bit;
+ int index;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ for (i = 0; i < ARRAY_SIZE(names); i++) {
+ int idx = of_property_match_string(node, "clock-names",
+ names[i]);
+ if (idx < 0)
+ return;
+
+ parents[i] = of_clk_get_parent_name(node, idx);
+ }
+
+ clk_data = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
+ if (!clk_data)
+ goto err_unmap;
+
+ number = of_property_count_u32_elems(node, "clock-indices");
+ of_property_read_u32_index(node, "clock-indices", number - 1, &number);
+
+ clk_data->clks = kcalloc(number + 1, sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clks)
+ goto err_free_data;
+
+ i = 0;
+ of_property_for_each_u32(node, "clock-indices", prop, p, index) {
+ of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name);
+
+ if (index == 17 || (index >= 29 && index <= 31))
+ clk_parent = AHB2;
+ else if (index <= 63 || index >= 128)
+ clk_parent = AHB1;
+ else if (index >= 64 && index <= 95)
+ clk_parent = APB1;
+ else if (index >= 96 && index <= 127)
+ clk_parent = APB2;
+ else {
+ WARN_ON(true);
+ continue;
+ }
+
+ clk_reg = reg + 4 * (index / 32);
+ clk_bit = index % 32;
+
+ clk_data->clks[index] = clk_register_gate(NULL, clk_name,
+ parents[clk_parent],
+ 0, clk_reg, clk_bit,
+ 0, &gates_lock);
+ i++;
+
+ if (IS_ERR(clk_data->clks[index])) {
+ WARN_ON(true);
+ continue;
+ }
+ }
+
+ clk_data->clk_num = number + 1;
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+ return;
+
+err_free_data:
+ kfree(clk_data);
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+}
+
+CLK_OF_DECLARE(sun8i_h3_bus_gates, "allwinner,sun8i-h3-bus-gates-clk",
+ sun8i_h3_bus_gates_init);
+CLK_OF_DECLARE(sun8i_a83t_bus_gates, "allwinner,sun8i-a83t-bus-gates-clk",
+ sun8i_h3_bus_gates_init);
diff --git a/drivers/clk/sunxi/clk-sun8i-mbus.c b/drivers/clk/sunxi/clk-sun8i-mbus.c
new file mode 100644
index 000000000..539ea2788
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-mbus.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2014 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/of_address.h>
+
+#define SUN8I_MBUS_ENABLE 31
+#define SUN8I_MBUS_MUX_SHIFT 24
+#define SUN8I_MBUS_MUX_MASK 0x3
+#define SUN8I_MBUS_DIV_SHIFT 0
+#define SUN8I_MBUS_DIV_WIDTH 3
+#define SUN8I_MBUS_MAX_PARENTS 4
+
+static DEFINE_SPINLOCK(sun8i_a23_mbus_lock);
+
+static void __init sun8i_a23_mbus_setup(struct device_node *node)
+{
+ int num_parents = of_clk_get_parent_count(node);
+ const char **parents;
+ const char *clk_name = node->name;
+ struct resource res;
+ struct clk_divider *div;
+ struct clk_gate *gate;
+ struct clk_mux *mux;
+ struct clk *clk;
+ void __iomem *reg;
+ int err;
+
+ parents = kcalloc(num_parents, sizeof(*parents), GFP_KERNEL);
+ if (!parents)
+ return;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for sun8i-mbus-clk\n");
+ goto err_free_parents;
+ }
+
+ div = kzalloc(sizeof(*div), GFP_KERNEL);
+ if (!div)
+ goto err_unmap;
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ goto err_free_div;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto err_free_mux;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+ of_clk_parent_fill(node, parents, num_parents);
+
+ gate->reg = reg;
+ gate->bit_idx = SUN8I_MBUS_ENABLE;
+ gate->lock = &sun8i_a23_mbus_lock;
+
+ div->reg = reg;
+ div->shift = SUN8I_MBUS_DIV_SHIFT;
+ div->width = SUN8I_MBUS_DIV_WIDTH;
+ div->lock = &sun8i_a23_mbus_lock;
+
+ mux->reg = reg;
+ mux->shift = SUN8I_MBUS_MUX_SHIFT;
+ mux->mask = SUN8I_MBUS_MUX_MASK;
+ mux->lock = &sun8i_a23_mbus_lock;
+
+ /* The MBUS clocks needs to be always enabled */
+ clk = clk_register_composite(NULL, clk_name, parents, num_parents,
+ &mux->hw, &clk_mux_ops,
+ &div->hw, &clk_divider_ops,
+ &gate->hw, &clk_gate_ops,
+ CLK_IS_CRITICAL);
+ if (IS_ERR(clk))
+ goto err_free_gate;
+
+ err = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (err)
+ goto err_unregister_clk;
+
+ kfree(parents); /* parents is deep copied */
+
+ return;
+
+err_unregister_clk:
+ /* TODO: The composite clock stuff will leak a bit here. */
+ clk_unregister(clk);
+err_free_gate:
+ kfree(gate);
+err_free_mux:
+ kfree(mux);
+err_free_div:
+ kfree(div);
+err_unmap:
+ iounmap(reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+err_free_parents:
+ kfree(parents);
+}
+CLK_OF_DECLARE(sun8i_a23_mbus, "allwinner,sun8i-a23-mbus-clk", sun8i_a23_mbus_setup);
diff --git a/drivers/clk/sunxi/clk-sun9i-core.c b/drivers/clk/sunxi/clk-sun9i-core.c
new file mode 100644
index 000000000..d93c7a53c
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun9i-core.c
@@ -0,0 +1,282 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2014 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/log2.h>
+
+#include "clk-factors.h"
+
+
+/*
+ * sun9i_a80_get_pll4_factors() - calculates n, p, m factors for PLL4
+ * PLL4 rate is calculated as follows
+ * rate = (parent_rate * n >> p) / (m + 1);
+ * parent_rate is always 24MHz
+ *
+ * p and m are named div1 and div2 in Allwinner's SDK
+ */
+
+static void sun9i_a80_get_pll4_factors(struct factors_request *req)
+{
+ int n;
+ int m = 1;
+ int p = 1;
+
+ /* Normalize value to a 6 MHz multiple (24 MHz / 4) */
+ n = DIV_ROUND_UP(req->rate, 6000000);
+
+ /* If n is too large switch to steps of 12 MHz */
+ if (n > 255) {
+ m = 0;
+ n = (n + 1) / 2;
+ }
+
+ /* If n is still too large switch to steps of 24 MHz */
+ if (n > 255) {
+ p = 0;
+ n = (n + 1) / 2;
+ }
+
+ /* n must be between 12 and 255 */
+ if (n > 255)
+ n = 255;
+ else if (n < 12)
+ n = 12;
+
+ req->rate = ((24000000 * n) >> p) / (m + 1);
+ req->n = n;
+ req->m = m;
+ req->p = p;
+}
+
+static const struct clk_factors_config sun9i_a80_pll4_config = {
+ .mshift = 18,
+ .mwidth = 1,
+ .nshift = 8,
+ .nwidth = 8,
+ .pshift = 16,
+ .pwidth = 1,
+};
+
+static const struct factors_data sun9i_a80_pll4_data __initconst = {
+ .enable = 31,
+ .table = &sun9i_a80_pll4_config,
+ .getter = sun9i_a80_get_pll4_factors,
+};
+
+static DEFINE_SPINLOCK(sun9i_a80_pll4_lock);
+
+static void __init sun9i_a80_pll4_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a80-pll4-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ sunxi_factors_register(node, &sun9i_a80_pll4_data,
+ &sun9i_a80_pll4_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_pll4, "allwinner,sun9i-a80-pll4-clk", sun9i_a80_pll4_setup);
+
+
+/*
+ * sun9i_a80_get_gt_factors() - calculates m factor for GT
+ * GT rate is calculated as follows
+ * rate = parent_rate / (m + 1);
+ */
+
+static void sun9i_a80_get_gt_factors(struct factors_request *req)
+{
+ u32 div;
+
+ if (req->parent_rate < req->rate)
+ req->rate = req->parent_rate;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ /* maximum divider is 4 */
+ if (div > 4)
+ div = 4;
+
+ req->rate = req->parent_rate / div;
+ req->m = div;
+}
+
+static const struct clk_factors_config sun9i_a80_gt_config = {
+ .mshift = 0,
+ .mwidth = 2,
+};
+
+static const struct factors_data sun9i_a80_gt_data __initconst = {
+ .mux = 24,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun9i_a80_gt_config,
+ .getter = sun9i_a80_get_gt_factors,
+};
+
+static DEFINE_SPINLOCK(sun9i_a80_gt_lock);
+
+static void __init sun9i_a80_gt_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a80-gt-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ /* The GT bus clock needs to be always enabled */
+ sunxi_factors_register_critical(node, &sun9i_a80_gt_data,
+ &sun9i_a80_gt_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_gt, "allwinner,sun9i-a80-gt-clk", sun9i_a80_gt_setup);
+
+
+/*
+ * sun9i_a80_get_ahb_factors() - calculates p factor for AHB0/1/2
+ * AHB rate is calculated as follows
+ * rate = parent_rate >> p;
+ */
+
+static void sun9i_a80_get_ahb_factors(struct factors_request *req)
+{
+ u32 _p;
+
+ if (req->parent_rate < req->rate)
+ req->rate = req->parent_rate;
+
+ _p = order_base_2(DIV_ROUND_UP(req->parent_rate, req->rate));
+
+ /* maximum p is 3 */
+ if (_p > 3)
+ _p = 3;
+
+ req->rate = req->parent_rate >> _p;
+ req->p = _p;
+}
+
+static const struct clk_factors_config sun9i_a80_ahb_config = {
+ .pshift = 0,
+ .pwidth = 2,
+};
+
+static const struct factors_data sun9i_a80_ahb_data __initconst = {
+ .mux = 24,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun9i_a80_ahb_config,
+ .getter = sun9i_a80_get_ahb_factors,
+};
+
+static DEFINE_SPINLOCK(sun9i_a80_ahb_lock);
+
+static void __init sun9i_a80_ahb_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a80-ahb-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ sunxi_factors_register(node, &sun9i_a80_ahb_data,
+ &sun9i_a80_ahb_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_ahb, "allwinner,sun9i-a80-ahb-clk", sun9i_a80_ahb_setup);
+
+
+static const struct factors_data sun9i_a80_apb0_data __initconst = {
+ .mux = 24,
+ .muxmask = BIT(0),
+ .table = &sun9i_a80_ahb_config,
+ .getter = sun9i_a80_get_ahb_factors,
+};
+
+static DEFINE_SPINLOCK(sun9i_a80_apb0_lock);
+
+static void __init sun9i_a80_apb0_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a80-apb0-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ sunxi_factors_register(node, &sun9i_a80_apb0_data,
+ &sun9i_a80_apb0_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_apb0, "allwinner,sun9i-a80-apb0-clk", sun9i_a80_apb0_setup);
+
+
+/*
+ * sun9i_a80_get_apb1_factors() - calculates m, p factors for APB1
+ * APB1 rate is calculated as follows
+ * rate = (parent_rate >> p) / (m + 1);
+ */
+
+static void sun9i_a80_get_apb1_factors(struct factors_request *req)
+{
+ u32 div;
+
+ if (req->parent_rate < req->rate)
+ req->rate = req->parent_rate;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ /* Highest possible divider is 256 (p = 3, m = 31) */
+ if (div > 256)
+ div = 256;
+
+ req->p = order_base_2(div);
+ req->m = (req->parent_rate >> req->p) - 1;
+ req->rate = (req->parent_rate >> req->p) / (req->m + 1);
+}
+
+static const struct clk_factors_config sun9i_a80_apb1_config = {
+ .mshift = 0,
+ .mwidth = 5,
+ .pshift = 16,
+ .pwidth = 2,
+};
+
+static const struct factors_data sun9i_a80_apb1_data __initconst = {
+ .mux = 24,
+ .muxmask = BIT(0),
+ .table = &sun9i_a80_apb1_config,
+ .getter = sun9i_a80_get_apb1_factors,
+};
+
+static DEFINE_SPINLOCK(sun9i_a80_apb1_lock);
+
+static void __init sun9i_a80_apb1_setup(struct device_node *node)
+{
+ void __iomem *reg;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg)) {
+ pr_err("Could not get registers for a80-apb1-clk: %pOFn\n",
+ node);
+ return;
+ }
+
+ sunxi_factors_register(node, &sun9i_a80_apb1_data,
+ &sun9i_a80_apb1_lock, reg);
+}
+CLK_OF_DECLARE(sun9i_a80_apb1, "allwinner,sun9i-a80-apb1-clk", sun9i_a80_apb1_setup);
diff --git a/drivers/clk/sunxi/clk-sun9i-cpus.c b/drivers/clk/sunxi/clk-sun9i-cpus.c
new file mode 100644
index 000000000..01255d827
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun9i-cpus.c
@@ -0,0 +1,242 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2015 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ *
+ * Allwinner A80 CPUS clock driver
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+static DEFINE_SPINLOCK(sun9i_a80_cpus_lock);
+
+/**
+ * sun9i_a80_cpus_clk_setup() - Setup function for a80 cpus composite clk
+ */
+
+#define SUN9I_CPUS_MAX_PARENTS 4
+#define SUN9I_CPUS_MUX_PARENT_PLL4 3
+#define SUN9I_CPUS_MUX_SHIFT 16
+#define SUN9I_CPUS_MUX_MASK GENMASK(17, 16)
+#define SUN9I_CPUS_MUX_GET_PARENT(reg) ((reg & SUN9I_CPUS_MUX_MASK) >> \
+ SUN9I_CPUS_MUX_SHIFT)
+
+#define SUN9I_CPUS_DIV_SHIFT 4
+#define SUN9I_CPUS_DIV_MASK GENMASK(5, 4)
+#define SUN9I_CPUS_DIV_GET(reg) ((reg & SUN9I_CPUS_DIV_MASK) >> \
+ SUN9I_CPUS_DIV_SHIFT)
+#define SUN9I_CPUS_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_DIV_MASK) | \
+ (div << SUN9I_CPUS_DIV_SHIFT))
+#define SUN9I_CPUS_PLL4_DIV_SHIFT 8
+#define SUN9I_CPUS_PLL4_DIV_MASK GENMASK(12, 8)
+#define SUN9I_CPUS_PLL4_DIV_GET(reg) ((reg & SUN9I_CPUS_PLL4_DIV_MASK) >> \
+ SUN9I_CPUS_PLL4_DIV_SHIFT)
+#define SUN9I_CPUS_PLL4_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_PLL4_DIV_MASK) | \
+ (div << SUN9I_CPUS_PLL4_DIV_SHIFT))
+
+struct sun9i_a80_cpus_clk {
+ struct clk_hw hw;
+ void __iomem *reg;
+};
+
+#define to_sun9i_a80_cpus_clk(_hw) container_of(_hw, struct sun9i_a80_cpus_clk, hw)
+
+static unsigned long sun9i_a80_cpus_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
+ unsigned long rate;
+ u32 reg;
+
+ /* Fetch the register value */
+ reg = readl(cpus->reg);
+
+ /* apply pre-divider first if parent is pll4 */
+ if (SUN9I_CPUS_MUX_GET_PARENT(reg) == SUN9I_CPUS_MUX_PARENT_PLL4)
+ parent_rate /= SUN9I_CPUS_PLL4_DIV_GET(reg) + 1;
+
+ /* clk divider */
+ rate = parent_rate / (SUN9I_CPUS_DIV_GET(reg) + 1);
+
+ return rate;
+}
+
+static long sun9i_a80_cpus_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
+ u8 parent, unsigned long parent_rate)
+{
+ u8 div, pre_div = 1;
+
+ /*
+ * clock can only divide, so we will never be able to achieve
+ * frequencies higher than the parent frequency
+ */
+ if (parent_rate && rate > parent_rate)
+ rate = parent_rate;
+
+ div = DIV_ROUND_UP(parent_rate, rate);
+
+ /* calculate pre-divider if parent is pll4 */
+ if (parent == SUN9I_CPUS_MUX_PARENT_PLL4 && div > 4) {
+ /* pre-divider is 1 ~ 32 */
+ if (div < 32) {
+ pre_div = div;
+ div = 1;
+ } else if (div < 64) {
+ pre_div = DIV_ROUND_UP(div, 2);
+ div = 2;
+ } else if (div < 96) {
+ pre_div = DIV_ROUND_UP(div, 3);
+ div = 3;
+ } else {
+ pre_div = DIV_ROUND_UP(div, 4);
+ div = 4;
+ }
+ }
+
+ /* we were asked to pass back divider values */
+ if (divp) {
+ *divp = div - 1;
+ *pre_divp = pre_div - 1;
+ }
+
+ return parent_rate / pre_div / div;
+}
+
+static int sun9i_a80_cpus_clk_determine_rate(struct clk_hw *clk,
+ struct clk_rate_request *req)
+{
+ struct clk_hw *parent, *best_parent = NULL;
+ int i, num_parents;
+ unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
+ unsigned long rate = req->rate;
+
+ /* find the parent that can help provide the fastest rate <= rate */
+ num_parents = clk_hw_get_num_parents(clk);
+ for (i = 0; i < num_parents; i++) {
+ parent = clk_hw_get_parent_by_index(clk, i);
+ if (!parent)
+ continue;
+ if (clk_hw_get_flags(clk) & CLK_SET_RATE_PARENT)
+ parent_rate = clk_hw_round_rate(parent, rate);
+ else
+ parent_rate = clk_hw_get_rate(parent);
+
+ child_rate = sun9i_a80_cpus_clk_round(rate, NULL, NULL, i,
+ parent_rate);
+
+ if (child_rate <= rate && child_rate > best_child_rate) {
+ best_parent = parent;
+ best = parent_rate;
+ best_child_rate = child_rate;
+ }
+ }
+
+ if (!best_parent)
+ return -EINVAL;
+
+ req->best_parent_hw = best_parent;
+ req->best_parent_rate = best;
+ req->rate = best_child_rate;
+
+ return 0;
+}
+
+static int sun9i_a80_cpus_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long parent_rate)
+{
+ struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
+ unsigned long flags;
+ u8 div, pre_div, parent;
+ u32 reg;
+
+ spin_lock_irqsave(&sun9i_a80_cpus_lock, flags);
+
+ reg = readl(cpus->reg);
+
+ /* need to know which parent is used to apply pre-divider */
+ parent = SUN9I_CPUS_MUX_GET_PARENT(reg);
+ sun9i_a80_cpus_clk_round(rate, &div, &pre_div, parent, parent_rate);
+
+ reg = SUN9I_CPUS_DIV_SET(reg, div);
+ reg = SUN9I_CPUS_PLL4_DIV_SET(reg, pre_div);
+ writel(reg, cpus->reg);
+
+ spin_unlock_irqrestore(&sun9i_a80_cpus_lock, flags);
+
+ return 0;
+}
+
+static const struct clk_ops sun9i_a80_cpus_clk_ops = {
+ .determine_rate = sun9i_a80_cpus_clk_determine_rate,
+ .recalc_rate = sun9i_a80_cpus_clk_recalc_rate,
+ .set_rate = sun9i_a80_cpus_clk_set_rate,
+};
+
+static void sun9i_a80_cpus_setup(struct device_node *node)
+{
+ const char *clk_name = node->name;
+ const char *parents[SUN9I_CPUS_MAX_PARENTS];
+ struct resource res;
+ struct sun9i_a80_cpus_clk *cpus;
+ struct clk_mux *mux;
+ struct clk *clk;
+ int ret;
+
+ cpus = kzalloc(sizeof(*cpus), GFP_KERNEL);
+ if (!cpus)
+ return;
+
+ cpus->reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(cpus->reg))
+ goto err_free_cpus;
+
+ of_property_read_string(node, "clock-output-names", &clk_name);
+
+ /* we have a mux, we will have >1 parents */
+ ret = of_clk_parent_fill(node, parents, SUN9I_CPUS_MAX_PARENTS);
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ goto err_unmap;
+
+ /* set up clock properties */
+ mux->reg = cpus->reg;
+ mux->shift = SUN9I_CPUS_MUX_SHIFT;
+ /* un-shifted mask is what mux_clk expects */
+ mux->mask = SUN9I_CPUS_MUX_MASK >> SUN9I_CPUS_MUX_SHIFT;
+ mux->lock = &sun9i_a80_cpus_lock;
+
+ clk = clk_register_composite(NULL, clk_name, parents, ret,
+ &mux->hw, &clk_mux_ops,
+ &cpus->hw, &sun9i_a80_cpus_clk_ops,
+ NULL, NULL, 0);
+ if (IS_ERR(clk))
+ goto err_free_mux;
+
+ ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
+ if (ret)
+ goto err_unregister;
+
+ return;
+
+err_unregister:
+ clk_unregister(clk);
+err_free_mux:
+ kfree(mux);
+err_unmap:
+ iounmap(cpus->reg);
+ of_address_to_resource(node, 0, &res);
+ release_mem_region(res.start, resource_size(&res));
+err_free_cpus:
+ kfree(cpus);
+}
+CLK_OF_DECLARE(sun9i_a80_cpus, "allwinner,sun9i-a80-cpus-clk",
+ sun9i_a80_cpus_setup);
diff --git a/drivers/clk/sunxi/clk-sun9i-mmc.c b/drivers/clk/sunxi/clk-sun9i-mmc.c
new file mode 100644
index 000000000..636bcf243
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun9i-mmc.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2015 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/reset.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define SUN9I_MMC_WIDTH 4
+
+#define SUN9I_MMC_GATE_BIT 16
+#define SUN9I_MMC_RESET_BIT 18
+
+struct sun9i_mmc_clk_data {
+ spinlock_t lock;
+ void __iomem *membase;
+ struct clk *clk;
+ struct reset_control *reset;
+ struct clk_onecell_data clk_data;
+ struct reset_controller_dev rcdev;
+};
+
+static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct sun9i_mmc_clk_data *data = container_of(rcdev,
+ struct sun9i_mmc_clk_data,
+ rcdev);
+ unsigned long flags;
+ void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id;
+ u32 val;
+
+ clk_prepare_enable(data->clk);
+ spin_lock_irqsave(&data->lock, flags);
+
+ val = readl(reg);
+ writel(val & ~BIT(SUN9I_MMC_RESET_BIT), reg);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct sun9i_mmc_clk_data *data = container_of(rcdev,
+ struct sun9i_mmc_clk_data,
+ rcdev);
+ unsigned long flags;
+ void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id;
+ u32 val;
+
+ clk_prepare_enable(data->clk);
+ spin_lock_irqsave(&data->lock, flags);
+
+ val = readl(reg);
+ writel(val | BIT(SUN9I_MMC_RESET_BIT), reg);
+
+ spin_unlock_irqrestore(&data->lock, flags);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static int sun9i_mmc_reset_reset(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ sun9i_mmc_reset_assert(rcdev, id);
+ udelay(10);
+ sun9i_mmc_reset_deassert(rcdev, id);
+
+ return 0;
+}
+
+static const struct reset_control_ops sun9i_mmc_reset_ops = {
+ .assert = sun9i_mmc_reset_assert,
+ .deassert = sun9i_mmc_reset_deassert,
+ .reset = sun9i_mmc_reset_reset,
+};
+
+static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct sun9i_mmc_clk_data *data;
+ struct clk_onecell_data *clk_data;
+ const char *clk_name = np->name;
+ const char *clk_parent;
+ struct resource *r;
+ int count, i, ret;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ spin_lock_init(&data->lock);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r)
+ return -EINVAL;
+ /* one clock/reset pair per word */
+ count = DIV_ROUND_UP((resource_size(r)), SUN9I_MMC_WIDTH);
+ data->membase = devm_ioremap_resource(&pdev->dev, r);
+ if (IS_ERR(data->membase))
+ return PTR_ERR(data->membase);
+
+ clk_data = &data->clk_data;
+ clk_data->clk_num = count;
+ clk_data->clks = devm_kcalloc(&pdev->dev, count, sizeof(struct clk *),
+ GFP_KERNEL);
+ if (!clk_data->clks)
+ return -ENOMEM;
+
+ data->clk = devm_clk_get(&pdev->dev, NULL);
+ if (IS_ERR(data->clk)) {
+ dev_err(&pdev->dev, "Could not get clock\n");
+ return PTR_ERR(data->clk);
+ }
+
+ data->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+ if (IS_ERR(data->reset)) {
+ dev_err(&pdev->dev, "Could not get reset control\n");
+ return PTR_ERR(data->reset);
+ }
+
+ ret = reset_control_deassert(data->reset);
+ if (ret) {
+ dev_err(&pdev->dev, "Reset deassert err %d\n", ret);
+ return ret;
+ }
+
+ clk_parent = __clk_get_name(data->clk);
+ for (i = 0; i < count; i++) {
+ of_property_read_string_index(np, "clock-output-names",
+ i, &clk_name);
+
+ clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name,
+ clk_parent, 0,
+ data->membase + SUN9I_MMC_WIDTH * i,
+ SUN9I_MMC_GATE_BIT, 0,
+ &data->lock);
+
+ if (IS_ERR(clk_data->clks[i])) {
+ ret = PTR_ERR(clk_data->clks[i]);
+ goto err_clk_register;
+ }
+ }
+
+ ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data);
+ if (ret)
+ goto err_clk_provider;
+
+ data->rcdev.owner = THIS_MODULE;
+ data->rcdev.nr_resets = count;
+ data->rcdev.ops = &sun9i_mmc_reset_ops;
+ data->rcdev.of_node = pdev->dev.of_node;
+
+ ret = reset_controller_register(&data->rcdev);
+ if (ret)
+ goto err_rc_reg;
+
+ platform_set_drvdata(pdev, data);
+
+ return 0;
+
+err_rc_reg:
+ of_clk_del_provider(np);
+
+err_clk_provider:
+ for (i = 0; i < count; i++)
+ clk_unregister(clk_data->clks[i]);
+
+err_clk_register:
+ reset_control_assert(data->reset);
+
+ return ret;
+}
+
+static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = {
+ { .compatible = "allwinner,sun9i-a80-mmc-config-clk" },
+ { /* sentinel */ }
+};
+
+static struct platform_driver sun9i_a80_mmc_config_clk_driver = {
+ .driver = {
+ .name = "sun9i-a80-mmc-config-clk",
+ .suppress_bind_attrs = true,
+ .of_match_table = sun9i_a80_mmc_config_clk_dt_ids,
+ },
+ .probe = sun9i_a80_mmc_config_clk_probe,
+};
+builtin_platform_driver(sun9i_a80_mmc_config_clk_driver);
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
new file mode 100644
index 000000000..5fe7049ea
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -0,0 +1,1167 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/log2.h>
+
+#include "clk-factors.h"
+
+static DEFINE_SPINLOCK(clk_lock);
+
+/* Maximum number of parents our clocks have */
+#define SUNXI_MAX_PARENTS 5
+
+/*
+ * sun4i_get_pll1_factors() - calculates n, k, m, p factors for PLL1
+ * PLL1 rate is calculated as follows
+ * rate = (parent_rate * n * (k + 1) >> p) / (m + 1);
+ * parent_rate is always 24Mhz
+ */
+
+static void sun4i_get_pll1_factors(struct factors_request *req)
+{
+ u8 div;
+
+ /* Normalize value to a 6M multiple */
+ div = req->rate / 6000000;
+ req->rate = 6000000 * div;
+
+ /* m is always zero for pll1 */
+ req->m = 0;
+
+ /* k is 1 only on these cases */
+ if (req->rate >= 768000000 || req->rate == 42000000 ||
+ req->rate == 54000000)
+ req->k = 1;
+ else
+ req->k = 0;
+
+ /* p will be 3 for divs under 10 */
+ if (div < 10)
+ req->p = 3;
+
+ /* p will be 2 for divs between 10 - 20 and odd divs under 32 */
+ else if (div < 20 || (div < 32 && (div & 1)))
+ req->p = 2;
+
+ /* p will be 1 for even divs under 32, divs under 40 and odd pairs
+ * of divs between 40-62 */
+ else if (div < 40 || (div < 64 && (div & 2)))
+ req->p = 1;
+
+ /* any other entries have p = 0 */
+ else
+ req->p = 0;
+
+ /* calculate a suitable n based on k and p */
+ div <<= req->p;
+ div /= (req->k + 1);
+ req->n = div / 4;
+}
+
+/*
+ * sun6i_a31_get_pll1_factors() - calculates n, k and m factors for PLL1
+ * PLL1 rate is calculated as follows
+ * rate = parent_rate * (n + 1) * (k + 1) / (m + 1);
+ * parent_rate should always be 24MHz
+ */
+static void sun6i_a31_get_pll1_factors(struct factors_request *req)
+{
+ /*
+ * We can operate only on MHz, this will make our life easier
+ * later.
+ */
+ u32 freq_mhz = req->rate / 1000000;
+ u32 parent_freq_mhz = req->parent_rate / 1000000;
+
+ /*
+ * Round down the frequency to the closest multiple of either
+ * 6 or 16
+ */
+ u32 round_freq_6 = rounddown(freq_mhz, 6);
+ u32 round_freq_16 = round_down(freq_mhz, 16);
+
+ if (round_freq_6 > round_freq_16)
+ freq_mhz = round_freq_6;
+ else
+ freq_mhz = round_freq_16;
+
+ req->rate = freq_mhz * 1000000;
+
+ /* If the frequency is a multiple of 32 MHz, k is always 3 */
+ if (!(freq_mhz % 32))
+ req->k = 3;
+ /* If the frequency is a multiple of 9 MHz, k is always 2 */
+ else if (!(freq_mhz % 9))
+ req->k = 2;
+ /* If the frequency is a multiple of 8 MHz, k is always 1 */
+ else if (!(freq_mhz % 8))
+ req->k = 1;
+ /* Otherwise, we don't use the k factor */
+ else
+ req->k = 0;
+
+ /*
+ * If the frequency is a multiple of 2 but not a multiple of
+ * 3, m is 3. This is the first time we use 6 here, yet we
+ * will use it on several other places.
+ * We use this number because it's the lowest frequency we can
+ * generate (with n = 0, k = 0, m = 3), so every other frequency
+ * somehow relates to this frequency.
+ */
+ if ((freq_mhz % 6) == 2 || (freq_mhz % 6) == 4)
+ req->m = 2;
+ /*
+ * If the frequency is a multiple of 6MHz, but the factor is
+ * odd, m will be 3
+ */
+ else if ((freq_mhz / 6) & 1)
+ req->m = 3;
+ /* Otherwise, we end up with m = 1 */
+ else
+ req->m = 1;
+
+ /* Calculate n thanks to the above factors we already got */
+ req->n = freq_mhz * (req->m + 1) / ((req->k + 1) * parent_freq_mhz)
+ - 1;
+
+ /*
+ * If n end up being outbound, and that we can still decrease
+ * m, do it.
+ */
+ if ((req->n + 1) > 31 && (req->m + 1) > 1) {
+ req->n = (req->n + 1) / 2 - 1;
+ req->m = (req->m + 1) / 2 - 1;
+ }
+}
+
+/*
+ * sun8i_a23_get_pll1_factors() - calculates n, k, m, p factors for PLL1
+ * PLL1 rate is calculated as follows
+ * rate = (parent_rate * (n + 1) * (k + 1) >> p) / (m + 1);
+ * parent_rate is always 24Mhz
+ */
+
+static void sun8i_a23_get_pll1_factors(struct factors_request *req)
+{
+ u8 div;
+
+ /* Normalize value to a 6M multiple */
+ div = req->rate / 6000000;
+ req->rate = 6000000 * div;
+
+ /* m is always zero for pll1 */
+ req->m = 0;
+
+ /* k is 1 only on these cases */
+ if (req->rate >= 768000000 || req->rate == 42000000 ||
+ req->rate == 54000000)
+ req->k = 1;
+ else
+ req->k = 0;
+
+ /* p will be 2 for divs under 20 and odd divs under 32 */
+ if (div < 20 || (div < 32 && (div & 1)))
+ req->p = 2;
+
+ /* p will be 1 for even divs under 32, divs under 40 and odd pairs
+ * of divs between 40-62 */
+ else if (div < 40 || (div < 64 && (div & 2)))
+ req->p = 1;
+
+ /* any other entries have p = 0 */
+ else
+ req->p = 0;
+
+ /* calculate a suitable n based on k and p */
+ div <<= req->p;
+ div /= (req->k + 1);
+ req->n = div / 4 - 1;
+}
+
+/*
+ * sun4i_get_pll5_factors() - calculates n, k factors for PLL5
+ * PLL5 rate is calculated as follows
+ * rate = parent_rate * n * (k + 1)
+ * parent_rate is always 24Mhz
+ */
+
+static void sun4i_get_pll5_factors(struct factors_request *req)
+{
+ u8 div;
+
+ /* Normalize value to a parent_rate multiple (24M) */
+ div = req->rate / req->parent_rate;
+ req->rate = req->parent_rate * div;
+
+ if (div < 31)
+ req->k = 0;
+ else if (div / 2 < 31)
+ req->k = 1;
+ else if (div / 3 < 31)
+ req->k = 2;
+ else
+ req->k = 3;
+
+ req->n = DIV_ROUND_UP(div, (req->k + 1));
+}
+
+/*
+ * sun6i_a31_get_pll6_factors() - calculates n, k factors for A31 PLL6x2
+ * PLL6x2 rate is calculated as follows
+ * rate = parent_rate * (n + 1) * (k + 1)
+ * parent_rate is always 24Mhz
+ */
+
+static void sun6i_a31_get_pll6_factors(struct factors_request *req)
+{
+ u8 div;
+
+ /* Normalize value to a parent_rate multiple (24M) */
+ div = req->rate / req->parent_rate;
+ req->rate = req->parent_rate * div;
+
+ req->k = div / 32;
+ if (req->k > 3)
+ req->k = 3;
+
+ req->n = DIV_ROUND_UP(div, (req->k + 1)) - 1;
+}
+
+/*
+ * sun5i_a13_get_ahb_factors() - calculates m, p factors for AHB
+ * AHB rate is calculated as follows
+ * rate = parent_rate >> p
+ */
+
+static void sun5i_a13_get_ahb_factors(struct factors_request *req)
+{
+ u32 div;
+
+ /* divide only */
+ if (req->parent_rate < req->rate)
+ req->rate = req->parent_rate;
+
+ /*
+ * user manual says valid speed is 8k ~ 276M, but tests show it
+ * can work at speeds up to 300M, just after reparenting to pll6
+ */
+ if (req->rate < 8000)
+ req->rate = 8000;
+ if (req->rate > 300000000)
+ req->rate = 300000000;
+
+ div = order_base_2(DIV_ROUND_UP(req->parent_rate, req->rate));
+
+ /* p = 0 ~ 3 */
+ if (div > 3)
+ div = 3;
+
+ req->rate = req->parent_rate >> div;
+
+ req->p = div;
+}
+
+#define SUN6I_AHB1_PARENT_PLL6 3
+
+/*
+ * sun6i_a31_get_ahb_factors() - calculates m, p factors for AHB
+ * AHB rate is calculated as follows
+ * rate = parent_rate >> p
+ *
+ * if parent is pll6, then
+ * parent_rate = pll6 rate / (m + 1)
+ */
+
+static void sun6i_get_ahb1_factors(struct factors_request *req)
+{
+ u8 div, calcp, calcm = 1;
+
+ /*
+ * clock can only divide, so we will never be able to achieve
+ * frequencies higher than the parent frequency
+ */
+ if (req->parent_rate && req->rate > req->parent_rate)
+ req->rate = req->parent_rate;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ /* calculate pre-divider if parent is pll6 */
+ if (req->parent_index == SUN6I_AHB1_PARENT_PLL6) {
+ if (div < 4)
+ calcp = 0;
+ else if (div / 2 < 4)
+ calcp = 1;
+ else if (div / 4 < 4)
+ calcp = 2;
+ else
+ calcp = 3;
+
+ calcm = DIV_ROUND_UP(div, 1 << calcp);
+ } else {
+ calcp = __roundup_pow_of_two(div);
+ calcp = calcp > 3 ? 3 : calcp;
+ }
+
+ req->rate = (req->parent_rate / calcm) >> calcp;
+ req->p = calcp;
+ req->m = calcm - 1;
+}
+
+/*
+ * sun6i_ahb1_recalc() - calculates AHB clock rate from m, p factors and
+ * parent index
+ */
+static void sun6i_ahb1_recalc(struct factors_request *req)
+{
+ req->rate = req->parent_rate;
+
+ /* apply pre-divider first if parent is pll6 */
+ if (req->parent_index == SUN6I_AHB1_PARENT_PLL6)
+ req->rate /= req->m + 1;
+
+ /* clk divider */
+ req->rate >>= req->p;
+}
+
+/*
+ * sun4i_get_apb1_factors() - calculates m, p factors for APB1
+ * APB1 rate is calculated as follows
+ * rate = (parent_rate >> p) / (m + 1);
+ */
+
+static void sun4i_get_apb1_factors(struct factors_request *req)
+{
+ u8 calcm, calcp;
+ int div;
+
+ if (req->parent_rate < req->rate)
+ req->rate = req->parent_rate;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ /* Invalid rate! */
+ if (div > 32)
+ return;
+
+ if (div <= 4)
+ calcp = 0;
+ else if (div <= 8)
+ calcp = 1;
+ else if (div <= 16)
+ calcp = 2;
+ else
+ calcp = 3;
+
+ calcm = (div >> calcp) - 1;
+
+ req->rate = (req->parent_rate >> calcp) / (calcm + 1);
+ req->m = calcm;
+ req->p = calcp;
+}
+
+
+
+
+/*
+ * sun7i_a20_get_out_factors() - calculates m, p factors for CLK_OUT_A/B
+ * CLK_OUT rate is calculated as follows
+ * rate = (parent_rate >> p) / (m + 1);
+ */
+
+static void sun7i_a20_get_out_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;
+
+ div = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ if (div < 32)
+ calcp = 0;
+ else if (div / 2 < 32)
+ calcp = 1;
+ else if (div / 4 < 32)
+ calcp = 2;
+ else
+ calcp = 3;
+
+ calcm = DIV_ROUND_UP(div, 1 << calcp);
+
+ req->rate = (req->parent_rate >> calcp) / calcm;
+ req->m = calcm - 1;
+ req->p = calcp;
+}
+
+/*
+ * sunxi_factors_clk_setup() - Setup function for factor clocks
+ */
+
+static const struct clk_factors_config sun4i_pll1_config = {
+ .nshift = 8,
+ .nwidth = 5,
+ .kshift = 4,
+ .kwidth = 2,
+ .mshift = 0,
+ .mwidth = 2,
+ .pshift = 16,
+ .pwidth = 2,
+};
+
+static const struct clk_factors_config sun6i_a31_pll1_config = {
+ .nshift = 8,
+ .nwidth = 5,
+ .kshift = 4,
+ .kwidth = 2,
+ .mshift = 0,
+ .mwidth = 2,
+ .n_start = 1,
+};
+
+static const struct clk_factors_config sun8i_a23_pll1_config = {
+ .nshift = 8,
+ .nwidth = 5,
+ .kshift = 4,
+ .kwidth = 2,
+ .mshift = 0,
+ .mwidth = 2,
+ .pshift = 16,
+ .pwidth = 2,
+ .n_start = 1,
+};
+
+static const struct clk_factors_config sun4i_pll5_config = {
+ .nshift = 8,
+ .nwidth = 5,
+ .kshift = 4,
+ .kwidth = 2,
+};
+
+static const struct clk_factors_config sun6i_a31_pll6_config = {
+ .nshift = 8,
+ .nwidth = 5,
+ .kshift = 4,
+ .kwidth = 2,
+ .n_start = 1,
+};
+
+static const struct clk_factors_config sun5i_a13_ahb_config = {
+ .pshift = 4,
+ .pwidth = 2,
+};
+
+static const struct clk_factors_config sun6i_ahb1_config = {
+ .mshift = 6,
+ .mwidth = 2,
+ .pshift = 4,
+ .pwidth = 2,
+};
+
+static const struct clk_factors_config sun4i_apb1_config = {
+ .mshift = 0,
+ .mwidth = 5,
+ .pshift = 16,
+ .pwidth = 2,
+};
+
+/* user manual says "n" but it's really "p" */
+static const struct clk_factors_config sun7i_a20_out_config = {
+ .mshift = 8,
+ .mwidth = 5,
+ .pshift = 20,
+ .pwidth = 2,
+};
+
+static const struct factors_data sun4i_pll1_data __initconst = {
+ .enable = 31,
+ .table = &sun4i_pll1_config,
+ .getter = sun4i_get_pll1_factors,
+};
+
+static const struct factors_data sun6i_a31_pll1_data __initconst = {
+ .enable = 31,
+ .table = &sun6i_a31_pll1_config,
+ .getter = sun6i_a31_get_pll1_factors,
+};
+
+static const struct factors_data sun8i_a23_pll1_data __initconst = {
+ .enable = 31,
+ .table = &sun8i_a23_pll1_config,
+ .getter = sun8i_a23_get_pll1_factors,
+};
+
+static const struct factors_data sun7i_a20_pll4_data __initconst = {
+ .enable = 31,
+ .table = &sun4i_pll5_config,
+ .getter = sun4i_get_pll5_factors,
+};
+
+static const struct factors_data sun4i_pll5_data __initconst = {
+ .enable = 31,
+ .table = &sun4i_pll5_config,
+ .getter = sun4i_get_pll5_factors,
+};
+
+static const struct factors_data sun6i_a31_pll6_data __initconst = {
+ .enable = 31,
+ .table = &sun6i_a31_pll6_config,
+ .getter = sun6i_a31_get_pll6_factors,
+};
+
+static const struct factors_data sun5i_a13_ahb_data __initconst = {
+ .mux = 6,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun5i_a13_ahb_config,
+ .getter = sun5i_a13_get_ahb_factors,
+};
+
+static const struct factors_data sun6i_ahb1_data __initconst = {
+ .mux = 12,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun6i_ahb1_config,
+ .getter = sun6i_get_ahb1_factors,
+ .recalc = sun6i_ahb1_recalc,
+};
+
+static const struct factors_data sun4i_apb1_data __initconst = {
+ .mux = 24,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun4i_apb1_config,
+ .getter = sun4i_get_apb1_factors,
+};
+
+static const struct factors_data sun7i_a20_out_data __initconst = {
+ .enable = 31,
+ .mux = 24,
+ .muxmask = BIT(1) | BIT(0),
+ .table = &sun7i_a20_out_config,
+ .getter = sun7i_a20_get_out_factors,
+};
+
+static struct clk * __init sunxi_factors_clk_setup(struct device_node *node,
+ const struct factors_data *data)
+{
+ void __iomem *reg;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not get registers for factors-clk: %pOFn\n",
+ node);
+ return NULL;
+ }
+
+ return sunxi_factors_register(node, data, &clk_lock, reg);
+}
+
+static void __init sun4i_pll1_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun4i_pll1_data);
+}
+CLK_OF_DECLARE(sun4i_pll1, "allwinner,sun4i-a10-pll1-clk",
+ sun4i_pll1_clk_setup);
+
+static void __init sun6i_pll1_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun6i_a31_pll1_data);
+}
+CLK_OF_DECLARE(sun6i_pll1, "allwinner,sun6i-a31-pll1-clk",
+ sun6i_pll1_clk_setup);
+
+static void __init sun8i_pll1_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun8i_a23_pll1_data);
+}
+CLK_OF_DECLARE(sun8i_pll1, "allwinner,sun8i-a23-pll1-clk",
+ sun8i_pll1_clk_setup);
+
+static void __init sun7i_pll4_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun7i_a20_pll4_data);
+}
+CLK_OF_DECLARE(sun7i_pll4, "allwinner,sun7i-a20-pll4-clk",
+ sun7i_pll4_clk_setup);
+
+static void __init sun5i_ahb_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun5i_a13_ahb_data);
+}
+CLK_OF_DECLARE(sun5i_ahb, "allwinner,sun5i-a13-ahb-clk",
+ sun5i_ahb_clk_setup);
+
+static void __init sun6i_ahb1_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun6i_ahb1_data);
+}
+CLK_OF_DECLARE(sun6i_a31_ahb1, "allwinner,sun6i-a31-ahb1-clk",
+ sun6i_ahb1_clk_setup);
+
+static void __init sun4i_apb1_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun4i_apb1_data);
+}
+CLK_OF_DECLARE(sun4i_apb1, "allwinner,sun4i-a10-apb1-clk",
+ sun4i_apb1_clk_setup);
+
+static void __init sun7i_out_clk_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun7i_a20_out_data);
+}
+CLK_OF_DECLARE(sun7i_out, "allwinner,sun7i-a20-out-clk",
+ sun7i_out_clk_setup);
+
+
+/*
+ * sunxi_mux_clk_setup() - Setup function for muxes
+ */
+
+#define SUNXI_MUX_GATE_WIDTH 2
+
+struct mux_data {
+ u8 shift;
+};
+
+static const struct mux_data sun4i_cpu_mux_data __initconst = {
+ .shift = 16,
+};
+
+static const struct mux_data sun6i_a31_ahb1_mux_data __initconst = {
+ .shift = 12,
+};
+
+static const struct mux_data sun8i_h3_ahb2_mux_data __initconst = {
+ .shift = 0,
+};
+
+static struct clk * __init sunxi_mux_clk_setup(struct device_node *node,
+ const struct mux_data *data,
+ unsigned long flags)
+{
+ struct clk *clk;
+ const char *clk_name = node->name;
+ const char *parents[SUNXI_MAX_PARENTS];
+ void __iomem *reg;
+ int i;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not map registers for mux-clk: %pOF\n", node);
+ return NULL;
+ }
+
+ i = of_clk_parent_fill(node, parents, SUNXI_MAX_PARENTS);
+ if (of_property_read_string(node, "clock-output-names", &clk_name)) {
+ pr_err("%s: could not read clock-output-names from \"%pOF\"\n",
+ __func__, node);
+ goto out_unmap;
+ }
+
+ clk = clk_register_mux(NULL, clk_name, parents, i,
+ CLK_SET_RATE_PARENT | flags, reg,
+ data->shift, SUNXI_MUX_GATE_WIDTH,
+ 0, &clk_lock);
+
+ if (IS_ERR(clk)) {
+ pr_err("%s: failed to register mux clock %s: %ld\n", __func__,
+ clk_name, PTR_ERR(clk));
+ goto out_unmap;
+ }
+
+ if (of_clk_add_provider(node, of_clk_src_simple_get, clk)) {
+ pr_err("%s: failed to add clock provider for %s\n",
+ __func__, clk_name);
+ clk_unregister_divider(clk);
+ goto out_unmap;
+ }
+
+ return clk;
+out_unmap:
+ iounmap(reg);
+ return NULL;
+}
+
+static void __init sun4i_cpu_clk_setup(struct device_node *node)
+{
+ /* Protect CPU clock */
+ sunxi_mux_clk_setup(node, &sun4i_cpu_mux_data, CLK_IS_CRITICAL);
+}
+CLK_OF_DECLARE(sun4i_cpu, "allwinner,sun4i-a10-cpu-clk",
+ sun4i_cpu_clk_setup);
+
+static void __init sun6i_ahb1_mux_clk_setup(struct device_node *node)
+{
+ sunxi_mux_clk_setup(node, &sun6i_a31_ahb1_mux_data, 0);
+}
+CLK_OF_DECLARE(sun6i_ahb1_mux, "allwinner,sun6i-a31-ahb1-mux-clk",
+ sun6i_ahb1_mux_clk_setup);
+
+static void __init sun8i_ahb2_clk_setup(struct device_node *node)
+{
+ sunxi_mux_clk_setup(node, &sun8i_h3_ahb2_mux_data, 0);
+}
+CLK_OF_DECLARE(sun8i_ahb2, "allwinner,sun8i-h3-ahb2-clk",
+ sun8i_ahb2_clk_setup);
+
+
+/*
+ * sunxi_divider_clk_setup() - Setup function for simple divider clocks
+ */
+
+struct div_data {
+ u8 shift;
+ u8 pow;
+ u8 width;
+ const struct clk_div_table *table;
+};
+
+static const struct div_data sun4i_axi_data __initconst = {
+ .shift = 0,
+ .pow = 0,
+ .width = 2,
+};
+
+static const struct clk_div_table sun8i_a23_axi_table[] __initconst = {
+ { .val = 0, .div = 1 },
+ { .val = 1, .div = 2 },
+ { .val = 2, .div = 3 },
+ { .val = 3, .div = 4 },
+ { .val = 4, .div = 4 },
+ { .val = 5, .div = 4 },
+ { .val = 6, .div = 4 },
+ { .val = 7, .div = 4 },
+ { } /* sentinel */
+};
+
+static const struct div_data sun8i_a23_axi_data __initconst = {
+ .width = 3,
+ .table = sun8i_a23_axi_table,
+};
+
+static const struct div_data sun4i_ahb_data __initconst = {
+ .shift = 4,
+ .pow = 1,
+ .width = 2,
+};
+
+static const struct clk_div_table sun4i_apb0_table[] __initconst = {
+ { .val = 0, .div = 2 },
+ { .val = 1, .div = 2 },
+ { .val = 2, .div = 4 },
+ { .val = 3, .div = 8 },
+ { } /* sentinel */
+};
+
+static const struct div_data sun4i_apb0_data __initconst = {
+ .shift = 8,
+ .pow = 1,
+ .width = 2,
+ .table = sun4i_apb0_table,
+};
+
+static void __init sunxi_divider_clk_setup(struct device_node *node,
+ const struct div_data *data)
+{
+ struct clk *clk;
+ const char *clk_name = node->name;
+ const char *clk_parent;
+ void __iomem *reg;
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not map registers for mux-clk: %pOF\n", node);
+ return;
+ }
+
+ clk_parent = of_clk_get_parent_name(node, 0);
+
+ if (of_property_read_string(node, "clock-output-names", &clk_name)) {
+ pr_err("%s: could not read clock-output-names from \"%pOF\"\n",
+ __func__, node);
+ goto out_unmap;
+ }
+
+ clk = clk_register_divider_table(NULL, clk_name, clk_parent, 0,
+ reg, data->shift, data->width,
+ data->pow ? CLK_DIVIDER_POWER_OF_TWO : 0,
+ data->table, &clk_lock);
+ if (IS_ERR(clk)) {
+ pr_err("%s: failed to register divider clock %s: %ld\n",
+ __func__, clk_name, PTR_ERR(clk));
+ goto out_unmap;
+ }
+
+ if (of_clk_add_provider(node, of_clk_src_simple_get, clk)) {
+ pr_err("%s: failed to add clock provider for %s\n",
+ __func__, clk_name);
+ goto out_unregister;
+ }
+
+ if (clk_register_clkdev(clk, clk_name, NULL)) {
+ of_clk_del_provider(node);
+ goto out_unregister;
+ }
+
+ return;
+out_unregister:
+ clk_unregister_divider(clk);
+
+out_unmap:
+ iounmap(reg);
+}
+
+static void __init sun4i_ahb_clk_setup(struct device_node *node)
+{
+ sunxi_divider_clk_setup(node, &sun4i_ahb_data);
+}
+CLK_OF_DECLARE(sun4i_ahb, "allwinner,sun4i-a10-ahb-clk",
+ sun4i_ahb_clk_setup);
+
+static void __init sun4i_apb0_clk_setup(struct device_node *node)
+{
+ sunxi_divider_clk_setup(node, &sun4i_apb0_data);
+}
+CLK_OF_DECLARE(sun4i_apb0, "allwinner,sun4i-a10-apb0-clk",
+ sun4i_apb0_clk_setup);
+
+static void __init sun4i_axi_clk_setup(struct device_node *node)
+{
+ sunxi_divider_clk_setup(node, &sun4i_axi_data);
+}
+CLK_OF_DECLARE(sun4i_axi, "allwinner,sun4i-a10-axi-clk",
+ sun4i_axi_clk_setup);
+
+static void __init sun8i_axi_clk_setup(struct device_node *node)
+{
+ sunxi_divider_clk_setup(node, &sun8i_a23_axi_data);
+}
+CLK_OF_DECLARE(sun8i_axi, "allwinner,sun8i-a23-axi-clk",
+ sun8i_axi_clk_setup);
+
+
+
+/*
+ * sunxi_gates_clk_setup() - Setup function for leaf gates on clocks
+ */
+
+#define SUNXI_GATES_MAX_SIZE 64
+
+struct gates_data {
+ DECLARE_BITMAP(mask, SUNXI_GATES_MAX_SIZE);
+};
+
+/*
+ * sunxi_divs_clk_setup() helper data
+ */
+
+#define SUNXI_DIVS_MAX_QTY 4
+#define SUNXI_DIVISOR_WIDTH 2
+
+struct divs_data {
+ const struct factors_data *factors; /* data for the factor clock */
+ int ndivs; /* number of outputs */
+ /*
+ * List of outputs. Refer to the diagram for sunxi_divs_clk_setup():
+ * self or base factor clock refers to the output from the pll
+ * itself. The remaining refer to fixed or configurable divider
+ * outputs.
+ */
+ struct {
+ u8 self; /* is it the base factor clock? (only one) */
+ u8 fixed; /* is it a fixed divisor? if not... */
+ struct clk_div_table *table; /* is it a table based divisor? */
+ u8 shift; /* otherwise it's a normal divisor with this shift */
+ u8 pow; /* is it power-of-two based? */
+ u8 gate; /* is it independently gateable? */
+ bool critical;
+ } div[SUNXI_DIVS_MAX_QTY];
+};
+
+static struct clk_div_table pll6_sata_tbl[] = {
+ { .val = 0, .div = 6, },
+ { .val = 1, .div = 12, },
+ { .val = 2, .div = 18, },
+ { .val = 3, .div = 24, },
+ { } /* sentinel */
+};
+
+static const struct divs_data pll5_divs_data __initconst = {
+ .factors = &sun4i_pll5_data,
+ .ndivs = 2,
+ .div = {
+ /* Protect PLL5_DDR */
+ { .shift = 0, .pow = 0, .critical = true }, /* M, DDR */
+ { .shift = 16, .pow = 1, }, /* P, other */
+ /* No output for the base factor clock */
+ }
+};
+
+static const struct divs_data pll6_divs_data __initconst = {
+ .factors = &sun4i_pll5_data,
+ .ndivs = 4,
+ .div = {
+ { .shift = 0, .table = pll6_sata_tbl, .gate = 14 }, /* M, SATA */
+ { .fixed = 2 }, /* P, other */
+ { .self = 1 }, /* base factor clock, 2x */
+ { .fixed = 4 }, /* pll6 / 4, used as ahb input */
+ }
+};
+
+static const struct divs_data sun6i_a31_pll6_divs_data __initconst = {
+ .factors = &sun6i_a31_pll6_data,
+ .ndivs = 2,
+ .div = {
+ { .fixed = 2 }, /* normal output */
+ { .self = 1 }, /* base factor clock, 2x */
+ }
+};
+
+/*
+ * sunxi_divs_clk_setup() - Setup function for leaf divisors on clocks
+ *
+ * These clocks look something like this
+ * ________________________
+ * | ___divisor 1---|----> to consumer
+ * parent >--| pll___/___divisor 2---|----> to consumer
+ * | \_______________|____> to consumer
+ * |________________________|
+ */
+
+static struct clk ** __init sunxi_divs_clk_setup(struct device_node *node,
+ const struct divs_data *data)
+{
+ struct clk_onecell_data *clk_data;
+ const char *parent;
+ const char *clk_name;
+ struct clk **clks, *pclk;
+ struct clk_hw *gate_hw, *rate_hw;
+ const struct clk_ops *rate_ops;
+ struct clk_gate *gate = NULL;
+ struct clk_fixed_factor *fix_factor;
+ struct clk_divider *divider;
+ struct factors_data factors = *data->factors;
+ char *derived_name = NULL;
+ void __iomem *reg;
+ int ndivs = SUNXI_DIVS_MAX_QTY, i = 0;
+ int flags, clkflags;
+
+ /* if number of children known, use it */
+ if (data->ndivs)
+ ndivs = data->ndivs;
+
+ /* Try to find a name for base factor clock */
+ for (i = 0; i < ndivs; i++) {
+ if (data->div[i].self) {
+ of_property_read_string_index(node, "clock-output-names",
+ i, &factors.name);
+ break;
+ }
+ }
+ /* If we don't have a .self clk use the first output-name up to '_' */
+ if (factors.name == NULL) {
+ char *endp;
+
+ of_property_read_string_index(node, "clock-output-names",
+ 0, &clk_name);
+ endp = strchr(clk_name, '_');
+ if (endp) {
+ derived_name = kstrndup(clk_name, endp - clk_name,
+ GFP_KERNEL);
+ if (!derived_name)
+ return NULL;
+ factors.name = derived_name;
+ } else {
+ factors.name = clk_name;
+ }
+ }
+
+ /* Set up factor clock that we will be dividing */
+ pclk = sunxi_factors_clk_setup(node, &factors);
+ if (!pclk)
+ return NULL;
+
+ parent = __clk_get_name(pclk);
+ kfree(derived_name);
+
+ reg = of_iomap(node, 0);
+ if (!reg) {
+ pr_err("Could not map registers for divs-clk: %pOF\n", node);
+ return NULL;
+ }
+
+ clk_data = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
+ if (!clk_data)
+ goto out_unmap;
+
+ clks = kcalloc(ndivs, sizeof(*clks), GFP_KERNEL);
+ if (!clks)
+ goto free_clkdata;
+
+ clk_data->clks = clks;
+
+ /* It's not a good idea to have automatic reparenting changing
+ * our RAM clock! */
+ clkflags = !strcmp("pll5", parent) ? 0 : CLK_SET_RATE_PARENT;
+
+ for (i = 0; i < ndivs; i++) {
+ if (of_property_read_string_index(node, "clock-output-names",
+ i, &clk_name) != 0)
+ break;
+
+ /* If this is the base factor clock, only update clks */
+ if (data->div[i].self) {
+ clk_data->clks[i] = pclk;
+ continue;
+ }
+
+ gate_hw = NULL;
+ rate_hw = NULL;
+ rate_ops = NULL;
+
+ /* If this leaf clock can be gated, create a gate */
+ if (data->div[i].gate) {
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ goto free_clks;
+
+ gate->reg = reg;
+ gate->bit_idx = data->div[i].gate;
+ gate->lock = &clk_lock;
+
+ gate_hw = &gate->hw;
+ }
+
+ /* Leaves can be fixed or configurable divisors */
+ if (data->div[i].fixed) {
+ fix_factor = kzalloc(sizeof(*fix_factor), GFP_KERNEL);
+ if (!fix_factor)
+ goto free_gate;
+
+ fix_factor->mult = 1;
+ fix_factor->div = data->div[i].fixed;
+
+ rate_hw = &fix_factor->hw;
+ rate_ops = &clk_fixed_factor_ops;
+ } else {
+ divider = kzalloc(sizeof(*divider), GFP_KERNEL);
+ if (!divider)
+ goto free_gate;
+
+ flags = data->div[i].pow ? CLK_DIVIDER_POWER_OF_TWO : 0;
+
+ divider->reg = reg;
+ divider->shift = data->div[i].shift;
+ divider->width = SUNXI_DIVISOR_WIDTH;
+ divider->flags = flags;
+ divider->lock = &clk_lock;
+ divider->table = data->div[i].table;
+
+ rate_hw = &divider->hw;
+ rate_ops = &clk_divider_ops;
+ }
+
+ /* Wrap the (potential) gate and the divisor on a composite
+ * clock to unify them */
+ clks[i] = clk_register_composite(NULL, clk_name, &parent, 1,
+ NULL, NULL,
+ rate_hw, rate_ops,
+ gate_hw, &clk_gate_ops,
+ clkflags |
+ (data->div[i].critical ?
+ CLK_IS_CRITICAL : 0));
+
+ WARN_ON(IS_ERR(clk_data->clks[i]));
+ }
+
+ /* Adjust to the real max */
+ clk_data->clk_num = i;
+
+ if (of_clk_add_provider(node, of_clk_src_onecell_get, clk_data)) {
+ pr_err("%s: failed to add clock provider for %s\n",
+ __func__, clk_name);
+ goto free_gate;
+ }
+
+ return clks;
+free_gate:
+ kfree(gate);
+free_clks:
+ kfree(clks);
+free_clkdata:
+ kfree(clk_data);
+out_unmap:
+ iounmap(reg);
+ return NULL;
+}
+
+static void __init sun4i_pll5_clk_setup(struct device_node *node)
+{
+ sunxi_divs_clk_setup(node, &pll5_divs_data);
+}
+CLK_OF_DECLARE(sun4i_pll5, "allwinner,sun4i-a10-pll5-clk",
+ sun4i_pll5_clk_setup);
+
+static void __init sun4i_pll6_clk_setup(struct device_node *node)
+{
+ sunxi_divs_clk_setup(node, &pll6_divs_data);
+}
+CLK_OF_DECLARE(sun4i_pll6, "allwinner,sun4i-a10-pll6-clk",
+ sun4i_pll6_clk_setup);
+
+static void __init sun6i_pll6_clk_setup(struct device_node *node)
+{
+ sunxi_divs_clk_setup(node, &sun6i_a31_pll6_divs_data);
+}
+CLK_OF_DECLARE(sun6i_pll6, "allwinner,sun6i-a31-pll6-clk",
+ sun6i_pll6_clk_setup);
+
+/*
+ * sun6i display
+ *
+ * rate = parent_rate / (m + 1);
+ */
+static void sun6i_display_factors(struct factors_request *req)
+{
+ u8 m;
+
+ if (req->rate > req->parent_rate)
+ req->rate = req->parent_rate;
+
+ m = DIV_ROUND_UP(req->parent_rate, req->rate);
+
+ req->rate = req->parent_rate / m;
+ req->m = m - 1;
+}
+
+static const struct clk_factors_config sun6i_display_config = {
+ .mshift = 0,
+ .mwidth = 4,
+};
+
+static const struct factors_data sun6i_display_data __initconst = {
+ .enable = 31,
+ .mux = 24,
+ .muxmask = BIT(2) | BIT(1) | BIT(0),
+ .table = &sun6i_display_config,
+ .getter = sun6i_display_factors,
+};
+
+static void __init sun6i_display_setup(struct device_node *node)
+{
+ sunxi_factors_clk_setup(node, &sun6i_display_data);
+}
+CLK_OF_DECLARE(sun6i_display, "allwinner,sun6i-a31-display-clk",
+ sun6i_display_setup);
diff --git a/drivers/clk/sunxi/clk-usb.c b/drivers/clk/sunxi/clk-usb.c
new file mode 100644
index 000000000..5460218f3
--- /dev/null
+++ b/drivers/clk/sunxi/clk-usb.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright 2013-2015 Emilio López
+ *
+ * Emilio López <emilio@elopez.com.ar>
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/reset-controller.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+
+/*
+ * sunxi_usb_reset... - reset bits in usb clk registers handling
+ */
+
+struct usb_reset_data {
+ void __iomem *reg;
+ spinlock_t *lock;
+ struct clk *clk;
+ struct reset_controller_dev rcdev;
+};
+
+static int sunxi_usb_reset_assert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct usb_reset_data *data = container_of(rcdev,
+ struct usb_reset_data,
+ rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ clk_prepare_enable(data->clk);
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg & ~BIT(id), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static int sunxi_usb_reset_deassert(struct reset_controller_dev *rcdev,
+ unsigned long id)
+{
+ struct usb_reset_data *data = container_of(rcdev,
+ struct usb_reset_data,
+ rcdev);
+ unsigned long flags;
+ u32 reg;
+
+ clk_prepare_enable(data->clk);
+ spin_lock_irqsave(data->lock, flags);
+
+ reg = readl(data->reg);
+ writel(reg | BIT(id), data->reg);
+
+ spin_unlock_irqrestore(data->lock, flags);
+ clk_disable_unprepare(data->clk);
+
+ return 0;
+}
+
+static const struct reset_control_ops sunxi_usb_reset_ops = {
+ .assert = sunxi_usb_reset_assert,
+ .deassert = sunxi_usb_reset_deassert,
+};
+
+/**
+ * sunxi_usb_clk_setup() - Setup function for usb gate clocks
+ */
+
+#define SUNXI_USB_MAX_SIZE 32
+
+struct usb_clk_data {
+ u32 clk_mask;
+ u32 reset_mask;
+ bool reset_needs_clk;
+};
+
+static void __init sunxi_usb_clk_setup(struct device_node *node,
+ const struct usb_clk_data *data,
+ spinlock_t *lock)
+{
+ struct clk_onecell_data *clk_data;
+ struct usb_reset_data *reset_data;
+ const char *clk_parent;
+ const char *clk_name;
+ void __iomem *reg;
+ int qty;
+ int i = 0;
+ int j = 0;
+
+ reg = of_io_request_and_map(node, 0, of_node_full_name(node));
+ if (IS_ERR(reg))
+ return;
+
+ clk_parent = of_clk_get_parent_name(node, 0);
+ if (!clk_parent)
+ return;
+
+ /* Worst-case size approximation and memory allocation */
+ qty = find_last_bit((unsigned long *)&data->clk_mask,
+ SUNXI_USB_MAX_SIZE);
+
+ clk_data = kmalloc(sizeof(struct clk_onecell_data), GFP_KERNEL);
+ if (!clk_data)
+ return;
+
+ clk_data->clks = kcalloc(qty + 1, sizeof(struct clk *), GFP_KERNEL);
+ if (!clk_data->clks) {
+ kfree(clk_data);
+ return;
+ }
+
+ for_each_set_bit(i, (unsigned long *)&data->clk_mask,
+ SUNXI_USB_MAX_SIZE) {
+ of_property_read_string_index(node, "clock-output-names",
+ j, &clk_name);
+ clk_data->clks[i] = clk_register_gate(NULL, clk_name,
+ clk_parent, 0,
+ reg, i, 0, lock);
+ WARN_ON(IS_ERR(clk_data->clks[i]));
+
+ j++;
+ }
+
+ /* Adjust to the real max */
+ clk_data->clk_num = i;
+
+ of_clk_add_provider(node, of_clk_src_onecell_get, clk_data);
+
+ /* Register a reset controller for usb with reset bits */
+ if (data->reset_mask == 0)
+ return;
+
+ reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL);
+ if (!reset_data)
+ return;
+
+ if (data->reset_needs_clk) {
+ reset_data->clk = of_clk_get(node, 0);
+ if (IS_ERR(reset_data->clk)) {
+ pr_err("Could not get clock for reset controls\n");
+ kfree(reset_data);
+ return;
+ }
+ }
+
+ reset_data->reg = reg;
+ reset_data->lock = lock;
+ reset_data->rcdev.nr_resets = __fls(data->reset_mask) + 1;
+ reset_data->rcdev.ops = &sunxi_usb_reset_ops;
+ reset_data->rcdev.of_node = node;
+ reset_controller_register(&reset_data->rcdev);
+}
+
+static const struct usb_clk_data sun4i_a10_usb_clk_data __initconst = {
+ .clk_mask = BIT(8) | BIT(7) | BIT(6),
+ .reset_mask = BIT(2) | BIT(1) | BIT(0),
+};
+
+static DEFINE_SPINLOCK(sun4i_a10_usb_lock);
+
+static void __init sun4i_a10_usb_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun4i_a10_usb_clk_data, &sun4i_a10_usb_lock);
+}
+CLK_OF_DECLARE(sun4i_a10_usb, "allwinner,sun4i-a10-usb-clk", sun4i_a10_usb_setup);
+
+static const struct usb_clk_data sun5i_a13_usb_clk_data __initconst = {
+ .clk_mask = BIT(8) | BIT(6),
+ .reset_mask = BIT(1) | BIT(0),
+};
+
+static void __init sun5i_a13_usb_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun5i_a13_usb_clk_data, &sun4i_a10_usb_lock);
+}
+CLK_OF_DECLARE(sun5i_a13_usb, "allwinner,sun5i-a13-usb-clk", sun5i_a13_usb_setup);
+
+static const struct usb_clk_data sun6i_a31_usb_clk_data __initconst = {
+ .clk_mask = BIT(18) | BIT(17) | BIT(16) | BIT(10) | BIT(9) | BIT(8),
+ .reset_mask = BIT(2) | BIT(1) | BIT(0),
+};
+
+static void __init sun6i_a31_usb_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun6i_a31_usb_clk_data, &sun4i_a10_usb_lock);
+}
+CLK_OF_DECLARE(sun6i_a31_usb, "allwinner,sun6i-a31-usb-clk", sun6i_a31_usb_setup);
+
+static const struct usb_clk_data sun8i_a23_usb_clk_data __initconst = {
+ .clk_mask = BIT(16) | BIT(11) | BIT(10) | BIT(9) | BIT(8),
+ .reset_mask = BIT(2) | BIT(1) | BIT(0),
+};
+
+static void __init sun8i_a23_usb_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun8i_a23_usb_clk_data, &sun4i_a10_usb_lock);
+}
+CLK_OF_DECLARE(sun8i_a23_usb, "allwinner,sun8i-a23-usb-clk", sun8i_a23_usb_setup);
+
+static const struct usb_clk_data sun8i_h3_usb_clk_data __initconst = {
+ .clk_mask = BIT(19) | BIT(18) | BIT(17) | BIT(16) |
+ BIT(11) | BIT(10) | BIT(9) | BIT(8),
+ .reset_mask = BIT(3) | BIT(2) | BIT(1) | BIT(0),
+};
+
+static void __init sun8i_h3_usb_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun8i_h3_usb_clk_data, &sun4i_a10_usb_lock);
+}
+CLK_OF_DECLARE(sun8i_h3_usb, "allwinner,sun8i-h3-usb-clk", sun8i_h3_usb_setup);
+
+static const struct usb_clk_data sun9i_a80_usb_mod_data __initconst = {
+ .clk_mask = BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1),
+ .reset_mask = BIT(19) | BIT(18) | BIT(17),
+ .reset_needs_clk = 1,
+};
+
+static DEFINE_SPINLOCK(a80_usb_mod_lock);
+
+static void __init sun9i_a80_usb_mod_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun9i_a80_usb_mod_data, &a80_usb_mod_lock);
+}
+CLK_OF_DECLARE(sun9i_a80_usb_mod, "allwinner,sun9i-a80-usb-mod-clk", sun9i_a80_usb_mod_setup);
+
+static const struct usb_clk_data sun9i_a80_usb_phy_data __initconst = {
+ .clk_mask = BIT(10) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1),
+ .reset_mask = BIT(21) | BIT(20) | BIT(19) | BIT(18) | BIT(17),
+ .reset_needs_clk = 1,
+};
+
+static DEFINE_SPINLOCK(a80_usb_phy_lock);
+
+static void __init sun9i_a80_usb_phy_setup(struct device_node *node)
+{
+ sunxi_usb_clk_setup(node, &sun9i_a80_usb_phy_data, &a80_usb_phy_lock);
+}
+CLK_OF_DECLARE(sun9i_a80_usb_phy, "allwinner,sun9i-a80-usb-phy-clk", sun9i_a80_usb_phy_setup);