Skip to content

Commit 6e973d2

Browse files
pawelmollMike Turquette
authored andcommitted
clk: vexpress: Add separate SP810 driver
Factor out the SP810 clocking code into a separate driver, selecting better (faster) parent at clk_prepare() time. This is to avoid problems with clocking infrastructure initialisation order, in particular to avoid dependency of fixed clock being initialized before SP810. It also makes vexpress platform OF-based clock initialisation code unnecessary. Signed-off-by: Pawel Moll <pawel.moll@arm.com> Tested-by: Catalin Marinas <catalin.marinas@arm.com> Signed-off-by: Mike Turquette <mturquette@linaro.org> [mturquette@linaro.org: add .unprepare, FIXME comment, cleaned up code]
1 parent 476ba5f commit 6e973d2

File tree

4 files changed

+196
-51
lines changed

4 files changed

+196
-51
lines changed

arch/arm/mach-vexpress/v2m.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <linux/regulator/fixed.h>
2222
#include <linux/regulator/machine.h>
2323
#include <linux/vexpress.h>
24+
#include <linux/clk-provider.h>
25+
#include <linux/clkdev.h>
2426

2527
#include <asm/arch_timer.h>
2628
#include <asm/mach-types.h>
@@ -433,14 +435,18 @@ static void __init v2m_dt_timer_init(void)
433435
{
434436
struct device_node *node = NULL;
435437

436-
vexpress_clk_of_init();
438+
of_clk_init(NULL);
437439

438440
do {
439441
node = of_find_compatible_node(node, NULL, "arm,sp804");
440442
} while (node && vexpress_get_site_by_node(node) != VEXPRESS_SITE_MB);
441443
if (node) {
442444
pr_info("Using SP804 '%s' as a clock & events source\n",
443445
node->full_name);
446+
WARN_ON(clk_register_clkdev(of_clk_get_by_name(node,
447+
"timclken1"), "v2m-timer0", "sp804"));
448+
WARN_ON(clk_register_clkdev(of_clk_get_by_name(node,
449+
"timclken2"), "v2m-timer1", "sp804"));
444450
v2m_sp804_init(of_iomap(node, 0),
445451
irq_of_parse_and_map(node, 0));
446452
}

drivers/clk/versatile/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ obj-$(CONFIG_ICST) += clk-icst.o
33
obj-$(CONFIG_ARCH_INTEGRATOR) += clk-integrator.o
44
obj-$(CONFIG_INTEGRATOR_IMPD1) += clk-impd1.o
55
obj-$(CONFIG_ARCH_REALVIEW) += clk-realview.o
6-
obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o
6+
obj-$(CONFIG_ARCH_VEXPRESS) += clk-vexpress.o clk-sp810.o
77
obj-$(CONFIG_VEXPRESS_CONFIG) += clk-vexpress-osc.o

drivers/clk/versatile/clk-sp810.c

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*
2+
* This program is free software; you can redistribute it and/or modify
3+
* it under the terms of the GNU General Public License version 2 as
4+
* published by the Free Software Foundation.
5+
*
6+
* This program is distributed in the hope that it will be useful,
7+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9+
* GNU General Public License for more details.
10+
*
11+
* Copyright (C) 2013 ARM Limited
12+
*/
13+
14+
#include <linux/amba/sp810.h>
15+
#include <linux/clkdev.h>
16+
#include <linux/clk-provider.h>
17+
#include <linux/err.h>
18+
#include <linux/of.h>
19+
#include <linux/of_address.h>
20+
21+
#define to_clk_sp810_timerclken(_hw) \
22+
container_of(_hw, struct clk_sp810_timerclken, hw)
23+
24+
struct clk_sp810;
25+
26+
struct clk_sp810_timerclken {
27+
struct clk_hw hw;
28+
struct clk *clk;
29+
struct clk_sp810 *sp810;
30+
int channel;
31+
};
32+
33+
struct clk_sp810 {
34+
struct device_node *node;
35+
int refclk_index, timclk_index;
36+
void __iomem *base;
37+
spinlock_t lock;
38+
struct clk_sp810_timerclken timerclken[4];
39+
struct clk *refclk;
40+
struct clk *timclk;
41+
};
42+
43+
static u8 clk_sp810_timerclken_get_parent(struct clk_hw *hw)
44+
{
45+
struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
46+
u32 val = readl(timerclken->sp810->base + SCCTRL);
47+
48+
return !!(val & (1 << SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel)));
49+
}
50+
51+
static int clk_sp810_timerclken_set_parent(struct clk_hw *hw, u8 index)
52+
{
53+
struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
54+
struct clk_sp810 *sp810 = timerclken->sp810;
55+
u32 val, shift = SCCTRL_TIMERENnSEL_SHIFT(timerclken->channel);
56+
unsigned long flags = 0;
57+
58+
if (WARN_ON(index > 1))
59+
return -EINVAL;
60+
61+
spin_lock_irqsave(&sp810->lock, flags);
62+
63+
val = readl(sp810->base + SCCTRL);
64+
val &= ~(1 << shift);
65+
val |= index << shift;
66+
writel(val, sp810->base + SCCTRL);
67+
68+
spin_unlock_irqrestore(&sp810->lock, flags);
69+
70+
return 0;
71+
}
72+
73+
/*
74+
* FIXME - setting the parent every time .prepare is invoked is inefficient.
75+
* This is better handled by a dedicated clock tree configuration mechanism at
76+
* init-time. Revisit this later when such a mechanism exists
77+
*/
78+
static int clk_sp810_timerclken_prepare(struct clk_hw *hw)
79+
{
80+
struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
81+
struct clk_sp810 *sp810 = timerclken->sp810;
82+
struct clk *old_parent = __clk_get_parent(hw->clk);
83+
struct clk *new_parent;
84+
85+
if (!sp810->refclk)
86+
sp810->refclk = of_clk_get(sp810->node, sp810->refclk_index);
87+
88+
if (!sp810->timclk)
89+
sp810->timclk = of_clk_get(sp810->node, sp810->timclk_index);
90+
91+
if (WARN_ON(IS_ERR(sp810->refclk) || IS_ERR(sp810->timclk)))
92+
return -ENOENT;
93+
94+
/* Select fastest parent */
95+
if (clk_get_rate(sp810->refclk) > clk_get_rate(sp810->timclk))
96+
new_parent = sp810->refclk;
97+
else
98+
new_parent = sp810->timclk;
99+
100+
/* Switch the parent if necessary */
101+
if (old_parent != new_parent) {
102+
clk_prepare(new_parent);
103+
clk_set_parent(hw->clk, new_parent);
104+
clk_unprepare(old_parent);
105+
}
106+
107+
return 0;
108+
}
109+
110+
static void clk_sp810_timerclken_unprepare(struct clk_hw *hw)
111+
{
112+
struct clk_sp810_timerclken *timerclken = to_clk_sp810_timerclken(hw);
113+
struct clk_sp810 *sp810 = timerclken->sp810;
114+
115+
clk_put(sp810->timclk);
116+
clk_put(sp810->refclk);
117+
}
118+
119+
static const struct clk_ops clk_sp810_timerclken_ops = {
120+
.prepare = clk_sp810_timerclken_prepare,
121+
.unprepare = clk_sp810_timerclken_unprepare,
122+
.get_parent = clk_sp810_timerclken_get_parent,
123+
.set_parent = clk_sp810_timerclken_set_parent,
124+
};
125+
126+
struct clk *clk_sp810_timerclken_of_get(struct of_phandle_args *clkspec,
127+
void *data)
128+
{
129+
struct clk_sp810 *sp810 = data;
130+
131+
if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] >
132+
ARRAY_SIZE(sp810->timerclken)))
133+
return NULL;
134+
135+
return sp810->timerclken[clkspec->args[0]].clk;
136+
}
137+
138+
void __init clk_sp810_of_setup(struct device_node *node)
139+
{
140+
struct clk_sp810 *sp810 = kzalloc(sizeof(*sp810), GFP_KERNEL);
141+
const char *parent_names[2];
142+
char name[12];
143+
struct clk_init_data init;
144+
int i;
145+
146+
if (!sp810) {
147+
pr_err("Failed to allocate memory for SP810!\n");
148+
return;
149+
}
150+
151+
sp810->refclk_index = of_property_match_string(node, "clock-names",
152+
"refclk");
153+
parent_names[0] = of_clk_get_parent_name(node, sp810->refclk_index);
154+
155+
sp810->timclk_index = of_property_match_string(node, "clock-names",
156+
"timclk");
157+
parent_names[1] = of_clk_get_parent_name(node, sp810->timclk_index);
158+
159+
if (parent_names[0] <= 0 || parent_names[1] <= 0) {
160+
pr_warn("Failed to obtain parent clocks for SP810!\n");
161+
return;
162+
}
163+
164+
sp810->node = node;
165+
sp810->base = of_iomap(node, 0);
166+
spin_lock_init(&sp810->lock);
167+
168+
init.name = name;
169+
init.ops = &clk_sp810_timerclken_ops;
170+
init.flags = CLK_IS_BASIC;
171+
init.parent_names = parent_names;
172+
init.num_parents = ARRAY_SIZE(parent_names);
173+
174+
for (i = 0; i < ARRAY_SIZE(sp810->timerclken); i++) {
175+
snprintf(name, ARRAY_SIZE(name), "timerclken%d", i);
176+
177+
sp810->timerclken[i].sp810 = sp810;
178+
sp810->timerclken[i].channel = i;
179+
sp810->timerclken[i].hw.init = &init;
180+
181+
sp810->timerclken[i].clk = clk_register(NULL,
182+
&sp810->timerclken[i].hw);
183+
WARN_ON(IS_ERR(sp810->timerclken[i].clk));
184+
}
185+
186+
of_clk_add_provider(node, clk_sp810_timerclken_of_get, sp810);
187+
}
188+
CLK_OF_DECLARE(sp810, "arm,sp810", clk_sp810_of_setup);

drivers/clk/versatile/clk-vexpress.c

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
#include <linux/clkdev.h>
1616
#include <linux/clk-provider.h>
1717
#include <linux/err.h>
18-
#include <linux/of.h>
19-
#include <linux/of_address.h>
2018
#include <linux/vexpress.h>
2119

2220
static struct clk *vexpress_sp810_timerclken[4];
@@ -86,50 +84,3 @@ void __init vexpress_clk_init(void __iomem *sp810_base)
8684
WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1],
8785
"v2m-timer1", "sp804"));
8886
}
89-
90-
#if defined(CONFIG_OF)
91-
92-
struct clk *vexpress_sp810_of_get(struct of_phandle_args *clkspec, void *data)
93-
{
94-
if (WARN_ON(clkspec->args_count != 1 || clkspec->args[0] >
95-
ARRAY_SIZE(vexpress_sp810_timerclken)))
96-
return NULL;
97-
98-
return vexpress_sp810_timerclken[clkspec->args[0]];
99-
}
100-
101-
void __init vexpress_clk_of_init(void)
102-
{
103-
struct device_node *node;
104-
struct clk *clk;
105-
struct clk *refclk, *timclk;
106-
107-
of_clk_init(NULL);
108-
109-
node = of_find_compatible_node(NULL, NULL, "arm,sp810");
110-
vexpress_sp810_init(of_iomap(node, 0));
111-
of_clk_add_provider(node, vexpress_sp810_of_get, NULL);
112-
113-
/* Select "better" (faster) parent for SP804 timers */
114-
refclk = of_clk_get_by_name(node, "refclk");
115-
timclk = of_clk_get_by_name(node, "timclk");
116-
if (!WARN_ON(IS_ERR(refclk) || IS_ERR(timclk))) {
117-
int i = 0;
118-
119-
if (clk_get_rate(refclk) > clk_get_rate(timclk))
120-
clk = refclk;
121-
else
122-
clk = timclk;
123-
124-
for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++)
125-
WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i],
126-
clk));
127-
}
128-
129-
WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0],
130-
"v2m-timer0", "sp804"));
131-
WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1],
132-
"v2m-timer1", "sp804"));
133-
}
134-
135-
#endif

0 commit comments

Comments
 (0)