diff options
Diffstat (limited to 'drivers/clk/sunxi')
| -rw-r--r-- | drivers/clk/sunxi/Makefile | 4 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-a10-hosc.c | 73 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-a20-gmac.c | 119 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-factors.c | 36 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-sun6i-apb0-gates.c | 99 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-sun6i-apb0.c | 77 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-sun6i-ar100.c | 233 | ||||
| -rw-r--r-- | drivers/clk/sunxi/clk-sunxi.c | 382 |
8 files changed, 906 insertions, 117 deletions
diff --git a/drivers/clk/sunxi/Makefile b/drivers/clk/sunxi/Makefile index b5bac917612..762fd64dbd1 100644 --- a/drivers/clk/sunxi/Makefile +++ b/drivers/clk/sunxi/Makefile @@ -3,3 +3,7 @@ # obj-y += clk-sunxi.o clk-factors.o +obj-y += clk-a10-hosc.o +obj-y += clk-a20-gmac.o + +obj-$(CONFIG_MFD_SUN6I_PRCM) += clk-sun6i-ar100.o clk-sun6i-apb0.o clk-sun6i-apb0-gates.o diff --git a/drivers/clk/sunxi/clk-a10-hosc.c b/drivers/clk/sunxi/clk-a10-hosc.c new file mode 100644 index 00000000000..0481d5d673d --- /dev/null +++ b/drivers/clk/sunxi/clk-a10-hosc.c @@ -0,0 +1,73 @@ +/* + * Copyright 2013 Emilio López + * + * Emilio López <emilio@elopez.com.ar> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/of.h> +#include <linux/of_address.h> + +#define SUNXI_OSC24M_GATE 0 + +static DEFINE_SPINLOCK(hosc_lock); + +static void __init sun4i_osc_clk_setup(struct device_node *node) +{ + struct clk *clk; + struct clk_fixed_rate *fixed; + struct clk_gate *gate; + const char *clk_name = node->name; + u32 rate; + + if (of_property_read_u32(node, "clock-frequency", &rate)) + return; + + /* allocate fixed-rate and gate clock structs */ + fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL); + if (!fixed) + return; + gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); + if (!gate) + goto err_free_fixed; + + of_property_read_string(node, "clock-output-names", &clk_name); + + /* set up gate and fixed rate properties */ + gate->reg = of_iomap(node, 0); + gate->bit_idx = SUNXI_OSC24M_GATE; + gate->lock = &hosc_lock; + fixed->fixed_rate = rate; + + clk = clk_register_composite(NULL, clk_name, + NULL, 0, + NULL, NULL, + &fixed->hw, &clk_fixed_rate_ops, + &gate->hw, &clk_gate_ops, + CLK_IS_ROOT); + + if (IS_ERR(clk)) + goto err_free_gate; + + of_clk_add_provider(node, of_clk_src_simple_get, clk); + clk_register_clkdev(clk, clk_name, NULL); + + 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-a20-gmac.c b/drivers/clk/sunxi/clk-a20-gmac.c new file mode 100644 index 00000000000..633ddc4389e --- /dev/null +++ b/drivers/clk/sunxi/clk-a20-gmac.c @@ -0,0 +1,119 @@ +/* + * Copyright 2013 Emilio López + * Emilio López <emilio@elopez.com.ar> + * + * Copyright 2013 Chen-Yu Tsai + * Chen-Yu Tsai <wens@csie.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/slab.h> + +static DEFINE_SPINLOCK(gmac_lock); + +/** + * sun7i_a20_gmac_clk_setup - Setup function for A20/A31 GMAC clock module + * + * This clock looks something like this + * ________________________ + * MII TX clock from PHY >-----|___________ _________|----> to GMAC core + * GMAC Int. RGMII TX clk >----|___________\__/__gate---|----> to PHY + * Ext. 125MHz RGMII TX clk >--|__divider__/ | + * |________________________| + * + * The external 125 MHz reference is optional, i.e. GMAC can use its + * internal TX clock just fine. The A31 GMAC clock module does not have + * the divider controls for the external reference. + * + * To keep it simple, let the GMAC use either the MII TX clock for MII mode, + * and its internal TX clock for GMII and RGMII modes. The GMAC driver should + * select the appropriate source and gate/ungate the output to the PHY. + * + * Only the GMAC should use this clock. Altering the clock so that it doesn't + * match the GMAC's operation parameters will result in the GMAC not being + * able to send traffic out. The GMAC driver should set the clock rate and + * enable/disable this clock to configure the required state. The clock + * driver then responds by auto-reparenting the clock. + */ + +#define SUN7I_A20_GMAC_GPIT 2 +#define SUN7I_A20_GMAC_MASK 0x3 +#define SUN7I_A20_GMAC_PARENTS 2 + +static 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 *reg; + + if (of_property_read_string(node, "clock-output-names", &clk_name)) + return; + + /* allocate mux and gate clock structs */ + mux = kzalloc(sizeof(struct clk_mux), GFP_KERNEL); + if (!mux) + return; + + gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); + if (!gate) + goto free_mux; + + /* gmac clock requires exactly 2 parents */ + parents[0] = of_clk_get_parent_name(node, 0); + parents[1] = of_clk_get_parent_name(node, 1); + if (!parents[0] || !parents[1]) + 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->flags = CLK_MUX_INDEX_BIT; + 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); + clk_register_clkdev(clk, clk_name, NULL); + + 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 index 9e232644f07..3806d97e529 100644 --- a/drivers/clk/sunxi/clk-factors.c +++ b/drivers/clk/sunxi/clk-factors.c @@ -77,6 +77,41 @@ static long clk_factors_round_rate(struct clk_hw *hw, unsigned long rate, return rate; } +static long clk_factors_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk **best_parent_p) +{ + struct clk *clk = hw->clk, *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_get_num_parents(clk); + for (i = 0; i < num_parents; i++) { + parent = clk_get_parent_by_index(clk, i); + if (!parent) + continue; + if (__clk_get_flags(clk) & CLK_SET_RATE_PARENT) + parent_rate = __clk_round_rate(parent, rate); + else + parent_rate = __clk_get_rate(parent); + + child_rate = clk_factors_round_rate(hw, rate, &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) + *best_parent_p = best_parent; + *best_parent_rate = best; + + return best_child_rate; +} + static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { @@ -113,6 +148,7 @@ static int clk_factors_set_rate(struct clk_hw *hw, unsigned long rate, } const struct clk_ops clk_factors_ops = { + .determine_rate = clk_factors_determine_rate, .recalc_rate = clk_factors_recalc_rate, .round_rate = clk_factors_round_rate, .set_rate = clk_factors_set_rate, 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 00000000000..670f90d629d --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-apb0-gates.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 Free Electrons + * + * License Terms: GNU General Public License v2 + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + * + * Allwinner A31 APB0 clock gates driver + * + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define SUN6I_APB0_GATES_MAX_SIZE 32 + +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 char *clk_parent; + const char *clk_name; + struct resource *r; + void __iomem *reg; + int gate_id; + int ngates; + int i; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(reg)) + return PTR_ERR(reg); + + clk_parent = of_clk_get_parent_name(np, 0); + if (!clk_parent) + return -EINVAL; + + ngates = of_property_count_strings(np, "clock-output-names"); + if (ngates < 0) + return ngates; + + if (!ngates || ngates > SUN6I_APB0_GATES_MAX_SIZE) + return -EINVAL; + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, + SUN6I_APB0_GATES_MAX_SIZE * + sizeof(struct clk *), + GFP_KERNEL); + if (!clk_data->clks) + return -ENOMEM; + + for (i = 0; i < ngates; i++) { + of_property_read_string_index(np, "clock-output-names", + i, &clk_name); + + gate_id = i; + of_property_read_u32_index(np, "clock-indices", i, &gate_id); + + WARN_ON(gate_id >= SUN6I_APB0_GATES_MAX_SIZE); + if (gate_id >= SUN6I_APB0_GATES_MAX_SIZE) + continue; + + clk_data->clks[gate_id] = clk_register_gate(&pdev->dev, + clk_name, + clk_parent, 0, + reg, gate_id, + 0, NULL); + WARN_ON(IS_ERR(clk_data->clks[gate_id])); + } + + clk_data->clk_num = ngates; + + return of_clk_add_provider(np, of_clk_src_onecell_get, clk_data); +} + +const struct of_device_id sun6i_a31_apb0_gates_clk_dt_ids[] = { + { .compatible = "allwinner,sun6i-a31-apb0-gates-clk" }, + { /* sentinel */ } +}; + +static struct platform_driver sun6i_a31_apb0_gates_clk_driver = { + .driver = { + .name = "sun6i-a31-apb0-gates-clk", + .owner = THIS_MODULE, + .of_match_table = sun6i_a31_apb0_gates_clk_dt_ids, + }, + .probe = sun6i_a31_apb0_gates_clk_probe, +}; +module_platform_driver(sun6i_a31_apb0_gates_clk_driver); + +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 APB0 gate clocks driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/sunxi/clk-sun6i-apb0.c b/drivers/clk/sunxi/clk-sun6i-apb0.c new file mode 100644 index 00000000000..11f17c34c2a --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-apb0.c @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 Free Electrons + * + * License Terms: GNU General Public License v2 + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + * + * Allwinner A31 APB0 clock driver + * + */ + +#include <linux/clk-provider.h> +#include <linux/module.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; + struct resource *r; + void __iomem *reg; + struct clk *clk; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + reg = devm_ioremap_resource(&pdev->dev, r); + 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); +} + +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", + .owner = THIS_MODULE, + .of_match_table = sun6i_a31_apb0_clk_dt_ids, + }, + .probe = sun6i_a31_apb0_clk_probe, +}; +module_platform_driver(sun6i_a31_apb0_clk_driver); + +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 APB0 clock Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/sunxi/clk-sun6i-ar100.c b/drivers/clk/sunxi/clk-sun6i-ar100.c new file mode 100644 index 00000000000..f73cc051f0d --- /dev/null +++ b/drivers/clk/sunxi/clk-sun6i-ar100.c @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2014 Free Electrons + * + * License Terms: GNU General Public License v2 + * Author: Boris BREZILLON <boris.brezillon@free-electrons.com> + * + * Allwinner A31 AR100 clock driver + * + */ + +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +#define SUN6I_AR100_MAX_PARENTS 4 +#define SUN6I_AR100_SHIFT_MASK 0x3 +#define SUN6I_AR100_SHIFT_MAX SUN6I_AR100_SHIFT_MASK +#define SUN6I_AR100_SHIFT_SHIFT 4 +#define SUN6I_AR100_DIV_MASK 0x1f +#define SUN6I_AR100_DIV_MAX (SUN6I_AR100_DIV_MASK + 1) +#define SUN6I_AR100_DIV_SHIFT 8 +#define SUN6I_AR100_MUX_MASK 0x3 +#define SUN6I_AR100_MUX_SHIFT 16 + +struct ar100_clk { + struct clk_hw hw; + void __iomem *reg; +}; + +static inline struct ar100_clk *to_ar100_clk(struct clk_hw *hw) +{ + return container_of(hw, struct ar100_clk, hw); +} + +static unsigned long ar100_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ar100_clk *clk = to_ar100_clk(hw); + u32 val = readl(clk->reg); + int shift = (val >> SUN6I_AR100_SHIFT_SHIFT) & SUN6I_AR100_SHIFT_MASK; + int div = (val >> SUN6I_AR100_DIV_SHIFT) & SUN6I_AR100_DIV_MASK; + + return (parent_rate >> shift) / (div + 1); +} + +static long ar100_determine_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *best_parent_rate, + struct clk **best_parent_clk) +{ + int nparents = __clk_get_num_parents(hw->clk); + long best_rate = -EINVAL; + int i; + + *best_parent_clk = NULL; + + for (i = 0; i < nparents; i++) { + unsigned long parent_rate; + unsigned long tmp_rate; + struct clk *parent; + unsigned long div; + int shift; + + parent = clk_get_parent_by_index(hw->clk, i); + parent_rate = __clk_get_rate(parent); + div = DIV_ROUND_UP(parent_rate, rate); + + /* + * The AR100 clk contains 2 divisors: + * - one power of 2 divisor + * - one regular divisor + * + * First check if we can safely shift (or divide by a power + * of 2) without losing precision on the requested rate. + */ + shift = ffs(div) - 1; + if (shift > SUN6I_AR100_SHIFT_MAX) + shift = SUN6I_AR100_SHIFT_MAX; + + div >>= shift; + + /* + * Then if the divisor is still bigger than what the HW + * actually supports, use a bigger shift (or power of 2 + * divider) value and accept to lose some precision. + */ + while (div > SUN6I_AR100_DIV_MAX) { + shift++; + div >>= 1; + if (shift > SUN6I_AR100_SHIFT_MAX) + break; + } + + /* + * If the shift value (or power of 2 divider) is bigger + * than what the HW actually support, skip this parent. + */ + if (shift > SUN6I_AR100_SHIFT_MAX) + continue; + + tmp_rate = (parent_rate >> shift) / div; + if (!*best_parent_clk || tmp_rate > best_rate) { + *best_parent_clk = parent; + *best_parent_rate = parent_rate; + best_rate = tmp_rate; + } + } + + return best_rate; +} + +static int ar100_set_parent(struct clk_hw *hw, u8 index) +{ + struct ar100_clk *clk = to_ar100_clk(hw); + u32 val = readl(clk->reg); + + if (index >= SUN6I_AR100_MAX_PARENTS) + return -EINVAL; + + val &= ~(SUN6I_AR100_MUX_MASK << SUN6I_AR100_MUX_SHIFT); + val |= (index << SUN6I_AR100_MUX_SHIFT); + writel(val, clk->reg); + + return 0; +} + +static u8 ar100_get_parent(struct clk_hw *hw) +{ + struct ar100_clk *clk = to_ar100_clk(hw); + return (readl(clk->reg) >> SUN6I_AR100_MUX_SHIFT) & + SUN6I_AR100_MUX_MASK; +} + +static int ar100_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + unsigned long div = parent_rate / rate; + struct ar100_clk *clk = to_ar100_clk(hw); + u32 val = readl(clk->reg); + int shift; + + if (parent_rate % rate) + return -EINVAL; + + shift = ffs(div) - 1; + if (shift > SUN6I_AR100_SHIFT_MAX) + shift = SUN6I_AR100_SHIFT_MAX; + + div >>= shift; + + if (div > SUN6I_AR100_DIV_MAX) + return -EINVAL; + + val &= ~((SUN6I_AR100_SHIFT_MASK << SUN6I_AR100_SHIFT_SHIFT) | + (SUN6I_AR100_DIV_MASK << SUN6I_AR100_DIV_SHIFT)); + val |= (shift << SUN6I_AR100_SHIFT_SHIFT) | + (div << SUN6I_AR100_DIV_SHIFT); + writel(val, clk->reg); + + return 0; +} + +struct clk_ops ar100_ops = { + .recalc_rate = ar100_recalc_rate, + .determine_rate = ar100_determine_rate, + .set_parent = ar100_set_parent, + .get_parent = ar100_get_parent, + .set_rate = ar100_set_rate, +}; + +static int sun6i_a31_ar100_clk_probe(struct platform_device *pdev) +{ + const char *parents[SUN6I_AR100_MAX_PARENTS]; + struct device_node *np = pdev->dev.of_node; + const char *clk_name = np->name; + struct clk_init_data init; + struct ar100_clk *ar100; + struct resource *r; + struct clk *clk; + int nparents; + int i; + + ar100 = devm_kzalloc(&pdev->dev, sizeof(*ar100), GFP_KERNEL); + if (!ar100) + return -ENOMEM; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ar100->reg = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(ar100->reg)) + return PTR_ERR(ar100->reg); + + nparents = of_clk_get_parent_count(np); + if (nparents > SUN6I_AR100_MAX_PARENTS) + nparents = SUN6I_AR100_MAX_PARENTS; + + for (i = 0; i < nparents; i++) + parents[i] = of_clk_get_parent_name(np, i); + + of_property_read_string(np, "clock-output-names", &clk_name); + + init.name = clk_name; + init.ops = &ar100_ops; + init.parent_names = parents; + init.num_parents = nparents; + init.flags = 0; + + ar100->hw.init = &init; + + clk = clk_register(&pdev->dev, &ar100->hw); + if (IS_ERR(clk)) + return PTR_ERR(clk); + + return of_clk_add_provider(np, of_clk_src_simple_get, clk); +} + +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", + .owner = THIS_MODULE, + .of_match_table = sun6i_a31_ar100_clk_dt_ids, + }, + .probe = sun6i_a31_ar100_clk_probe, +}; +module_platform_driver(sun6i_a31_ar100_clk_driver); + +MODULE_AUTHOR("Boris BREZILLON <boris.brezillon@free-electrons.com>"); +MODULE_DESCRIPTION("Allwinner A31 AR100 clock Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index abb6c5ac8a1..fb2ce8440f0 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -18,6 +18,7 @@ #include <linux/clkdev.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/reset-controller.h> #include "clk-factors.h" @@ -27,61 +28,6 @@ static DEFINE_SPINLOCK(clk_lock); #define SUNXI_MAX_PARENTS 5 /** - * sun4i_osc_clk_setup() - Setup function for gatable oscillator - */ - -#define SUNXI_OSC24M_GATE 0 - -static void __init sun4i_osc_clk_setup(struct device_node *node) -{ - struct clk *clk; - struct clk_fixed_rate *fixed; - struct clk_gate *gate; - const char *clk_name = node->name; - u32 rate; - - if (of_property_read_u32(node, "clock-frequency", &rate)) - return; - - /* allocate fixed-rate and gate clock structs */ - fixed = kzalloc(sizeof(struct clk_fixed_rate), GFP_KERNEL); - if (!fixed) - return; - gate = kzalloc(sizeof(struct clk_gate), GFP_KERNEL); - if (!gate) - goto err_free_fixed; - - /* set up gate and fixed rate properties */ - gate->reg = of_iomap(node, 0); - gate->bit_idx = SUNXI_OSC24M_GATE; - gate->lock = &clk_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, - CLK_IS_ROOT); - - if (IS_ERR(clk)) - goto err_free_gate; - - of_clk_add_provider(node, of_clk_src_simple_get, clk); - clk_register_clkdev(clk, clk_name, NULL); - - return; - -err_free_gate: - kfree(gate); -err_free_fixed: - kfree(fixed); -} -CLK_OF_DECLARE(sun4i_osc, "allwinner,sun4i-osc-clk", sun4i_osc_clk_setup); - - - -/** * 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); @@ -249,7 +195,38 @@ static void sun4i_get_pll5_factors(u32 *freq, u32 parent_rate, *n = DIV_ROUND_UP(div, (*k+1)); } +/** + * sun6i_a31_get_pll6_factors() - calculates n, k factors for A31 PLL6 + * PLL6 rate is calculated as follows + * rate = parent_rate * n * (k + 1) / 2 + * parent_rate is always 24Mhz + */ + +static void sun6i_a31_get_pll6_factors(u32 *freq, u32 parent_rate, + u8 *n, u8 *k, u8 *m, u8 *p) +{ + u8 div; + /* + * We always have 24MHz / 2, so we can just say that our + * parent clock is 12MHz. + */ + parent_rate = parent_rate / 2; + + /* Normalize value to a parent_rate multiple (24M / 2) */ + div = *freq / parent_rate; + *freq = parent_rate * div; + + /* we were called to round the frequency, we can now return */ + if (n == NULL) + return; + + *k = div / 32; + if (*k > 3) + *k = 3; + + *n = DIV_ROUND_UP(div, (*k+1)); +} /** * sun4i_get_apb1_factors() - calculates m, p factors for APB1 @@ -265,7 +242,7 @@ static void sun4i_get_apb1_factors(u32 *freq, u32 parent_rate, if (parent_rate < *freq) *freq = parent_rate; - parent_rate = (parent_rate + (*freq - 1)) / *freq; + parent_rate = DIV_ROUND_UP(parent_rate, *freq); /* Invalid rate! */ if (parent_rate > 32) @@ -296,7 +273,7 @@ static void sun4i_get_apb1_factors(u32 *freq, u32 parent_rate, /** * sun4i_get_mod0_factors() - calculates m, n factors for MOD0-style clocks - * MMC rate is calculated as follows + * MOD0 rate is calculated as follows * rate = (parent_rate >> p) / (m + 1); */ @@ -310,7 +287,7 @@ static void sun4i_get_mod0_factors(u32 *freq, u32 parent_rate, if (*freq > parent_rate) *freq = parent_rate; - div = parent_rate / *freq; + div = DIV_ROUND_UP(parent_rate, *freq); if (div < 16) calcp = 0; @@ -351,7 +328,7 @@ static void sun7i_a20_get_out_factors(u32 *freq, u32 parent_rate, if (*freq > parent_rate) *freq = parent_rate; - div = parent_rate / *freq; + div = DIV_ROUND_UP(parent_rate, *freq); if (div < 32) calcp = 0; @@ -374,6 +351,41 @@ static void sun7i_a20_get_out_factors(u32 *freq, u32 parent_rate, *p = calcp; } +/** + * clk_sunxi_mmc_phase_control() - configures MMC clock phase control + */ + +void clk_sunxi_mmc_phase_control(struct clk *clk, u8 sample, u8 output) +{ + #define to_clk_composite(_hw) container_of(_hw, struct clk_composite, hw) + #define to_clk_factors(_hw) container_of(_hw, struct clk_factors, hw) + + struct clk_hw *hw = __clk_get_hw(clk); + struct clk_composite *composite = to_clk_composite(hw); + struct clk_hw *rate_hw = composite->rate_hw; + struct clk_factors *factors = to_clk_factors(rate_hw); + unsigned long flags = 0; + u32 reg; + + if (factors->lock) + spin_lock_irqsave(factors->lock, flags); + + reg = readl(factors->reg); + + /* set sample clock phase control */ + reg &= ~(0x7 << 20); + reg |= ((sample & 0x7) << 20); + + /* set output clock phase control */ + reg &= ~(0x7 << 8); + reg |= ((output & 0x7) << 8); + + writel(reg, factors->reg); + + if (factors->lock) + spin_unlock_irqrestore(factors->lock, flags); +} +EXPORT_SYMBOL(clk_sunxi_mmc_phase_control); /** @@ -387,6 +399,7 @@ struct factors_data { int mux; struct clk_factors_config *table; void (*getter) (u32 *rate, u32 parent_rate, u8 *n, u8 *k, u8 *m, u8 *p); + const char *name; }; static struct clk_factors_config sun4i_pll1_config = { @@ -416,6 +429,13 @@ static struct clk_factors_config sun4i_pll5_config = { .kwidth = 2, }; +static struct clk_factors_config sun6i_a31_pll6_config = { + .nshift = 8, + .nwidth = 5, + .kshift = 4, + .kwidth = 2, +}; + static struct clk_factors_config sun4i_apb1_config = { .mshift = 0, .mwidth = 5, @@ -451,10 +471,30 @@ static const struct factors_data sun6i_a31_pll1_data __initconst = { .getter = sun6i_a31_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, + .name = "pll5", +}; + +static const struct factors_data sun4i_pll6_data __initconst = { + .enable = 31, + .table = &sun4i_pll5_config, + .getter = sun4i_get_pll5_factors, + .name = "pll6", +}; + +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 sun4i_apb1_data __initconst = { @@ -497,14 +537,14 @@ static struct clk * __init sunxi_factors_clk_setup(struct device_node *node, (parents[i] = of_clk_get_parent_name(node, i)) != NULL) i++; - /* Nodes should be providing the name via clock-output-names - * but originally our dts didn't, and so we used node->name. - * The new, better nodes look like clk@deadbeef, so we pull the - * name just in this case */ - if (!strcmp("clk", clk_name)) { - of_property_read_string_index(node, "clock-output-names", - 0, &clk_name); - } + /* + * some factor clocks, such as pll5 and pll6, may have multiple + * outputs, and have their name designated in factors_data + */ + if (data->name) + clk_name = data->name; + else + of_property_read_string(node, "clock-output-names", &clk_name); factors = kzalloc(sizeof(struct clk_factors), GFP_KERNEL); if (!factors) @@ -601,6 +641,8 @@ static void __init sunxi_mux_clk_setup(struct device_node *node, (parents[i] = of_clk_get_parent_name(node, i)) != NULL) i++; + of_property_read_string(node, "clock-output-names", &clk_name); + clk = clk_register_mux(NULL, clk_name, parents, i, CLK_SET_RATE_NO_REPARENT, reg, data->shift, SUNXI_MUX_GATE_WIDTH, @@ -660,6 +702,8 @@ static void __init sunxi_divider_clk_setup(struct device_node *node, clk_parent = of_clk_get_parent_name(node, 0); + of_property_read_string(node, "clock-output-names", &clk_name); + clk = clk_register_divider(NULL, clk_name, clk_parent, 0, reg, data->shift, data->width, data->pow ? CLK_DIVIDER_POWER_OF_TWO : 0, @@ -673,6 +717,59 @@ static void __init sunxi_divider_clk_setup(struct device_node *node, /** + * sunxi_gates_reset... - reset bits in leaf gate clk registers handling + */ + +struct gates_reset_data { + void __iomem *reg; + spinlock_t *lock; + struct reset_controller_dev rcdev; +}; + +static int sunxi_gates_reset_assert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct gates_reset_data *data = container_of(rcdev, + struct gates_reset_data, + rcdev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(data->lock, flags); + + reg = readl(data->reg); + writel(reg & ~BIT(id), data->reg); + + spin_unlock_irqrestore(data->lock, flags); + + return 0; +} + +static int sunxi_gates_reset_deassert(struct reset_controller_dev *rcdev, + unsigned long id) +{ + struct gates_reset_data *data = container_of(rcdev, + struct gates_reset_data, + rcdev); + unsigned long flags; + u32 reg; + + spin_lock_irqsave(data->lock, flags); + + reg = readl(data->reg); + writel(reg | BIT(id), data->reg); + + spin_unlock_irqrestore(data->lock, flags); + + return 0; +} + +static struct reset_control_ops sunxi_gates_reset_ops = { + .assert = sunxi_gates_reset_assert, + .deassert = sunxi_gates_reset_deassert, +}; + +/** * sunxi_gates_clk_setup() - Setup function for leaf gates on clocks */ @@ -680,6 +777,7 @@ static void __init sunxi_divider_clk_setup(struct device_node *node, struct gates_data { DECLARE_BITMAP(mask, SUNXI_GATES_MAX_SIZE); + u32 reset_mask; }; static const struct gates_data sun4i_axi_gates_data __initconst = { @@ -746,10 +844,26 @@ static const struct gates_data sun7i_a20_apb1_gates_data __initconst = { .mask = { 0xff80ff }, }; +static const struct gates_data sun4i_a10_usb_gates_data __initconst = { + .mask = {0x1C0}, + .reset_mask = 0x07, +}; + +static const struct gates_data sun5i_a13_usb_gates_data __initconst = { + .mask = {0x140}, + .reset_mask = 0x03, +}; + +static const struct gates_data sun6i_a31_usb_gates_data __initconst = { + .mask = { BIT(18) | BIT(17) | BIT(16) | BIT(10) | BIT(9) | BIT(8) }, + .reset_mask = BIT(2) | BIT(1) | BIT(0), +}; + static void __init sunxi_gates_clk_setup(struct device_node *node, struct gates_data *data) { struct clk_onecell_data *clk_data; + struct gates_reset_data *reset_data; const char *clk_parent; const char *clk_name; void *reg; @@ -793,6 +907,21 @@ static void __init sunxi_gates_clk_setup(struct device_node *node, clk_data->clk_num = i; of_clk_add_provider(node, of_clk_src_onecell_get, clk_data); + + /* Register a reset controler for gates with reset bits */ + if (data->reset_mask == 0) + return; + + reset_data = kzalloc(sizeof(*reset_data), GFP_KERNEL); + if (!reset_data) + return; + + reset_data->reg = reg; + reset_data->lock = &clk_lock; + reset_data->rcdev.nr_resets = __fls(data->reset_mask) + 1; + reset_data->rcdev.ops = &sunxi_gates_reset_ops; + reset_data->rcdev.of_node = node; + reset_controller_register(&reset_data->rcdev); } @@ -832,7 +961,7 @@ static const struct divs_data pll5_divs_data __initconst = { }; static const struct divs_data pll6_divs_data __initconst = { - .factors = &sun4i_pll5_data, + .factors = &sun4i_pll6_data, .div = { { .shift = 0, .table = pll6_sata_tbl, .gate = 14 }, /* M, SATA */ { .fixed = 2 }, /* P, other */ @@ -854,7 +983,7 @@ static void __init sunxi_divs_clk_setup(struct device_node *node, struct divs_data *data) { struct clk_onecell_data *clk_data; - const char *parent = node->name; + const char *parent; const char *clk_name; struct clk **clks, *pclk; struct clk_hw *gate_hw, *rate_hw; @@ -868,6 +997,7 @@ static void __init sunxi_divs_clk_setup(struct device_node *node, /* Set up factor clock that we will be dividing */ pclk = sunxi_factors_clk_setup(node, data->factors); + parent = __clk_get_name(pclk); reg = of_iomap(node, 0); @@ -970,56 +1100,61 @@ free_clkdata: /* Matches for factors clocks */ static const struct of_device_id clk_factors_match[] __initconst = { - {.compatible = "allwinner,sun4i-pll1-clk", .data = &sun4i_pll1_data,}, + {.compatible = "allwinner,sun4i-a10-pll1-clk", .data = &sun4i_pll1_data,}, {.compatible = "allwinner,sun6i-a31-pll1-clk", .data = &sun6i_a31_pll1_data,}, - {.compatible = "allwinner,sun4i-apb1-clk", .data = &sun4i_apb1_data,}, - {.compatible = "allwinner,sun4i-mod0-clk", .data = &sun4i_mod0_data,}, + {.compatible = "allwinner,sun7i-a20-pll4-clk", .data = &sun7i_a20_pll4_data,}, + {.compatible = "allwinner,sun6i-a31-pll6-clk", .data = &sun6i_a31_pll6_data,}, + {.compatible = "allwinner,sun4i-a10-apb1-clk", .data = &sun4i_apb1_data,}, + {.compatible = "allwinner,sun4i-a10-mod0-clk", .data = &sun4i_mod0_data,}, {.compatible = "allwinner,sun7i-a20-out-clk", .data = &sun7i_a20_out_data,}, {} }; /* Matches for divider clocks */ static const struct of_device_id clk_div_match[] __initconst = { - {.compatible = "allwinner,sun4i-axi-clk", .data = &sun4i_axi_data,}, - {.compatible = "allwinner,sun4i-ahb-clk", .data = &sun4i_ahb_data,}, - {.compatible = "allwinner,sun4i-apb0-clk", .data = &sun4i_apb0_data,}, + {.compatible = "allwinner,sun4i-a10-axi-clk", .data = &sun4i_axi_data,}, + {.compatible = "allwinner,sun4i-a10-ahb-clk", .data = &sun4i_ahb_data,}, + {.compatible = "allwinner,sun4i-a10-apb0-clk", .data = &sun4i_apb0_data,}, {.compatible = "allwinner,sun6i-a31-apb2-div-clk", .data = &sun6i_a31_apb2_div_data,}, {} }; /* Matches for divided outputs */ static const struct of_device_id clk_divs_match[] __initconst = { - {.compatible = "allwinner,sun4i-pll5-clk", .data = &pll5_divs_data,}, - {.compatible = "allwinner,sun4i-pll6-clk", .data = &pll6_divs_data,}, + {.compatible = "allwinner,sun4i-a10-pll5-clk", .data = &pll5_divs_data,}, + {.compatible = "allwinner,sun4i-a10-pll6-clk", .data = &pll6_divs_data,}, {} }; /* Matches for mux clocks */ static const struct of_device_id clk_mux_match[] __initconst = { - {.compatible = "allwinner,sun4i-cpu-clk", .data = &sun4i_cpu_mux_data,}, - {.compatible = "allwinner,sun4i-apb1-mux-clk", .data = &sun4i_apb1_mux_data,}, + {.compatible = "allwinner,sun4i-a10-cpu-clk", .data = &sun4i_cpu_mux_data,}, + {.compatible = "allwinner,sun4i-a10-apb1-mux-clk", .data = &sun4i_apb1_mux_data,}, {.compatible = "allwinner,sun6i-a31-ahb1-mux-clk", .data = &sun6i_a31_ahb1_mux_data,}, {} }; /* Matches for gate clocks */ static const struct of_device_id clk_gates_match[] __initconst = { - {.compatible = "allwinner,sun4i-axi-gates-clk", .data = &sun4i_axi_gates_data,}, - {.compatible = "allwinner,sun4i-ahb-gates-clk", .data = &sun4i_ahb_gates_data,}, + {.compatible = "allwinner,sun4i-a10-axi-gates-clk", .data = &sun4i_axi_gates_data,}, + {.compatible = "allwinner,sun4i-a10-ahb-gates-clk", .data = &sun4i_ahb_gates_data,}, {.compatible = "allwinner,sun5i-a10s-ahb-gates-clk", .data = &sun5i_a10s_ahb_gates_data,}, {.compatible = "allwinner,sun5i-a13-ahb-gates-clk", .data = &sun5i_a13_ahb_gates_data,}, {.compatible = "allwinner,sun6i-a31-ahb1-gates-clk", .data = &sun6i_a31_ahb1_gates_data,}, {.compatible = "allwinner,sun7i-a20-ahb-gates-clk", .data = &sun7i_a20_ahb_gates_data,}, - {.compatible = "allwinner,sun4i-apb0-gates-clk", .data = &sun4i_apb0_gates_data,}, + {.compatible = "allwinner,sun4i-a10-apb0-gates-clk", .data = &sun4i_apb0_gates_data,}, {.compatible = "allwinner,sun5i-a10s-apb0-gates-clk", .data = &sun5i_a10s_apb0_gates_data,}, {.compatible = "allwinner,sun5i-a13-apb0-gates-clk", .data = &sun5i_a13_apb0_gates_data,}, {.compatible = "allwinner,sun7i-a20-apb0-gates-clk", .data = &sun7i_a20_apb0_gates_data,}, - {.compatible = "allwinner,sun4i-apb1-gates-clk", .data = &sun4i_apb1_gates_data,}, + {.compatible = "allwinner,sun4i-a10-apb1-gates-clk", .data = &sun4i_apb1_gates_data,}, {.compatible = "allwinner,sun5i-a10s-apb1-gates-clk", .data = &sun5i_a10s_apb1_gates_data,}, {.compatible = "allwinner,sun5i-a13-apb1-gates-clk", .data = &sun5i_a13_apb1_gates_data,}, {.compatible = "allwinner,sun6i-a31-apb1-gates-clk", .data = &sun6i_a31_apb1_gates_data,}, {.compatible = "allwinner,sun7i-a20-apb1-gates-clk", .data = &sun7i_a20_apb1_gates_data,}, {.compatible = "allwinner,sun6i-a31-apb2-gates-clk", .data = &sun6i_a31_apb2_gates_data,}, + {.compatible = "allwinner,sun4i-a10-usb-clk", .data = &sun4i_a10_usb_gates_data,}, + {.compatible = "allwinner,sun5i-a13-usb-clk", .data = &sun5i_a13_usb_gates_data,}, + {.compatible = "allwinner,sun6i-a31-usb-clk", .data = &sun6i_a31_usb_gates_data,}, {} }; @@ -1031,40 +1166,16 @@ static void __init of_sunxi_table_clock_setup(const struct of_device_id *clk_mat const struct of_device_id *match; void (*setup_function)(struct device_node *, const void *) = function; - for_each_matching_node(np, clk_match) { - match = of_match_node(clk_match, np); + for_each_matching_node_and_match(np, clk_match, &match) { data = match->data; setup_function(np, data); } } -/** - * System clock protection - * - * By enabling these critical clocks, we prevent their accidental gating - * by the framework - */ -static void __init sunxi_clock_protect(void) +static void __init sunxi_init_clocks(const char *clocks[], int nclocks) { - struct clk *clk; - - /* memory bus clock - sun5i+ */ - clk = clk_get(NULL, "mbus"); - if (!IS_ERR(clk)) { - clk_prepare_enable(clk); - clk_put(clk); - } - - /* DDR clock - sun4i+ */ - clk = clk_get(NULL, "pll5_ddr"); - if (!IS_ERR(clk)) { - clk_prepare_enable(clk); - clk_put(clk); - } -} + unsigned int i; -static void __init sunxi_init_clocks(void) -{ /* Register factor clocks */ of_sunxi_table_clock_setup(clk_factors_match, sunxi_factors_clk_setup); @@ -1080,11 +1191,48 @@ static void __init sunxi_init_clocks(void) /* Register gate clocks */ of_sunxi_table_clock_setup(clk_gates_match, sunxi_gates_clk_setup); - /* Enable core system clocks */ - sunxi_clock_protect(); + /* Protect the clocks that needs to stay on */ + for (i = 0; i < nclocks; i++) { + struct clk *clk = clk_get(NULL, clocks[i]); + + if (!IS_ERR(clk)) + clk_prepare_enable(clk); + } +} + +static const char *sun4i_a10_critical_clocks[] __initdata = { + "pll5_ddr", +}; + +static void __init sun4i_a10_init_clocks(struct device_node *node) +{ + sunxi_init_clocks(sun4i_a10_critical_clocks, + ARRAY_SIZE(sun4i_a10_critical_clocks)); +} +CLK_OF_DECLARE(sun4i_a10_clk_init, "allwinner,sun4i-a10", sun4i_a10_init_clocks); + +static const char *sun5i_critical_clocks[] __initdata = { + "mbus", + "pll5_ddr", +}; + +static void __init sun5i_init_clocks(struct device_node *node) +{ + sunxi_init_clocks(sun5i_critical_clocks, + ARRAY_SIZE(sun5i_critical_clocks)); +} +CLK_OF_DECLARE(sun5i_a10s_clk_init, "allwinner,sun5i-a10s", sun5i_init_clocks); +CLK_OF_DECLARE(sun5i_a13_clk_init, "allwinner,sun5i-a13", sun5i_init_clocks); +CLK_OF_DECLARE(sun7i_a20_clk_init, "allwinner,sun7i-a20", sun5i_init_clocks); + +static const char *sun6i_critical_clocks[] __initdata = { + "cpu", + "ahb1_sdram", +}; + +static void __init sun6i_init_clocks(struct device_node *node) +{ + sunxi_init_clocks(sun6i_critical_clocks, + ARRAY_SIZE(sun6i_critical_clocks)); } -CLK_OF_DECLARE(sun4i_a10_clk_init, "allwinner,sun4i-a10", sunxi_init_clocks); -CLK_OF_DECLARE(sun5i_a10s_clk_init, "allwinner,sun5i-a10s", sunxi_init_clocks); -CLK_OF_DECLARE(sun5i_a13_clk_init, "allwinner,sun5i-a13", sunxi_init_clocks); -CLK_OF_DECLARE(sun6i_a31_clk_init, "allwinner,sun6i-a31", sunxi_init_clocks); -CLK_OF_DECLARE(sun7i_a20_clk_init, "allwinner,sun7i-a20", sunxi_init_clocks); +CLK_OF_DECLARE(sun6i_a31_clk_init, "allwinner,sun6i-a31", sun6i_init_clocks); |
