From ccb975c3c2afc932b30cb52e7924f9db286a962d Mon Sep 17 00:00:00 2001 From: Viresh Kumar Date: Sat, 24 Nov 2012 00:51:07 +0530 Subject: clk: Vexpress-spc: Add clock driver This patch adds spc clock controller. In Vexpress cpu cluster clock is controlled via spc controller and so it must be present in clk framework. vexpress_clk_[of_]register_spc() registers cluster clocks with and without DT. These are added as root clocks without any parents and their names are "cluster[0|1|..]". Now, platform must add clocks of all the cpus below these clusters. cpufreq driver would get cpu clock and will do clk_get[set]_rate() on cpu clock, which will then pass it to cluster clocks. And finally spc will get programmed. This patch doesn't add non-DT clocks for clusters and cpus as i don't see a user of that for now. Signed-off-by: Viresh Kumar --- drivers/clk/versatile/Makefile | 2 +- drivers/clk/versatile/clk-vexpress-spc.c | 126 +++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 drivers/clk/versatile/clk-vexpress-spc.c (limited to 'drivers/clk/versatile') diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile index ec3b88fe3e6..d1359f44043 100644 --- a/drivers/clk/versatile/Makefile +++ b/drivers/clk/versatile/Makefile @@ -4,4 +4,4 @@ obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o -obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o +obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o clk-vexpress-spc.o diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c new file mode 100644 index 00000000000..8620f54eded --- /dev/null +++ b/drivers/clk/versatile/clk-vexpress-spc.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2012 ARM Limited + * Copyright (C) 2012 Linaro + * + * Author: Viresh Kumar + * + * 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. + */ + +/* SPC clock programming interface for Vexpress cpus */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +struct clk_spc { + struct clk_hw hw; + spinlock_t *lock; + int cluster; +}; + +#define to_clk_spc(spc) container_of(spc, struct clk_spc, hw) + +static unsigned long spc_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + u32 freq; + + if (vexpress_spc_get_performance(spc->cluster, &freq)) { + return -EIO; + pr_err("%s: Failed", __func__); + } + + return freq * 1000; +} + +static long spc_round_rate(struct clk_hw *hw, unsigned long drate, + unsigned long *parent_rate) +{ + return drate; +} + +static int spc_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct clk_spc *spc = to_clk_spc(hw); + + return vexpress_spc_set_performance(spc->cluster, rate / 1000); +} + +static struct clk_ops clk_spc_ops = { + .recalc_rate = spc_recalc_rate, + .round_rate = spc_round_rate, + .set_rate = spc_set_rate, +}; + +struct clk *vexpress_clk_register_spc(const char *name, int cluster_id) +{ + struct clk_init_data init; + struct clk_spc *spc; + struct clk *clk; + + if (!name) { + pr_err("Invalid name passed"); + return ERR_PTR(-EINVAL); + } + + spc = kzalloc(sizeof(*spc), GFP_KERNEL); + if (!spc) { + pr_err("could not allocate spc clk\n"); + return ERR_PTR(-ENOMEM); + } + + spc->hw.init = &init; + spc->cluster = cluster_id; + + init.name = name; + init.ops = &clk_spc_ops; + init.flags = CLK_IS_ROOT | CLK_GET_RATE_NOCACHE; + init.num_parents = 0; + + clk = clk_register(NULL, &spc->hw); + if (!IS_ERR_OR_NULL(clk)) + return clk; + + pr_err("clk register failed\n"); + kfree(spc); + + return NULL; +} + +#if defined(CONFIG_OF) +void __init vexpress_clk_of_register_spc(void) +{ + char name[9] = "cluster"; + struct device_node *node = NULL; + struct clk *clk; + const u32 *val; + int cluster_id = 0, len; + + while ((node = of_find_node_by_name(node, "cluster"))) { + val = of_get_property(node, "reg", &len); + if (val && len == 4) + cluster_id = be32_to_cpup(val); + + name[7] = cluster_id + '0'; + clk = vexpress_clk_register_spc(name, cluster_id); + if (IS_ERR(clk)) + return; + + pr_debug("Registered clock '%s'\n", name); + clk_register_clkdev(clk, name, NULL); + } +} +CLK_OF_DECLARE(spc, "arm,spc", vexpress_clk_of_register_spc); +#endif -- cgit v1.2.3 From 264f35a56f2dd96c0626f28fca3d6b08c25e686e Mon Sep 17 00:00:00 2001 From: Mark Hambleton Date: Thu, 3 Jan 2013 15:32:11 +0000 Subject: clk: vexpress-spc: Register clock only when spc is found clk-spc depends on spc and when spc is not present in DT (ex: on fast models), we must not register clk-spc. Signed-off-by: Mark Hambleton Signed-off-by: Viresh Kumar --- drivers/clk/versatile/clk-vexpress-spc.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/clk/versatile') diff --git a/drivers/clk/versatile/clk-vexpress-spc.c b/drivers/clk/versatile/clk-vexpress-spc.c index 8620f54eded..d3f8fb44cca 100644 --- a/drivers/clk/versatile/clk-vexpress-spc.c +++ b/drivers/clk/versatile/clk-vexpress-spc.c @@ -108,6 +108,11 @@ void __init vexpress_clk_of_register_spc(void) const u32 *val; int cluster_id = 0, len; + if (!of_find_compatible_node(NULL, NULL, "arm,spc")) { + pr_debug("%s: No SPC found, Exiting!!\n", __func__); + return; + } + while ((node = of_find_node_by_name(node, "cluster"))) { val = of_get_property(node, "reg", &len); if (val && len == 4) -- cgit v1.2.3 From d474fa5597a521fc60fd0563cc63514a4824673c Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Tue, 29 Jan 2013 16:30:22 +0000 Subject: clk: vexpress: Add separate SP810 driver Split SP810 clocking code into separate driver, selecting better (faster) parent at clk_prepare() time. This is to avoid problems with clock infrastructure initialization order, in particular to avoid dependency of fixed clock being initialized before SP810. Signed-off-by: Pawel Moll Signed-off-by: Catalin Marinas --- drivers/clk/versatile/Makefile | 2 +- drivers/clk/versatile/clk-sp810.c | 172 +++++++++++++++++++++++++++++++++++ drivers/clk/versatile/clk-vexpress.c | 96 ++++--------------- 3 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 drivers/clk/versatile/clk-sp810.c (limited to 'drivers/clk/versatile') diff --git a/drivers/clk/versatile/Makefile b/drivers/clk/versatile/Makefile index ec3b88fe3e6..c16ca787170 100644 --- a/drivers/clk/versatile/Makefile +++ b/drivers/clk/versatile/Makefile @@ -3,5 +3,5 @@ obj-$(CONFIG_ICST) += clk-icst.o obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o -obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o +obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o clk-sp810.o obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o diff --git a/drivers/clk/versatile/clk-sp810.c b/drivers/clk/versatile/clk-sp810.c new file mode 100644 index 00000000000..20399f9c1c5 --- /dev/null +++ b/drivers/clk/versatile/clk-sp810.c @@ -0,0 +1,172 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include +#include +#include +#include +#include +#include + +#define to_clk_sp810_timerclken(_hw) \ + container_of(_hw, struct clk_sp810_timerclken, hw) + +struct clk_sp810; + +struct clk_sp810_timerclken { + struct clk_hw hw; + struct clk *clk; + struct clk_sp810 *sp810; + int channel; +}; + +struct clk_sp810 { + struct device_node *node; + int refclk_index, timclk_index; + void __iomem *base; + spinlock_t lock; + struct clk_sp810_timerclken timerclken[4]; +}; + +static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + u32 val = readl(timerclken->sp810->base + SCCTRL); + + return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel))); +} + +static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + struct clk_sp810 *sp810 = timerclken->sp810; + u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel); + unsigned long flags = 0; + + if (WARN_ON(index > 1)) + return -EINVAL; + + spin_lock_irqsave(&sp810->lock, flags); + + val = readl(sp810->base + SCCTRL); + val &= ~(1 << shift); + val |= index << shift; + writel(val, sp810->base + SCCTRL); + + spin_unlock_irqrestore(&sp810->lock, flags); + + return 0; +} + +static int clk_sp810_timerclken_prepare(struct clk_hw *hw) +{ + struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw); + struct clk_sp810 *sp810 = timerclken->sp810; + struct clk *refclk = of_clk_get(sp810->node, sp810->refclk_index); + struct clk *timclk = of_clk_get(sp810->node, sp810->timclk_index); + struct clk *old_parent = __clk_get_parent(hw->clk); + struct clk *new_parent = old_parent; + int new_parent_index; + + if (WARN_ON(IS_ERR(refclk) || IS_ERR(timclk))) + return -ENOENT; + + /* Select "better" (faster) parent */ + if (__clk_get_rate(refclk) > __clk_get_rate(timclk)) { + new_parent = refclk; + new_parent_index = 0; + } else { + new_parent = timclk; + new_parent_index = 1; + } + + /* Switch the parent if necessary */ + if (old_parent != new_parent) { + __clk_prepare(new_parent); + clk_sp810_timerclken_set_parent(hw, new_parent_index); + __clk_reparent(hw->clk, new_parent); + __clk_unprepare(old_parent); + } + + return 0; +} + +static const struct clk_ops clk_sp810_timerclken_ops = { + .prepare = clk_sp810_timerclken_prepare, + .get_parent = clk_sp810_timerclken_get_parent, + .set_parent = clk_sp810_timerclken_set_parent, +}; + +struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec, + void *data) +{ + struct clk_sp810 *sp810 = data; + + if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] > + ARRAY_SIZE(sp810->timerclken))) + return NULL; + + return sp810->timerclken[clkspec->args[0]].clk; +} + +void __init clk_sp810_of_setup(struct device_node *node) +{ + struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL); + const char *parent_names[2]; + char name[12]; + struct clk_init_data init; + int i; + + if (!sp810) { + pr_err("Failed to allocate memory for SP810!\n"); + return; + } + + sp810->refclk_index = of_property_match_string(node, "clock-names", + "refclk"); + parent_names[0] = of_clk_get_parent_name(node, sp810->refclk_index); + + sp810->timclk_index = of_property_match_string(node, "clock-names", + "timclk"); + parent_names[1] = of_clk_get_parent_name(node, sp810->timclk_index); + + if (!parent_names[0] || !parent_names[1]) { + pr_warn("Failed to obtain parent clocks for SP810!\n"); + return; + } + + sp810->node = node; + sp810->base = of_iomap(node, 0); + spin_lock_init(&sp810->lock); + + init.name = name; + init.ops = &clk_sp810_timerclken_ops; + init.flags = CLK_IS_BASIC; + init.parent_names = parent_names; + init.num_parents = ARRAY_SIZE(parent_names); + + for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) { + snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); + + sp810->timerclken[i].sp810 = sp810; + sp810->timerclken[i].channel = i; + sp810->timerclken[i].hw.init = &init; + + sp810->timerclken[i].clk = clk_register(NULL, + &sp810->timerclken[i].hw); + WARN_ON(IS_ERR(sp810->timerclken[i].clk)); + } + + of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810); +} +CLK_OF_DECLARE(vexpress_soc, "arm,sp810", clk_sp810_of_setup); diff --git a/drivers/clk/versatile/clk-vexpress.c b/drivers/clk/versatile/clk-vexpress.c index 82b45aad8cc..187306e104c 100644 --- a/drivers/clk/versatile/clk-vexpress.c +++ b/drivers/clk/versatile/clk-vexpress.c @@ -16,39 +16,10 @@ #include #include #include -#include #include -static struct clk *vexpress_sp810_timerclken[4]; static DEFINE_SPINLOCK(vexpress_sp810_lock); -static void __init vexpress_sp810_init(void __iomem *base) -{ - int i; - - if (WARN_ON(!base)) - return; - - for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) { - char name[12]; - const char *parents[] = { - "v2m:refclk32khz", /* REFCLK */ - "v2m:refclk1mhz" /* TIMCLK */ - }; - - snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); - - vexpress_sp810_timerclken[i] = clk_register_mux(NULL, name, - parents, 2, 0, base + SCCTRL, - SCCTRL_TIMERENnSEL_SHIFT(i), 1, - 0, &vexpress_sp810_lock); - - if (WARN_ON(IS_ERR(vexpress_sp810_timerclken[i]))) - break; - } -} - - static const char * const vexpress_clk_24mhz_periphs[] __initconst = { "mb:uart0", "mb:uart1", "mb:uart2", "mb:uart3", "mb:mmci", "mb:kmi0", "mb:kmi1" @@ -57,6 +28,11 @@ static const char * const vexpress_clk_24mhz_periphs[] __initconst = { void __init vexpress_clk_init(void __iomem *sp810_base) { struct clk *clk; + const char *sp810_parent_names[] = { + "v2m:refclk32khz", /* REFCLK */ + "v2m:refclk1mhz" /* TIMCLK */ + }; + struct clk *sp810_parent; int i; clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, @@ -73,63 +49,29 @@ void __init vexpress_clk_init(void __iomem *sp810_base) CLK_IS_ROOT, 32768); WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); - clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, + sp810_parent = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, CLK_IS_ROOT, 1000000); - vexpress_sp810_init(sp810_base); - - for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) - WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); - - WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], - "v2m-timer0", "sp804")); - WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], - "v2m-timer1", "sp804")); + clk = clk_register_mux(NULL, "timerclken0", + sp810_parent_names, 2, 0, sp810_base + SCCTRL, + SCCTRL_TIMERENnSEL_SHIFT(0), 1, + 0, &vexpress_sp810_lock); + WARN_ON(clk_set_parent(clk, sp810_parent)); + WARN_ON(clk_register_clkdev(clk, "v2m-timer0", "sp804")); + + clk = clk_register_mux(NULL, "timerclken1", + sp810_parent_names, 2, 0, sp810_base + SCCTRL, + SCCTRL_TIMERENnSEL_SHIFT(1), 1, + 0, &vexpress_sp810_lock); + WARN_ON(clk_set_parent(clk, sp810_parent)); + WARN_ON(clk_register_clkdev(clk, "v2m-timer1", "sp804")); } #if defined(CONFIG_OF) -struct clk *vexpress_sp810_of_get(struct of_phandle_args *clkspec, void *data) -{ - if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] > - ARRAY_SIZE(vexpress_sp810_timerclken))) - return NULL; - - return vexpress_sp810_timerclken[clkspec->args[0]]; -} - void __init vexpress_clk_of_init(void) { - struct device_node *node; - struct clk *clk; - struct clk *refclk, *timclk; - of_clk_init(NULL); - - node = of_find_compatible_node(NULL, NULL, "arm,sp810"); - vexpress_sp810_init(of_iomap(node, 0)); - of_clk_add_provider(node, vexpress_sp810_of_get, NULL); - - /* Select "better" (faster) parent for SP804 timers */ - refclk = of_clk_get_by_name(node, "refclk"); - timclk = of_clk_get_by_name(node, "timclk"); - if (!WARN_ON(IS_ERR(refclk) || IS_ERR(timclk))) { - int i = 0; - - if (clk_get_rate(refclk) > clk_get_rate(timclk)) - clk = refclk; - else - clk = timclk; - - for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) - WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], - clk)); - } - - WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], - "v2m-timer0", "sp804")); - WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], - "v2m-timer1", "sp804")); } #endif -- cgit v1.2.3