diff options
author | Michael Turquette <mturquette@baylibre.com> | 2015-12-23 23:23:18 -0800 |
---|---|---|
committer | Michael Turquette <mturquette@baylibre.com> | 2015-12-23 23:23:18 -0800 |
commit | cf87a88f519777999bf5374809b34827bb4ab5dd (patch) | |
tree | af42fa20565a33db269faa81cc4c5d1183657723 /drivers/clk | |
parent | a915e30dd26ea5f3cc2e2c044aba38ee5973d3fa (diff) | |
parent | fee3103ac3c0aeab52b337903f5af3cd3b3c02d8 (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')
-rw-r--r-- | drivers/clk/sunxi/Makefile | 5 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-a10-ve.c | 171 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-simple-gates.c | 14 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sun8i-apb0.c | 80 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sun8i-bus-gates.c | 112 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sun9i-cpus.c | 240 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-sunxi.c | 6 | ||||
-rw-r--r-- | drivers/clk/sunxi/clk-usb.c | 12 |
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); |