summaryrefslogtreecommitdiff
path: root/drivers/clk/sunxi
diff options
context:
space:
mode:
authorMichael Turquette <mturquette@baylibre.com>2015-12-23 23:23:18 -0800
committerMichael Turquette <mturquette@baylibre.com>2015-12-23 23:23:18 -0800
commitcf87a88f519777999bf5374809b34827bb4ab5dd (patch)
treeaf42fa20565a33db269faa81cc4c5d1183657723 /drivers/clk/sunxi
parenta915e30dd26ea5f3cc2e2c044aba38ee5973d3fa (diff)
parentfee3103ac3c0aeab52b337903f5af3cd3b3c02d8 (diff)
Merge tag 'sunxi-clocks-for-4.5' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into clk-next
Allwinner clocks changes for 4.5 Clock patches for the Allwinner SoCs: - H3 clocks - A10/A20 Video Engine clocks - DRAM gates - A80 special CPU clock
Diffstat (limited to 'drivers/clk/sunxi')
-rw-r--r--drivers/clk/sunxi/Makefile5
-rw-r--r--drivers/clk/sunxi/clk-a10-ve.c171
-rw-r--r--drivers/clk/sunxi/clk-simple-gates.c14
-rw-r--r--drivers/clk/sunxi/clk-sun8i-apb0.c80
-rw-r--r--drivers/clk/sunxi/clk-sun8i-bus-gates.c112
-rw-r--r--drivers/clk/sunxi/clk-sun9i-cpus.c240
-rw-r--r--drivers/clk/sunxi/clk-sunxi.c6
-rw-r--r--drivers/clk/sunxi/clk-usb.c12
8 files changed, 628 insertions, 12 deletions
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile
index cb4c299214ce..3fd7901d48e4 100644
--- a/drivers/clk/sunxi/Makefile
+++ b/drivers/clk/sunxi/Makefile
@@ -7,14 +7,19 @@ obj-y += clk-a10-codec.o
obj-y += clk-a10-hosc.o
obj-y += clk-a10-mod1.o
obj-y += clk-a10-pll2.o
+obj-y += clk-a10-ve.o
obj-y += clk-a20-gmac.o
obj-y += clk-mod0.o
obj-y += clk-simple-gates.o
+obj-y += clk-sun8i-bus-gates.o
obj-y += clk-sun8i-mbus.o
obj-y += clk-sun9i-core.o
obj-y += clk-sun9i-mmc.o
obj-y += clk-usb.o
+obj-$(CONFIG_MACH_SUN9I) += clk-sun8i-apb0.o
+obj-$(CONFIG_MACH_SUN9I) += clk-sun9i-cpus.o
+
obj-$(CONFIG_MFD_SUN6I_PRCM) += \
clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o \
clk-sun8i-apb0.o
diff --git a/drivers/clk/sunxi/clk-a10-ve.c b/drivers/clk/sunxi/clk-a10-ve.c
new file mode 100644
index 000000000000..044c1717b762
--- /dev/null
+++ b/drivers/clk/sunxi/clk-a10-ve.c
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2015 Chen-Yu Tsai
+ *
+ * Chen-Yu Tsai <wens@csie.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk-provider.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 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-simple-gates.c b/drivers/clk/sunxi/clk-simple-gates.c
index 0214c6548afd..f4da52b5ca0e 100644
--- a/drivers/clk/sunxi/clk-simple-gates.c
+++ b/drivers/clk/sunxi/clk-simple-gates.c
@@ -140,6 +140,8 @@ 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 */
@@ -158,3 +160,15 @@ 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-sun8i-apb0.c b/drivers/clk/sunxi/clk-sun8i-apb0.c
index 7ae5d2c2cde1..7ba61103a6f5 100644
--- a/drivers/clk/sunxi/clk-sun8i-apb0.c
+++ b/drivers/clk/sunxi/clk-sun8i-apb0.c
@@ -17,13 +17,77 @@
#include <linux/clk-provider.h>
#include <linux/module.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, CLK_DIVIDER_POWER_OF_TWO, 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(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;
- const char *clk_name = np->name;
- const char *clk_parent;
struct resource *r;
void __iomem *reg;
struct clk *clk;
@@ -33,19 +97,11 @@ static int sun8i_a23_apb0_clk_probe(struct platform_device *pdev)
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);
-
- /* The A23 APB0 clock is a standard 2 bit wide divider clock */
- clk = clk_register_divider(&pdev->dev, clk_name, clk_parent, 0, reg,
- 0, 2, CLK_DIVIDER_POWER_OF_TWO, NULL);
+ clk = sun8i_a23_apb0_register(np, reg);
if (IS_ERR(clk))
return PTR_ERR(clk);
- return of_clk_add_provider(np, of_clk_src_simple_get, clk);
+ return 0;
}
static const struct of_device_id sun8i_a23_apb0_clk_dt_ids[] = {
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 000000000000..e32d18ba252b
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun8i-bus-gates.c
@@ -0,0 +1,112 @@
+/*
+ * 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>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/clk-provider.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;
+
+ 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);
diff --git a/drivers/clk/sunxi/clk-sun9i-cpus.c b/drivers/clk/sunxi/clk-sun9i-cpus.c
new file mode 100644
index 000000000000..7626d2194b96
--- /dev/null
+++ b/drivers/clk/sunxi/clk-sun9i-cpus.c
@@ -0,0 +1,240 @@
+/*
+ * 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/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-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c
index 9c79af0c03b2..5ba2188ee99c 100644
--- a/drivers/clk/sunxi/clk-sunxi.c
+++ b/drivers/clk/sunxi/clk-sunxi.c
@@ -778,6 +778,10 @@ 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 void __init sunxi_mux_clk_setup(struct device_node *node,
struct mux_data *data)
{
@@ -1130,6 +1134,7 @@ static const struct of_device_id clk_divs_match[] __initconst = {
static const struct of_device_id clk_mux_match[] __initconst = {
{.compatible = "allwinner,sun4i-a10-cpu-clk", .data = &sun4i_cpu_mux_data,},
{.compatible = "allwinner,sun6i-a31-ahb1-mux-clk", .data = &sun6i_a31_ahb1_mux_data,},
+ {.compatible = "allwinner,sun8i-h3-ahb2-clk", .data = &sun8i_h3_ahb2_mux_data,},
{}
};
@@ -1212,6 +1217,7 @@ CLK_OF_DECLARE(sun6i_a31_clk_init, "allwinner,sun6i-a31", sun6i_init_clocks);
CLK_OF_DECLARE(sun6i_a31s_clk_init, "allwinner,sun6i-a31s", sun6i_init_clocks);
CLK_OF_DECLARE(sun8i_a23_clk_init, "allwinner,sun8i-a23", sun6i_init_clocks);
CLK_OF_DECLARE(sun8i_a33_clk_init, "allwinner,sun8i-a33", sun6i_init_clocks);
+CLK_OF_DECLARE(sun8i_h3_clk_init, "allwinner,sun8i-h3", sun6i_init_clocks);
static void __init sun9i_init_clocks(struct device_node *node)
{
diff --git a/drivers/clk/sunxi/clk-usb.c b/drivers/clk/sunxi/clk-usb.c
index 1a72cd672839..67b8e38f4ee9 100644
--- a/drivers/clk/sunxi/clk-usb.c
+++ b/drivers/clk/sunxi/clk-usb.c
@@ -243,3 +243,15 @@ 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);
+
+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);