Commit | Line | Data |
---|---|---|
bcd6f569 PM |
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) 2012 ARM Limited | |
12 | */ | |
13 | ||
da660b4a | 14 | #include <linux/amba/sp810.h> |
bcd6f569 PM |
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 | #include <linux/vexpress.h> | |
21 | ||
bcd6f569 PM |
22 | static struct clk *vexpress_sp810_timerclken[4]; |
23 | static DEFINE_SPINLOCK(vexpress_sp810_lock); | |
24 | ||
25 | static void __init vexpress_sp810_init(void __iomem *base) | |
26 | { | |
27 | int i; | |
28 | ||
29 | if (WARN_ON(!base)) | |
30 | return; | |
31 | ||
32 | for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) { | |
33 | char name[12]; | |
34 | const char *parents[] = { | |
35 | "v2m:refclk32khz", /* REFCLK */ | |
36 | "v2m:refclk1mhz" /* TIMCLK */ | |
37 | }; | |
38 | ||
39 | snprintf(name, ARRAY_SIZE(name), "timerclken%d", i); | |
40 | ||
41 | vexpress_sp810_timerclken[i] = clk_register_mux(NULL, name, | |
42 | parents, 2, 0, base + SCCTRL, | |
43 | SCCTRL_TIMERENnSEL_SHIFT(i), 1, | |
44 | 0, &vexpress_sp810_lock); | |
45 | ||
46 | if (WARN_ON(IS_ERR(vexpress_sp810_timerclken[i]))) | |
47 | break; | |
48 | } | |
49 | } | |
50 | ||
51 | ||
52 | static const char * const vexpress_clk_24mhz_periphs[] __initconst = { | |
53 | "mb:uart0", "mb:uart1", "mb:uart2", "mb:uart3", | |
54 | "mb:mmci", "mb:kmi0", "mb:kmi1" | |
55 | }; | |
56 | ||
57 | void __init vexpress_clk_init(void __iomem *sp810_base) | |
58 | { | |
59 | struct clk *clk; | |
60 | int i; | |
61 | ||
62 | clk = clk_register_fixed_rate(NULL, "dummy_apb_pclk", NULL, | |
63 | CLK_IS_ROOT, 0); | |
64 | WARN_ON(clk_register_clkdev(clk, "apb_pclk", NULL)); | |
65 | ||
66 | clk = clk_register_fixed_rate(NULL, "v2m:clk_24mhz", NULL, | |
67 | CLK_IS_ROOT, 24000000); | |
68 | for (i = 0; i < ARRAY_SIZE(vexpress_clk_24mhz_periphs); i++) | |
69 | WARN_ON(clk_register_clkdev(clk, NULL, | |
70 | vexpress_clk_24mhz_periphs[i])); | |
71 | ||
72 | clk = clk_register_fixed_rate(NULL, "v2m:refclk32khz", NULL, | |
73 | CLK_IS_ROOT, 32768); | |
74 | WARN_ON(clk_register_clkdev(clk, NULL, "v2m:wdt")); | |
75 | ||
76 | clk = clk_register_fixed_rate(NULL, "v2m:refclk1mhz", NULL, | |
77 | CLK_IS_ROOT, 1000000); | |
78 | ||
79 | vexpress_sp810_init(sp810_base); | |
80 | ||
81 | for (i = 0; i < ARRAY_SIZE(vexpress_sp810_timerclken); i++) | |
82 | WARN_ON(clk_set_parent(vexpress_sp810_timerclken[i], clk)); | |
83 | ||
84 | WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[0], | |
85 | "v2m-timer0", "sp804")); | |
86 | WARN_ON(clk_register_clkdev(vexpress_sp810_timerclken[1], | |
87 | "v2m-timer1", "sp804")); | |
88 | } | |
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 | ||
bcd6f569 PM |
101 | void __init vexpress_clk_of_init(void) |
102 | { | |
103 | struct device_node *node; | |
104 | struct clk *clk; | |
105 | struct clk *refclk, *timclk; | |
106 | ||
8ae5ac56 | 107 | of_clk_init(NULL); |
bcd6f569 PM |
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 |