Commit | Line | Data |
---|---|---|
600f7cfe BS |
1 | /* arch/arm/mach-msm/clock.c |
2 | * | |
3 | * Copyright (C) 2007 Google, Inc. | |
5e96da5d | 4 | * Copyright (c) 2007-2010, Code Aurora Forum. All rights reserved. |
600f7cfe BS |
5 | * |
6 | * This software is licensed under the terms of the GNU General Public | |
7 | * License version 2, as published by the Free Software Foundation, and | |
8 | * may be copied, distributed, and modified under those terms. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU General Public License for more details. | |
14 | * | |
15 | */ | |
16 | ||
600f7cfe BS |
17 | #include <linux/kernel.h> |
18 | #include <linux/init.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/list.h> | |
21 | #include <linux/err.h> | |
22 | #include <linux/clk.h> | |
23 | #include <linux/spinlock.h> | |
5e96da5d DW |
24 | #include <linux/debugfs.h> |
25 | #include <linux/ctype.h> | |
26 | #include <linux/pm_qos_params.h> | |
27 | #include <mach/clk.h> | |
600f7cfe BS |
28 | |
29 | #include "clock.h" | |
30 | #include "proc_comm.h" | |
37a298fb | 31 | #include "clock-7x30.h" |
600f7cfe BS |
32 | |
33 | static DEFINE_MUTEX(clocks_mutex); | |
34 | static DEFINE_SPINLOCK(clocks_lock); | |
35 | static LIST_HEAD(clocks); | |
5e96da5d DW |
36 | struct clk *msm_clocks; |
37 | unsigned msm_num_clocks; | |
600f7cfe | 38 | |
600f7cfe BS |
39 | /* |
40 | * Standard clock functions defined in include/linux/clk.h | |
41 | */ | |
42 | struct clk *clk_get(struct device *dev, const char *id) | |
43 | { | |
44 | struct clk *clk; | |
45 | ||
46 | mutex_lock(&clocks_mutex); | |
47 | ||
48 | list_for_each_entry(clk, &clocks, list) | |
49 | if (!strcmp(id, clk->name) && clk->dev == dev) | |
50 | goto found_it; | |
51 | ||
52 | list_for_each_entry(clk, &clocks, list) | |
53 | if (!strcmp(id, clk->name) && clk->dev == NULL) | |
54 | goto found_it; | |
55 | ||
56 | clk = ERR_PTR(-ENOENT); | |
57 | found_it: | |
58 | mutex_unlock(&clocks_mutex); | |
59 | return clk; | |
60 | } | |
61 | EXPORT_SYMBOL(clk_get); | |
62 | ||
63 | void clk_put(struct clk *clk) | |
64 | { | |
65 | } | |
66 | EXPORT_SYMBOL(clk_put); | |
67 | ||
68 | int clk_enable(struct clk *clk) | |
69 | { | |
70 | unsigned long flags; | |
71 | spin_lock_irqsave(&clocks_lock, flags); | |
72 | clk->count++; | |
437f629d | 73 | if (clk->count == 1) |
5e96da5d | 74 | clk->ops->enable(clk->id); |
600f7cfe BS |
75 | spin_unlock_irqrestore(&clocks_lock, flags); |
76 | return 0; | |
77 | } | |
78 | EXPORT_SYMBOL(clk_enable); | |
79 | ||
80 | void clk_disable(struct clk *clk) | |
81 | { | |
82 | unsigned long flags; | |
83 | spin_lock_irqsave(&clocks_lock, flags); | |
84 | BUG_ON(clk->count == 0); | |
85 | clk->count--; | |
437f629d | 86 | if (clk->count == 0) |
5e96da5d | 87 | clk->ops->disable(clk->id); |
600f7cfe BS |
88 | spin_unlock_irqrestore(&clocks_lock, flags); |
89 | } | |
90 | EXPORT_SYMBOL(clk_disable); | |
91 | ||
5e96da5d DW |
92 | int clk_reset(struct clk *clk, enum clk_reset_action action) |
93 | { | |
94 | if (!clk->ops->reset) | |
95 | clk->ops->reset = &pc_clk_reset; | |
96 | return clk->ops->reset(clk->remote_id, action); | |
97 | } | |
98 | EXPORT_SYMBOL(clk_reset); | |
99 | ||
600f7cfe BS |
100 | unsigned long clk_get_rate(struct clk *clk) |
101 | { | |
5e96da5d | 102 | return clk->ops->get_rate(clk->id); |
600f7cfe BS |
103 | } |
104 | EXPORT_SYMBOL(clk_get_rate); | |
105 | ||
106 | int clk_set_rate(struct clk *clk, unsigned long rate) | |
107 | { | |
3a790bbe DW |
108 | int ret; |
109 | if (clk->flags & CLKFLAG_MAX) { | |
110 | ret = clk->ops->set_max_rate(clk->id, rate); | |
111 | if (ret) | |
112 | return ret; | |
113 | } | |
114 | if (clk->flags & CLKFLAG_MIN) { | |
115 | ret = clk->ops->set_min_rate(clk->id, rate); | |
116 | if (ret) | |
117 | return ret; | |
118 | } | |
119 | ||
120 | if (clk->flags & CLKFLAG_MAX || clk->flags & CLKFLAG_MIN) | |
121 | return ret; | |
122 | ||
5e96da5d | 123 | return clk->ops->set_rate(clk->id, rate); |
600f7cfe BS |
124 | } |
125 | EXPORT_SYMBOL(clk_set_rate); | |
126 | ||
5e96da5d DW |
127 | long clk_round_rate(struct clk *clk, unsigned long rate) |
128 | { | |
129 | return clk->ops->round_rate(clk->id, rate); | |
130 | } | |
131 | EXPORT_SYMBOL(clk_round_rate); | |
132 | ||
133 | int clk_set_min_rate(struct clk *clk, unsigned long rate) | |
134 | { | |
135 | return clk->ops->set_min_rate(clk->id, rate); | |
136 | } | |
137 | EXPORT_SYMBOL(clk_set_min_rate); | |
138 | ||
139 | int clk_set_max_rate(struct clk *clk, unsigned long rate) | |
140 | { | |
141 | return clk->ops->set_max_rate(clk->id, rate); | |
142 | } | |
143 | EXPORT_SYMBOL(clk_set_max_rate); | |
144 | ||
600f7cfe BS |
145 | int clk_set_parent(struct clk *clk, struct clk *parent) |
146 | { | |
147 | return -ENOSYS; | |
148 | } | |
149 | EXPORT_SYMBOL(clk_set_parent); | |
150 | ||
151 | struct clk *clk_get_parent(struct clk *clk) | |
152 | { | |
153 | return ERR_PTR(-ENOSYS); | |
154 | } | |
155 | EXPORT_SYMBOL(clk_get_parent); | |
156 | ||
157 | int clk_set_flags(struct clk *clk, unsigned long flags) | |
158 | { | |
159 | if (clk == NULL || IS_ERR(clk)) | |
160 | return -EINVAL; | |
5e96da5d | 161 | return clk->ops->set_flags(clk->id, flags); |
600f7cfe BS |
162 | } |
163 | EXPORT_SYMBOL(clk_set_flags); | |
164 | ||
5e96da5d DW |
165 | /* EBI1 is the only shared clock that several clients want to vote on as of |
166 | * this commit. If this changes in the future, then it might be better to | |
167 | * make clk_min_rate handle the voting or make ebi1_clk_set_min_rate more | |
168 | * generic to support different clocks. | |
169 | */ | |
170 | static struct clk *ebi1_clk; | |
600f7cfe | 171 | |
5e96da5d DW |
172 | static void __init set_clock_ops(struct clk *clk) |
173 | { | |
174 | if (!clk->ops) { | |
175 | clk->ops = &clk_ops_pcom; | |
176 | clk->id = clk->remote_id; | |
177 | } | |
178 | } | |
179 | ||
180 | void __init msm_clock_init(struct clk *clock_tbl, unsigned num_clocks) | |
600f7cfe BS |
181 | { |
182 | unsigned n; | |
183 | ||
600f7cfe | 184 | mutex_lock(&clocks_mutex); |
5e96da5d DW |
185 | msm_clocks = clock_tbl; |
186 | msm_num_clocks = num_clocks; | |
187 | for (n = 0; n < msm_num_clocks; n++) { | |
188 | set_clock_ops(&msm_clocks[n]); | |
600f7cfe | 189 | list_add_tail(&msm_clocks[n].list, &clocks); |
5e96da5d | 190 | } |
600f7cfe | 191 | mutex_unlock(&clocks_mutex); |
5e96da5d DW |
192 | |
193 | ebi1_clk = clk_get(NULL, "ebi1_clk"); | |
194 | BUG_ON(ebi1_clk == NULL); | |
195 | ||
196 | } | |
197 | ||
198 | #if defined(CONFIG_DEBUG_FS) | |
199 | static struct clk *msm_clock_get_nth(unsigned index) | |
200 | { | |
201 | if (index < msm_num_clocks) | |
202 | return msm_clocks + index; | |
203 | else | |
204 | return 0; | |
205 | } | |
206 | ||
207 | static int clock_debug_rate_set(void *data, u64 val) | |
208 | { | |
209 | struct clk *clock = data; | |
210 | int ret; | |
211 | ||
212 | /* Only increases to max rate will succeed, but that's actually good | |
213 | * for debugging purposes. So we don't check for error. */ | |
214 | if (clock->flags & CLK_MAX) | |
215 | clk_set_max_rate(clock, val); | |
216 | if (clock->flags & CLK_MIN) | |
217 | ret = clk_set_min_rate(clock, val); | |
218 | else | |
219 | ret = clk_set_rate(clock, val); | |
220 | if (ret != 0) | |
221 | printk(KERN_ERR "clk_set%s_rate failed (%d)\n", | |
222 | (clock->flags & CLK_MIN) ? "_min" : "", ret); | |
223 | return ret; | |
224 | } | |
225 | ||
226 | static int clock_debug_rate_get(void *data, u64 *val) | |
227 | { | |
228 | struct clk *clock = data; | |
229 | *val = clk_get_rate(clock); | |
230 | return 0; | |
231 | } | |
232 | ||
233 | static int clock_debug_enable_set(void *data, u64 val) | |
234 | { | |
235 | struct clk *clock = data; | |
236 | int rc = 0; | |
237 | ||
238 | if (val) | |
239 | rc = clock->ops->enable(clock->id); | |
240 | else | |
241 | clock->ops->disable(clock->id); | |
242 | ||
243 | return rc; | |
600f7cfe BS |
244 | } |
245 | ||
5e96da5d DW |
246 | static int clock_debug_enable_get(void *data, u64 *val) |
247 | { | |
248 | struct clk *clock = data; | |
249 | ||
250 | *val = clock->ops->is_enabled(clock->id); | |
251 | ||
252 | return 0; | |
253 | } | |
254 | ||
255 | static int clock_debug_local_get(void *data, u64 *val) | |
256 | { | |
257 | struct clk *clock = data; | |
258 | ||
259 | *val = clock->ops != &clk_ops_pcom; | |
260 | ||
261 | return 0; | |
262 | } | |
263 | ||
264 | DEFINE_SIMPLE_ATTRIBUTE(clock_rate_fops, clock_debug_rate_get, | |
265 | clock_debug_rate_set, "%llu\n"); | |
266 | DEFINE_SIMPLE_ATTRIBUTE(clock_enable_fops, clock_debug_enable_get, | |
267 | clock_debug_enable_set, "%llu\n"); | |
268 | DEFINE_SIMPLE_ATTRIBUTE(clock_local_fops, clock_debug_local_get, | |
269 | NULL, "%llu\n"); | |
270 | ||
271 | static int __init clock_debug_init(void) | |
272 | { | |
273 | struct dentry *dent_rate, *dent_enable, *dent_local; | |
274 | struct clk *clock; | |
275 | unsigned n = 0; | |
276 | char temp[50], *ptr; | |
277 | ||
278 | dent_rate = debugfs_create_dir("clk_rate", 0); | |
279 | if (IS_ERR(dent_rate)) | |
280 | return PTR_ERR(dent_rate); | |
281 | ||
282 | dent_enable = debugfs_create_dir("clk_enable", 0); | |
283 | if (IS_ERR(dent_enable)) | |
284 | return PTR_ERR(dent_enable); | |
285 | ||
286 | dent_local = debugfs_create_dir("clk_local", NULL); | |
287 | if (IS_ERR(dent_local)) | |
288 | return PTR_ERR(dent_local); | |
289 | ||
290 | while ((clock = msm_clock_get_nth(n++)) != 0) { | |
291 | strncpy(temp, clock->dbg_name, ARRAY_SIZE(temp)-1); | |
292 | for (ptr = temp; *ptr; ptr++) | |
293 | *ptr = tolower(*ptr); | |
294 | debugfs_create_file(temp, 0644, dent_rate, | |
295 | clock, &clock_rate_fops); | |
296 | debugfs_create_file(temp, 0644, dent_enable, | |
297 | clock, &clock_enable_fops); | |
298 | debugfs_create_file(temp, S_IRUGO, dent_local, | |
299 | clock, &clock_local_fops); | |
300 | } | |
301 | return 0; | |
302 | } | |
303 | ||
304 | device_initcall(clock_debug_init); | |
305 | #endif | |
306 | ||
600f7cfe BS |
307 | /* The bootloader and/or AMSS may have left various clocks enabled. |
308 | * Disable any clocks that belong to us (CLKFLAG_AUTO_OFF) but have | |
309 | * not been explicitly enabled by a clk_enable() call. | |
310 | */ | |
311 | static int __init clock_late_init(void) | |
312 | { | |
313 | unsigned long flags; | |
314 | struct clk *clk; | |
315 | unsigned count = 0; | |
316 | ||
317 | mutex_lock(&clocks_mutex); | |
318 | list_for_each_entry(clk, &clocks, list) { | |
319 | if (clk->flags & CLKFLAG_AUTO_OFF) { | |
320 | spin_lock_irqsave(&clocks_lock, flags); | |
321 | if (!clk->count) { | |
322 | count++; | |
5e96da5d | 323 | clk->ops->auto_off(clk->id); |
600f7cfe BS |
324 | } |
325 | spin_unlock_irqrestore(&clocks_lock, flags); | |
326 | } | |
327 | } | |
328 | mutex_unlock(&clocks_mutex); | |
329 | pr_info("clock_late_init() disabled %d unused clocks\n", count); | |
330 | return 0; | |
331 | } | |
332 | ||
333 | late_initcall(clock_late_init); | |
5e96da5d | 334 |