diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /drivers/clk/sunxi | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'drivers/clk/sunxi')
26 files changed, 4997 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/Kconfig b/drivers/clk/sunxi/Kconfig new file mode 100644 index 000000000..5e2f92bfe --- /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 (ARM && ARCH_SUNXI) + +if CLK_SUNXI + +config CLK_SUNXI_CLOCKS + bool "Legacy clock drivers" + default ARCH_SUNXI + 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 ARCH_SUNXI + 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 ARCH_SUNXI + 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 ARCH_SUNXI + 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..29eb5a024 --- /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_obj(struct clk_fixed_rate); + if (!fixed) + return; + gate = kzalloc_obj(struct clk_gate); + 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..3e267e293 --- /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_obj(*mux); + if (!mux) + goto err_unmap; + + gate = kzalloc_obj(*gate); + 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..96ebe7242 --- /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_obj(*clk_data); + if (!clk_data) + goto err_unmap; + + clks = kzalloc_objs(struct clk *, SUN4I_PLL2_OUTPUTS); + 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_obj(struct clk_gate); + 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_obj(struct clk_multiplier); + 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..75a501956 --- /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_obj(*div); + if (!div) + goto err_unmap; + + gate = kzalloc_obj(*gate); + 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_obj(*reset_data); + 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..2a81536b6 --- /dev/null +++ b/drivers/clk/sunxi/clk-a20-gmac.c @@ -0,0 +1,113 @@ +// 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); + + +#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 */ +}; + +/** + * sun7i_a20_gmac_clk_setup - Setup function for A20/A31 GMAC clock module + * @node: &struct device_node for the clock + * + * 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. + */ +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_obj(struct clk_mux); + if (!mux) + return; + + gate = kzalloc_obj(struct clk_gate); + 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..149187f18 --- /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_obj(struct clk_factors); + 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_obj(struct clk_gate); + 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_obj(struct clk_mux); + 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..b409a8d70 --- /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_obj(*clk_data); + if (!clk_data) + return; + + clk_data->clks = kzalloc_objs(*clk_data->clks, 3); + 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_obj(*phase); + 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..99e83ada7 --- /dev/null +++ b/drivers/clk/sunxi/clk-simple-gates.c @@ -0,0 +1,168 @@ +// 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 resource res; + void __iomem *clk_reg; + void __iomem *reg; + 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_obj(struct clk_onecell_data); + 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 = kzalloc_objs(struct clk *, number + 1); + if (!clk_data->clks) + goto err_free_data; + + of_property_for_each_u32(node, "clock-indices", 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..e17325e74 --- /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_obj(*mux); + 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_obj(*gate); + 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_obj(*div); + 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_obj(*reset_data); + 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..1b8b1959d --- /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_obj(*gate); + if (!gate) + goto err_unmap; + + gate->reg = reg; + gate->bit_idx = SUN4I_A10_PLL3_GATE_BIT; + gate->lock = &sun4i_a10_pll3_lock; + + mult = kzalloc_obj(*mult); + 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..b61eba112 --- /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_obj(*tclk); + 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..b0a93b4fb --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c @@ -0,0 +1,98 @@ +// 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/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..df0f055ba --- /dev/null +++ b/drivers/clk/sunxi/clk-sun8i-bus-gates.c @@ -0,0 +1,107 @@ +// 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 resource res; + void __iomem *clk_reg; + void __iomem *reg; + 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_obj(struct clk_onecell_data); + 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 = kzalloc_objs(struct clk *, number + 1); + if (!clk_data->clks) + goto err_free_data; + + i = 0; + of_property_for_each_u32(node, "clock-indices", 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..fe7a0693b --- /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_obj(*div); + if (!div) + goto err_unmap; + + mux = kzalloc_obj(*mux); + if (!mux) + goto err_free_div; + + gate = kzalloc_obj(*gate); + 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..516a94f28 --- /dev/null +++ b/drivers/clk/sunxi/clk-sun9i-cpus.c @@ -0,0 +1,243 @@ +// 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); + + +#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, +}; + +/** + * sun9i_a80_cpus_setup() - Setup function for a80 cpus composite clk + * @node: &struct device_node for the clock + */ +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_obj(*cpus); + 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_obj(*mux); + 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..91074017c --- /dev/null +++ b/drivers/clk/sunxi/clk-sun9i-mmc.c @@ -0,0 +1,202 @@ +// 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/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); + + data->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &r); + if (IS_ERR(data->membase)) + return PTR_ERR(data->membase); + + /* one clock/reset pair per word */ + count = DIV_ROUND_UP((resource_size(r)), SUN9I_MMC_WIDTH); + + 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..c6557c1de --- /dev/null +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -0,0 +1,1156 @@ +// 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_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_obj(struct clk_onecell_data); + if (!clk_data) + goto out_unmap; + + clks = kzalloc_objs(*clks, ndivs); + 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_obj(*gate); + 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_obj(*fix_factor); + 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_obj(*divider); + 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 = ÷r->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..903d22c00 --- /dev/null +++ b/drivers/clk/sunxi/clk-usb.c @@ -0,0 +1,252 @@ +// 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, +}; + + +#define SUNXI_USB_MAX_SIZE 32 + +struct usb_clk_data { + u32 clk_mask; + u32 reset_mask; + bool reset_needs_clk; +}; + +/** + * sunxi_usb_clk_setup() - Setup function for usb gate clocks + * @node: &struct device_node for the clock + * @data: &struct usb_clk_data for the clock + * @lock: spinlock for the clock + */ +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_obj(struct clk_onecell_data); + if (!clk_data) + return; + + clk_data->clks = kzalloc_objs(struct clk *, qty + 1); + 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_obj(*reset_data); + 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); |
