diff options
Diffstat (limited to 'drivers/clk/mvebu/clk-gating-ctrl.c')
-rw-r--r-- | drivers/clk/mvebu/clk-gating-ctrl.c | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/drivers/clk/mvebu/clk-gating-ctrl.c b/drivers/clk/mvebu/clk-gating-ctrl.c new file mode 100644 index 000000000000..8fa5408b6c7d --- /dev/null +++ b/drivers/clk/mvebu/clk-gating-ctrl.c @@ -0,0 +1,249 @@ +/* + * Marvell MVEBU clock gating control. + * + * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> + * Andrew Lunn <andrew@lunn.ch> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +#include <linux/kernel.h> +#include <linux/bitops.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/clk/mvebu.h> +#include <linux/of.h> +#include <linux/of_address.h> + +struct mvebu_gating_ctrl { + spinlock_t lock; + struct clk **gates; + int num_gates; +}; + +struct mvebu_soc_descr { + const char *name; + const char *parent; + int bit_idx; +}; + +#define to_clk_gate(_hw) container_of(_hw, struct clk_gate, hw) + +static struct clk *mvebu_clk_gating_get_src( + struct of_phandle_args *clkspec, void *data) +{ + struct mvebu_gating_ctrl *ctrl = (struct mvebu_gating_ctrl *)data; + int n; + + if (clkspec->args_count < 1) + return ERR_PTR(-EINVAL); + + for (n = 0; n < ctrl->num_gates; n++) { + struct clk_gate *gate = + to_clk_gate(__clk_get_hw(ctrl->gates[n])); + if (clkspec->args[0] == gate->bit_idx) + return ctrl->gates[n]; + } + return ERR_PTR(-ENODEV); +} + +static void __init mvebu_clk_gating_setup( + struct device_node *np, const struct mvebu_soc_descr *descr) +{ + struct mvebu_gating_ctrl *ctrl; + struct clk *clk; + void __iomem *base; + const char *default_parent = NULL; + int n; + + base = of_iomap(np, 0); + + clk = of_clk_get(np, 0); + if (!IS_ERR(clk)) { + default_parent = __clk_get_name(clk); + clk_put(clk); + } + + ctrl = kzalloc(sizeof(struct mvebu_gating_ctrl), GFP_KERNEL); + if (WARN_ON(!ctrl)) + return; + + spin_lock_init(&ctrl->lock); + + /* + * Count, allocate, and register clock gates + */ + for (n = 0; descr[n].name;) + n++; + + ctrl->num_gates = n; + ctrl->gates = kzalloc(ctrl->num_gates * sizeof(struct clk *), + GFP_KERNEL); + if (WARN_ON(!ctrl->gates)) { + kfree(ctrl); + return; + } + + for (n = 0; n < ctrl->num_gates; n++) { + u8 flags = 0; + const char *parent = + (descr[n].parent) ? descr[n].parent : default_parent; + + /* + * On Armada 370, the DDR clock is a special case: it + * isn't taken by any driver, but should anyway be + * kept enabled, so we mark it as IGNORE_UNUSED for + * now. + */ + if (!strcmp(descr[n].name, "ddr")) + flags |= CLK_IGNORE_UNUSED; + + ctrl->gates[n] = clk_register_gate(NULL, descr[n].name, parent, + flags, base, descr[n].bit_idx, 0, &ctrl->lock); + WARN_ON(IS_ERR(ctrl->gates[n])); + } + of_clk_add_provider(np, mvebu_clk_gating_get_src, ctrl); +} + +/* + * SoC specific clock gating control + */ + +#ifdef CONFIG_MACH_ARMADA_370 +static const struct mvebu_soc_descr __initconst armada_370_gating_descr[] = { + { "audio", NULL, 0 }, + { "pex0_en", NULL, 1 }, + { "pex1_en", NULL, 2 }, + { "ge1", NULL, 3 }, + { "ge0", NULL, 4 }, + { "pex0", NULL, 5 }, + { "pex1", NULL, 9 }, + { "sata0", NULL, 15 }, + { "sdio", NULL, 17 }, + { "tdm", NULL, 25 }, + { "ddr", NULL, 28 }, + { "sata1", NULL, 30 }, + { } +}; +#endif + +#ifdef CONFIG_MACH_ARMADA_XP +static const struct mvebu_soc_descr __initconst armada_xp_gating_descr[] = { + { "audio", NULL, 0 }, + { "ge3", NULL, 1 }, + { "ge2", NULL, 2 }, + { "ge1", NULL, 3 }, + { "ge0", NULL, 4 }, + { "pex0", NULL, 5 }, + { "pex1", NULL, 6 }, + { "pex2", NULL, 7 }, + { "pex3", NULL, 8 }, + { "bp", NULL, 13 }, + { "sata0lnk", NULL, 14 }, + { "sata0", "sata0lnk", 15 }, + { "lcd", NULL, 16 }, + { "sdio", NULL, 17 }, + { "usb0", NULL, 18 }, + { "usb1", NULL, 19 }, + { "usb2", NULL, 20 }, + { "xor0", NULL, 22 }, + { "crypto", NULL, 23 }, + { "tdm", NULL, 25 }, + { "xor1", NULL, 28 }, + { "sata1lnk", NULL, 29 }, + { "sata1", "sata1lnk", 30 }, + { } +}; +#endif + +#ifdef CONFIG_ARCH_DOVE +static const struct mvebu_soc_descr __initconst dove_gating_descr[] = { + { "usb0", NULL, 0 }, + { "usb1", NULL, 1 }, + { "ge", "gephy", 2 }, + { "sata", NULL, 3 }, + { "pex0", NULL, 4 }, + { "pex1", NULL, 5 }, + { "sdio0", NULL, 8 }, + { "sdio1", NULL, 9 }, + { "nand", NULL, 10 }, + { "camera", NULL, 11 }, + { "i2s0", NULL, 12 }, + { "i2s1", NULL, 13 }, + { "crypto", NULL, 15 }, + { "ac97", NULL, 21 }, + { "pdma", NULL, 22 }, + { "xor0", NULL, 23 }, + { "xor1", NULL, 24 }, + { "gephy", NULL, 30 }, + { } +}; +#endif + +#ifdef CONFIG_ARCH_KIRKWOOD +static const struct mvebu_soc_descr __initconst kirkwood_gating_descr[] = { + { "ge0", NULL, 0 }, + { "pex0", NULL, 2 }, + { "usb0", NULL, 3 }, + { "sdio", NULL, 4 }, + { "tsu", NULL, 5 }, + { "runit", NULL, 7 }, + { "xor0", NULL, 8 }, + { "audio", NULL, 9 }, + { "sata0", NULL, 14 }, + { "sata1", NULL, 15 }, + { "xor1", NULL, 16 }, + { "crypto", NULL, 17 }, + { "pex1", NULL, 18 }, + { "ge1", NULL, 19 }, + { "tdm", NULL, 20 }, + { } +}; +#endif + +static const __initdata struct of_device_id clk_gating_match[] = { +#ifdef CONFIG_MACH_ARMADA_370 + { + .compatible = "marvell,armada-370-gating-clock", + .data = armada_370_gating_descr, + }, +#endif + +#ifdef CONFIG_MACH_ARMADA_XP + { + .compatible = "marvell,armada-xp-gating-clock", + .data = armada_xp_gating_descr, + }, +#endif + +#ifdef CONFIG_ARCH_DOVE + { + .compatible = "marvell,dove-gating-clock", + .data = dove_gating_descr, + }, +#endif + +#ifdef CONFIG_ARCH_KIRKWOOD + { + .compatible = "marvell,kirkwood-gating-clock", + .data = kirkwood_gating_descr, + }, +#endif + + { } +}; + +void __init mvebu_gating_clk_init(void) +{ + struct device_node *np; + + for_each_matching_node(np, clk_gating_match) { + const struct of_device_id *match = + of_match_node(clk_gating_match, np); + mvebu_clk_gating_setup(np, + (const struct mvebu_soc_descr *)match->data); + } +} |