Commit | Line | Data |
---|---|---|
5de363b6 | 1 | // SPDX-License-Identifier: GPL-2.0 |
85eb8c8d RW |
2 | /* |
3 | * drivers/base/power/clock_ops.c - Generic clock manipulation PM callbacks | |
4 | * | |
5 | * Copyright (c) 2011 Rafael J. Wysocki <rjw@sisk.pl>, Renesas Electronics Corp. | |
85eb8c8d RW |
6 | */ |
7 | ||
85eb8c8d | 8 | #include <linux/kernel.h> |
51990e82 | 9 | #include <linux/device.h> |
85eb8c8d RW |
10 | #include <linux/io.h> |
11 | #include <linux/pm.h> | |
b5e8d269 | 12 | #include <linux/pm_clock.h> |
85eb8c8d | 13 | #include <linux/clk.h> |
245bd6f6 | 14 | #include <linux/clkdev.h> |
7e186d9d | 15 | #include <linux/of_clk.h> |
85eb8c8d RW |
16 | #include <linux/slab.h> |
17 | #include <linux/err.h> | |
989561de | 18 | #include <linux/pm_domain.h> |
75f50400 | 19 | #include <linux/pm_runtime.h> |
85eb8c8d | 20 | |
a6175616 | 21 | #ifdef CONFIG_PM_CLK |
85eb8c8d | 22 | |
85eb8c8d RW |
23 | enum pce_status { |
24 | PCE_STATUS_NONE = 0, | |
25 | PCE_STATUS_ACQUIRED, | |
0bfa0820 | 26 | PCE_STATUS_PREPARED, |
85eb8c8d RW |
27 | PCE_STATUS_ENABLED, |
28 | PCE_STATUS_ERROR, | |
29 | }; | |
30 | ||
31 | struct pm_clock_entry { | |
32 | struct list_head node; | |
33 | char *con_id; | |
34 | struct clk *clk; | |
35 | enum pce_status status; | |
0bfa0820 | 36 | bool enabled_when_prepared; |
85eb8c8d RW |
37 | }; |
38 | ||
0bfa0820 NP |
39 | /** |
40 | * pm_clk_list_lock - ensure exclusive access for modifying the PM clock | |
41 | * entry list. | |
42 | * @psd: pm_subsys_data instance corresponding to the PM clock entry list | |
43 | * and clk_op_might_sleep count to be modified. | |
44 | * | |
45 | * Get exclusive access before modifying the PM clock entry list and the | |
46 | * clock_op_might_sleep count to guard against concurrent modifications. | |
47 | * This also protects against a concurrent clock_op_might_sleep and PM clock | |
48 | * entry list usage in pm_clk_suspend()/pm_clk_resume() that may or may not | |
49 | * happen in atomic context, hence both the mutex and the spinlock must be | |
50 | * taken here. | |
51 | */ | |
52 | static void pm_clk_list_lock(struct pm_subsys_data *psd) | |
53 | __acquires(&psd->lock) | |
54 | { | |
55 | mutex_lock(&psd->clock_mutex); | |
56 | spin_lock_irq(&psd->lock); | |
57 | } | |
58 | ||
59 | /** | |
60 | * pm_clk_list_unlock - counterpart to pm_clk_list_lock(). | |
61 | * @psd: the same pm_subsys_data instance previously passed to | |
62 | * pm_clk_list_lock(). | |
63 | */ | |
64 | static void pm_clk_list_unlock(struct pm_subsys_data *psd) | |
65 | __releases(&psd->lock) | |
66 | { | |
67 | spin_unlock_irq(&psd->lock); | |
68 | mutex_unlock(&psd->clock_mutex); | |
69 | } | |
70 | ||
71 | /** | |
72 | * pm_clk_op_lock - ensure exclusive access for performing clock operations. | |
73 | * @psd: pm_subsys_data instance corresponding to the PM clock entry list | |
74 | * and clk_op_might_sleep count being used. | |
75 | * @flags: stored irq flags. | |
76 | * @fn: string for the caller function's name. | |
77 | * | |
78 | * This is used by pm_clk_suspend() and pm_clk_resume() to guard | |
79 | * against concurrent modifications to the clock entry list and the | |
80 | * clock_op_might_sleep count. If clock_op_might_sleep is != 0 then | |
81 | * only the mutex can be locked and those functions can only be used in | |
82 | * non atomic context. If clock_op_might_sleep == 0 then these functions | |
83 | * may be used in any context and only the spinlock can be locked. | |
84 | * Returns -EINVAL if called in atomic context when clock ops might sleep. | |
85 | */ | |
86 | static int pm_clk_op_lock(struct pm_subsys_data *psd, unsigned long *flags, | |
87 | const char *fn) | |
88 | /* sparse annotations don't work here as exit state isn't static */ | |
89 | { | |
90 | bool atomic_context = in_atomic() || irqs_disabled(); | |
91 | ||
92 | try_again: | |
93 | spin_lock_irqsave(&psd->lock, *flags); | |
94 | if (!psd->clock_op_might_sleep) { | |
95 | /* the __release is there to work around sparse limitations */ | |
96 | __release(&psd->lock); | |
97 | return 0; | |
98 | } | |
99 | ||
100 | /* bail out if in atomic context */ | |
101 | if (atomic_context) { | |
102 | pr_err("%s: atomic context with clock_ops_might_sleep = %d", | |
103 | fn, psd->clock_op_might_sleep); | |
104 | spin_unlock_irqrestore(&psd->lock, *flags); | |
105 | might_sleep(); | |
106 | return -EPERM; | |
107 | } | |
108 | ||
109 | /* we must switch to the mutex */ | |
110 | spin_unlock_irqrestore(&psd->lock, *flags); | |
111 | mutex_lock(&psd->clock_mutex); | |
112 | ||
113 | /* | |
114 | * There was a possibility for psd->clock_op_might_sleep | |
115 | * to become 0 above. Keep the mutex only if not the case. | |
116 | */ | |
117 | if (likely(psd->clock_op_might_sleep)) | |
118 | return 0; | |
119 | ||
120 | mutex_unlock(&psd->clock_mutex); | |
121 | goto try_again; | |
122 | } | |
123 | ||
124 | /** | |
125 | * pm_clk_op_unlock - counterpart to pm_clk_op_lock(). | |
126 | * @psd: the same pm_subsys_data instance previously passed to | |
127 | * pm_clk_op_lock(). | |
128 | * @flags: irq flags provided by pm_clk_op_lock(). | |
129 | */ | |
130 | static void pm_clk_op_unlock(struct pm_subsys_data *psd, unsigned long *flags) | |
131 | /* sparse annotations don't work here as entry state isn't static */ | |
132 | { | |
133 | if (psd->clock_op_might_sleep) { | |
134 | mutex_unlock(&psd->clock_mutex); | |
135 | } else { | |
136 | /* the __acquire is there to work around sparse limitations */ | |
137 | __acquire(&psd->lock); | |
138 | spin_unlock_irqrestore(&psd->lock, *flags); | |
139 | } | |
140 | } | |
141 | ||
5cda3fbb | 142 | /** |
33b688e3 | 143 | * __pm_clk_enable - Enable a clock, reporting any errors |
5cda3fbb | 144 | * @dev: The device for the given clock |
471f7707 | 145 | * @ce: PM clock entry corresponding to the clock. |
5cda3fbb | 146 | */ |
f4745a92 | 147 | static inline void __pm_clk_enable(struct device *dev, struct pm_clock_entry *ce) |
5cda3fbb | 148 | { |
471f7707 SG |
149 | int ret; |
150 | ||
0bfa0820 NP |
151 | switch (ce->status) { |
152 | case PCE_STATUS_ACQUIRED: | |
153 | ret = clk_prepare_enable(ce->clk); | |
154 | break; | |
155 | case PCE_STATUS_PREPARED: | |
471f7707 | 156 | ret = clk_enable(ce->clk); |
0bfa0820 NP |
157 | break; |
158 | default: | |
159 | return; | |
471f7707 | 160 | } |
0bfa0820 NP |
161 | if (!ret) |
162 | ce->status = PCE_STATUS_ENABLED; | |
163 | else | |
164 | dev_err(dev, "%s: failed to enable clk %p, error %d\n", | |
165 | __func__, ce->clk, ret); | |
5cda3fbb BD |
166 | } |
167 | ||
e8b364b8 RW |
168 | /** |
169 | * pm_clk_acquire - Acquire a device clock. | |
170 | * @dev: Device whose clock is to be acquired. | |
171 | * @ce: PM clock entry corresponding to the clock. | |
172 | */ | |
173 | static void pm_clk_acquire(struct device *dev, struct pm_clock_entry *ce) | |
174 | { | |
245bd6f6 GU |
175 | if (!ce->clk) |
176 | ce->clk = clk_get(dev, ce->con_id); | |
e8b364b8 RW |
177 | if (IS_ERR(ce->clk)) { |
178 | ce->status = PCE_STATUS_ERROR; | |
0bfa0820 NP |
179 | return; |
180 | } else if (clk_is_enabled_when_prepared(ce->clk)) { | |
181 | /* we defer preparing the clock in that case */ | |
182 | ce->status = PCE_STATUS_ACQUIRED; | |
183 | ce->enabled_when_prepared = true; | |
184 | } else if (clk_prepare(ce->clk)) { | |
185 | ce->status = PCE_STATUS_ERROR; | |
186 | dev_err(dev, "clk_prepare() failed\n"); | |
187 | return; | |
e8b364b8 | 188 | } else { |
0bfa0820 | 189 | ce->status = PCE_STATUS_PREPARED; |
e8b364b8 | 190 | } |
0bfa0820 NP |
191 | dev_dbg(dev, "Clock %pC con_id %s managed by runtime PM.\n", |
192 | ce->clk, ce->con_id); | |
e8b364b8 RW |
193 | } |
194 | ||
245bd6f6 GU |
195 | static int __pm_clk_add(struct device *dev, const char *con_id, |
196 | struct clk *clk) | |
85eb8c8d | 197 | { |
5c095a0e | 198 | struct pm_subsys_data *psd = dev_to_psd(dev); |
85eb8c8d RW |
199 | struct pm_clock_entry *ce; |
200 | ||
5c095a0e | 201 | if (!psd) |
85eb8c8d RW |
202 | return -EINVAL; |
203 | ||
204 | ce = kzalloc(sizeof(*ce), GFP_KERNEL); | |
59d84ca8 | 205 | if (!ce) |
85eb8c8d | 206 | return -ENOMEM; |
85eb8c8d RW |
207 | |
208 | if (con_id) { | |
209 | ce->con_id = kstrdup(con_id, GFP_KERNEL); | |
210 | if (!ce->con_id) { | |
85eb8c8d RW |
211 | kfree(ce); |
212 | return -ENOMEM; | |
213 | } | |
245bd6f6 | 214 | } else { |
772b0508 | 215 | if (IS_ERR(clk)) { |
245bd6f6 GU |
216 | kfree(ce); |
217 | return -ENOENT; | |
218 | } | |
219 | ce->clk = clk; | |
85eb8c8d RW |
220 | } |
221 | ||
e8b364b8 RW |
222 | pm_clk_acquire(dev, ce); |
223 | ||
0bfa0820 | 224 | pm_clk_list_lock(psd); |
5c095a0e | 225 | list_add_tail(&ce->node, &psd->clock_list); |
0bfa0820 NP |
226 | if (ce->enabled_when_prepared) |
227 | psd->clock_op_might_sleep++; | |
228 | pm_clk_list_unlock(psd); | |
85eb8c8d RW |
229 | return 0; |
230 | } | |
231 | ||
245bd6f6 GU |
232 | /** |
233 | * pm_clk_add - Start using a device clock for power management. | |
234 | * @dev: Device whose clock is going to be used for power management. | |
235 | * @con_id: Connection ID of the clock. | |
236 | * | |
237 | * Add the clock represented by @con_id to the list of clocks used for | |
238 | * the power management of @dev. | |
239 | */ | |
240 | int pm_clk_add(struct device *dev, const char *con_id) | |
241 | { | |
242 | return __pm_clk_add(dev, con_id, NULL); | |
243 | } | |
29b968b2 | 244 | EXPORT_SYMBOL_GPL(pm_clk_add); |
245bd6f6 GU |
245 | |
246 | /** | |
247 | * pm_clk_add_clk - Start using a device clock for power management. | |
248 | * @dev: Device whose clock is going to be used for power management. | |
249 | * @clk: Clock pointer | |
250 | * | |
251 | * Add the clock to the list of clocks used for the power management of @dev. | |
772b0508 SB |
252 | * The power-management code will take control of the clock reference, so |
253 | * callers should not call clk_put() on @clk after this function sucessfully | |
254 | * returned. | |
245bd6f6 GU |
255 | */ |
256 | int pm_clk_add_clk(struct device *dev, struct clk *clk) | |
257 | { | |
258 | return __pm_clk_add(dev, NULL, clk); | |
259 | } | |
29b968b2 | 260 | EXPORT_SYMBOL_GPL(pm_clk_add_clk); |
245bd6f6 | 261 | |
02113ba9 | 262 | |
498b5fdd JH |
263 | /** |
264 | * of_pm_clk_add_clk - Start using a device clock for power management. | |
265 | * @dev: Device whose clock is going to be used for power management. | |
266 | * @name: Name of clock that is going to be used for power management. | |
267 | * | |
268 | * Add the clock described in the 'clocks' device-tree node that matches | |
269 | * with the 'name' provided, to the list of clocks used for the power | |
270 | * management of @dev. On success, returns 0. Returns a negative error | |
271 | * code if the clock is not found or cannot be added. | |
272 | */ | |
273 | int of_pm_clk_add_clk(struct device *dev, const char *name) | |
274 | { | |
275 | struct clk *clk; | |
276 | int ret; | |
277 | ||
278 | if (!dev || !dev->of_node || !name) | |
279 | return -EINVAL; | |
280 | ||
281 | clk = of_clk_get_by_name(dev->of_node, name); | |
282 | if (IS_ERR(clk)) | |
283 | return PTR_ERR(clk); | |
284 | ||
285 | ret = pm_clk_add_clk(dev, clk); | |
286 | if (ret) { | |
287 | clk_put(clk); | |
288 | return ret; | |
289 | } | |
290 | ||
291 | return 0; | |
292 | } | |
293 | EXPORT_SYMBOL_GPL(of_pm_clk_add_clk); | |
294 | ||
02113ba9 JH |
295 | /** |
296 | * of_pm_clk_add_clks - Start using device clock(s) for power management. | |
297 | * @dev: Device whose clock(s) is going to be used for power management. | |
298 | * | |
299 | * Add a series of clocks described in the 'clocks' device-tree node for | |
300 | * a device to the list of clocks used for the power management of @dev. | |
301 | * On success, returns the number of clocks added. Returns a negative | |
302 | * error code if there are no clocks in the device node for the device | |
303 | * or if adding a clock fails. | |
304 | */ | |
305 | int of_pm_clk_add_clks(struct device *dev) | |
306 | { | |
307 | struct clk **clks; | |
5e2e2f9f | 308 | int i, count; |
02113ba9 JH |
309 | int ret; |
310 | ||
311 | if (!dev || !dev->of_node) | |
312 | return -EINVAL; | |
313 | ||
7e186d9d | 314 | count = of_clk_get_parent_count(dev->of_node); |
0b26985a | 315 | if (count <= 0) |
02113ba9 JH |
316 | return -ENODEV; |
317 | ||
318 | clks = kcalloc(count, sizeof(*clks), GFP_KERNEL); | |
319 | if (!clks) | |
320 | return -ENOMEM; | |
321 | ||
322 | for (i = 0; i < count; i++) { | |
323 | clks[i] = of_clk_get(dev->of_node, i); | |
324 | if (IS_ERR(clks[i])) { | |
325 | ret = PTR_ERR(clks[i]); | |
326 | goto error; | |
327 | } | |
328 | ||
329 | ret = pm_clk_add_clk(dev, clks[i]); | |
330 | if (ret) { | |
331 | clk_put(clks[i]); | |
332 | goto error; | |
333 | } | |
334 | } | |
335 | ||
336 | kfree(clks); | |
337 | ||
338 | return i; | |
339 | ||
340 | error: | |
341 | while (i--) | |
342 | pm_clk_remove_clk(dev, clks[i]); | |
343 | ||
344 | kfree(clks); | |
345 | ||
346 | return ret; | |
347 | } | |
29b968b2 | 348 | EXPORT_SYMBOL_GPL(of_pm_clk_add_clks); |
02113ba9 | 349 | |
85eb8c8d | 350 | /** |
3d5c3036 RW |
351 | * __pm_clk_remove - Destroy PM clock entry. |
352 | * @ce: PM clock entry to destroy. | |
85eb8c8d | 353 | */ |
3d5c3036 | 354 | static void __pm_clk_remove(struct pm_clock_entry *ce) |
85eb8c8d RW |
355 | { |
356 | if (!ce) | |
357 | return; | |
358 | ||
0bfa0820 NP |
359 | switch (ce->status) { |
360 | case PCE_STATUS_ENABLED: | |
361 | clk_disable(ce->clk); | |
362 | fallthrough; | |
363 | case PCE_STATUS_PREPARED: | |
364 | clk_unprepare(ce->clk); | |
365 | fallthrough; | |
366 | case PCE_STATUS_ACQUIRED: | |
367 | case PCE_STATUS_ERROR: | |
368 | if (!IS_ERR(ce->clk)) | |
85eb8c8d | 369 | clk_put(ce->clk); |
0bfa0820 NP |
370 | break; |
371 | default: | |
372 | break; | |
85eb8c8d RW |
373 | } |
374 | ||
0ab1e79b | 375 | kfree(ce->con_id); |
85eb8c8d RW |
376 | kfree(ce); |
377 | } | |
378 | ||
379 | /** | |
3d5c3036 RW |
380 | * pm_clk_remove - Stop using a device clock for power management. |
381 | * @dev: Device whose clock should not be used for PM any more. | |
85eb8c8d RW |
382 | * @con_id: Connection ID of the clock. |
383 | * | |
384 | * Remove the clock represented by @con_id from the list of clocks used for | |
3d5c3036 | 385 | * the power management of @dev. |
85eb8c8d | 386 | */ |
3d5c3036 | 387 | void pm_clk_remove(struct device *dev, const char *con_id) |
85eb8c8d | 388 | { |
5c095a0e | 389 | struct pm_subsys_data *psd = dev_to_psd(dev); |
85eb8c8d RW |
390 | struct pm_clock_entry *ce; |
391 | ||
5c095a0e | 392 | if (!psd) |
85eb8c8d RW |
393 | return; |
394 | ||
0bfa0820 | 395 | pm_clk_list_lock(psd); |
85eb8c8d | 396 | |
5c095a0e | 397 | list_for_each_entry(ce, &psd->clock_list, node) { |
e8b364b8 RW |
398 | if (!con_id && !ce->con_id) |
399 | goto remove; | |
400 | else if (!con_id || !ce->con_id) | |
85eb8c8d | 401 | continue; |
e8b364b8 RW |
402 | else if (!strcmp(con_id, ce->con_id)) |
403 | goto remove; | |
85eb8c8d RW |
404 | } |
405 | ||
0bfa0820 | 406 | pm_clk_list_unlock(psd); |
e8b364b8 RW |
407 | return; |
408 | ||
409 | remove: | |
410 | list_del(&ce->node); | |
0bfa0820 NP |
411 | if (ce->enabled_when_prepared) |
412 | psd->clock_op_might_sleep--; | |
413 | pm_clk_list_unlock(psd); | |
e8b364b8 RW |
414 | |
415 | __pm_clk_remove(ce); | |
85eb8c8d | 416 | } |
29b968b2 | 417 | EXPORT_SYMBOL_GPL(pm_clk_remove); |
85eb8c8d | 418 | |
02113ba9 JH |
419 | /** |
420 | * pm_clk_remove_clk - Stop using a device clock for power management. | |
421 | * @dev: Device whose clock should not be used for PM any more. | |
422 | * @clk: Clock pointer | |
423 | * | |
424 | * Remove the clock pointed to by @clk from the list of clocks used for | |
425 | * the power management of @dev. | |
426 | */ | |
427 | void pm_clk_remove_clk(struct device *dev, struct clk *clk) | |
428 | { | |
429 | struct pm_subsys_data *psd = dev_to_psd(dev); | |
430 | struct pm_clock_entry *ce; | |
431 | ||
432 | if (!psd || !clk) | |
433 | return; | |
434 | ||
0bfa0820 | 435 | pm_clk_list_lock(psd); |
02113ba9 JH |
436 | |
437 | list_for_each_entry(ce, &psd->clock_list, node) { | |
438 | if (clk == ce->clk) | |
439 | goto remove; | |
440 | } | |
441 | ||
0bfa0820 | 442 | pm_clk_list_unlock(psd); |
02113ba9 JH |
443 | return; |
444 | ||
445 | remove: | |
446 | list_del(&ce->node); | |
0bfa0820 NP |
447 | if (ce->enabled_when_prepared) |
448 | psd->clock_op_might_sleep--; | |
449 | pm_clk_list_unlock(psd); | |
02113ba9 JH |
450 | |
451 | __pm_clk_remove(ce); | |
452 | } | |
29b968b2 | 453 | EXPORT_SYMBOL_GPL(pm_clk_remove_clk); |
02113ba9 | 454 | |
85eb8c8d | 455 | /** |
3d5c3036 RW |
456 | * pm_clk_init - Initialize a device's list of power management clocks. |
457 | * @dev: Device to initialize the list of PM clocks for. | |
85eb8c8d | 458 | * |
5c095a0e | 459 | * Initialize the lock and clock_list members of the device's pm_subsys_data |
0bfa0820 | 460 | * object, set the count of clocks that might sleep to 0. |
85eb8c8d | 461 | */ |
5c095a0e | 462 | void pm_clk_init(struct device *dev) |
85eb8c8d | 463 | { |
5c095a0e | 464 | struct pm_subsys_data *psd = dev_to_psd(dev); |
0bfa0820 | 465 | if (psd) { |
ef27bed1 | 466 | INIT_LIST_HEAD(&psd->clock_list); |
0bfa0820 NP |
467 | mutex_init(&psd->clock_mutex); |
468 | psd->clock_op_might_sleep = 0; | |
469 | } | |
5c095a0e | 470 | } |
29b968b2 | 471 | EXPORT_SYMBOL_GPL(pm_clk_init); |
5c095a0e RW |
472 | |
473 | /** | |
474 | * pm_clk_create - Create and initialize a device's list of PM clocks. | |
475 | * @dev: Device to create and initialize the list of PM clocks for. | |
476 | * | |
477 | * Allocate a struct pm_subsys_data object, initialize its lock and clock_list | |
478 | * members and make the @dev's power.subsys_data field point to it. | |
479 | */ | |
480 | int pm_clk_create(struct device *dev) | |
481 | { | |
77254950 | 482 | return dev_pm_get_subsys_data(dev); |
85eb8c8d | 483 | } |
29b968b2 | 484 | EXPORT_SYMBOL_GPL(pm_clk_create); |
85eb8c8d RW |
485 | |
486 | /** | |
3d5c3036 RW |
487 | * pm_clk_destroy - Destroy a device's list of power management clocks. |
488 | * @dev: Device to destroy the list of PM clocks for. | |
85eb8c8d RW |
489 | * |
490 | * Clear the @dev's power.subsys_data field, remove the list of clock entries | |
5c095a0e | 491 | * from the struct pm_subsys_data object pointed to by it before and free |
85eb8c8d RW |
492 | * that object. |
493 | */ | |
3d5c3036 | 494 | void pm_clk_destroy(struct device *dev) |
85eb8c8d | 495 | { |
5c095a0e | 496 | struct pm_subsys_data *psd = dev_to_psd(dev); |
85eb8c8d | 497 | struct pm_clock_entry *ce, *c; |
e8b364b8 | 498 | struct list_head list; |
85eb8c8d | 499 | |
5c095a0e | 500 | if (!psd) |
85eb8c8d RW |
501 | return; |
502 | ||
e8b364b8 | 503 | INIT_LIST_HEAD(&list); |
85eb8c8d | 504 | |
0bfa0820 | 505 | pm_clk_list_lock(psd); |
85eb8c8d | 506 | |
5c095a0e | 507 | list_for_each_entry_safe_reverse(ce, c, &psd->clock_list, node) |
e8b364b8 | 508 | list_move(&ce->node, &list); |
0bfa0820 | 509 | psd->clock_op_might_sleep = 0; |
85eb8c8d | 510 | |
0bfa0820 | 511 | pm_clk_list_unlock(psd); |
85eb8c8d | 512 | |
ef27bed1 | 513 | dev_pm_put_subsys_data(dev); |
e8b364b8 RW |
514 | |
515 | list_for_each_entry_safe_reverse(ce, c, &list, node) { | |
516 | list_del(&ce->node); | |
517 | __pm_clk_remove(ce); | |
518 | } | |
85eb8c8d | 519 | } |
29b968b2 | 520 | EXPORT_SYMBOL_GPL(pm_clk_destroy); |
85eb8c8d | 521 | |
a649136b DB |
522 | static void pm_clk_destroy_action(void *data) |
523 | { | |
524 | pm_clk_destroy(data); | |
525 | } | |
526 | ||
527 | int devm_pm_clk_create(struct device *dev) | |
528 | { | |
529 | int ret; | |
530 | ||
531 | ret = pm_clk_create(dev); | |
532 | if (ret) | |
533 | return ret; | |
534 | ||
535 | return devm_add_action_or_reset(dev, pm_clk_destroy_action, dev); | |
536 | } | |
537 | EXPORT_SYMBOL_GPL(devm_pm_clk_create); | |
538 | ||
85eb8c8d | 539 | /** |
3d5c3036 | 540 | * pm_clk_suspend - Disable clocks in a device's PM clock list. |
85eb8c8d RW |
541 | * @dev: Device to disable the clocks for. |
542 | */ | |
3d5c3036 | 543 | int pm_clk_suspend(struct device *dev) |
85eb8c8d | 544 | { |
5c095a0e | 545 | struct pm_subsys_data *psd = dev_to_psd(dev); |
85eb8c8d | 546 | struct pm_clock_entry *ce; |
b7ab83ed | 547 | unsigned long flags; |
0bfa0820 | 548 | int ret; |
85eb8c8d RW |
549 | |
550 | dev_dbg(dev, "%s()\n", __func__); | |
551 | ||
5c095a0e | 552 | if (!psd) |
85eb8c8d RW |
553 | return 0; |
554 | ||
0bfa0820 NP |
555 | ret = pm_clk_op_lock(psd, &flags, __func__); |
556 | if (ret) | |
557 | return ret; | |
85eb8c8d | 558 | |
5c095a0e | 559 | list_for_each_entry_reverse(ce, &psd->clock_list, node) { |
0bfa0820 NP |
560 | if (ce->status == PCE_STATUS_ENABLED) { |
561 | if (ce->enabled_when_prepared) { | |
562 | clk_disable_unprepare(ce->clk); | |
563 | ce->status = PCE_STATUS_ACQUIRED; | |
564 | } else { | |
24050956 | 565 | clk_disable(ce->clk); |
0bfa0820 NP |
566 | ce->status = PCE_STATUS_PREPARED; |
567 | } | |
85eb8c8d RW |
568 | } |
569 | } | |
570 | ||
0bfa0820 | 571 | pm_clk_op_unlock(psd, &flags); |
85eb8c8d RW |
572 | |
573 | return 0; | |
574 | } | |
29b968b2 | 575 | EXPORT_SYMBOL_GPL(pm_clk_suspend); |
85eb8c8d RW |
576 | |
577 | /** | |
3d5c3036 | 578 | * pm_clk_resume - Enable clocks in a device's PM clock list. |
85eb8c8d RW |
579 | * @dev: Device to enable the clocks for. |
580 | */ | |
3d5c3036 | 581 | int pm_clk_resume(struct device *dev) |
85eb8c8d | 582 | { |
5c095a0e | 583 | struct pm_subsys_data *psd = dev_to_psd(dev); |
85eb8c8d | 584 | struct pm_clock_entry *ce; |
b7ab83ed | 585 | unsigned long flags; |
0bfa0820 | 586 | int ret; |
85eb8c8d RW |
587 | |
588 | dev_dbg(dev, "%s()\n", __func__); | |
589 | ||
5c095a0e | 590 | if (!psd) |
85eb8c8d RW |
591 | return 0; |
592 | ||
0bfa0820 NP |
593 | ret = pm_clk_op_lock(psd, &flags, __func__); |
594 | if (ret) | |
595 | return ret; | |
85eb8c8d | 596 | |
471f7707 SG |
597 | list_for_each_entry(ce, &psd->clock_list, node) |
598 | __pm_clk_enable(dev, ce); | |
85eb8c8d | 599 | |
0bfa0820 | 600 | pm_clk_op_unlock(psd, &flags); |
85eb8c8d RW |
601 | |
602 | return 0; | |
603 | } | |
29b968b2 | 604 | EXPORT_SYMBOL_GPL(pm_clk_resume); |
85eb8c8d RW |
605 | |
606 | /** | |
3d5c3036 | 607 | * pm_clk_notify - Notify routine for device addition and removal. |
85eb8c8d RW |
608 | * @nb: Notifier block object this function is a member of. |
609 | * @action: Operation being carried out by the caller. | |
610 | * @data: Device the routine is being run for. | |
611 | * | |
612 | * For this function to work, @nb must be a member of an object of type | |
613 | * struct pm_clk_notifier_block containing all of the requisite data. | |
564b905a RW |
614 | * Specifically, the pm_domain member of that object is copied to the device's |
615 | * pm_domain field and its con_ids member is used to populate the device's list | |
3d5c3036 | 616 | * of PM clocks, depending on @action. |
85eb8c8d | 617 | * |
564b905a | 618 | * If the device's pm_domain field is already populated with a value different |
85eb8c8d RW |
619 | * from the one stored in the struct pm_clk_notifier_block object, the function |
620 | * does nothing. | |
621 | */ | |
3d5c3036 | 622 | static int pm_clk_notify(struct notifier_block *nb, |
85eb8c8d RW |
623 | unsigned long action, void *data) |
624 | { | |
625 | struct pm_clk_notifier_block *clknb; | |
626 | struct device *dev = data; | |
3b3eca31 | 627 | char **con_id; |
85eb8c8d RW |
628 | int error; |
629 | ||
630 | dev_dbg(dev, "%s() %ld\n", __func__, action); | |
631 | ||
632 | clknb = container_of(nb, struct pm_clk_notifier_block, nb); | |
633 | ||
634 | switch (action) { | |
635 | case BUS_NOTIFY_ADD_DEVICE: | |
564b905a | 636 | if (dev->pm_domain) |
85eb8c8d RW |
637 | break; |
638 | ||
5c095a0e | 639 | error = pm_clk_create(dev); |
85eb8c8d RW |
640 | if (error) |
641 | break; | |
642 | ||
989561de | 643 | dev_pm_domain_set(dev, clknb->pm_domain); |
85eb8c8d | 644 | if (clknb->con_ids[0]) { |
3b3eca31 | 645 | for (con_id = clknb->con_ids; *con_id; con_id++) |
3d5c3036 | 646 | pm_clk_add(dev, *con_id); |
85eb8c8d | 647 | } else { |
3d5c3036 | 648 | pm_clk_add(dev, NULL); |
85eb8c8d RW |
649 | } |
650 | ||
651 | break; | |
652 | case BUS_NOTIFY_DEL_DEVICE: | |
564b905a | 653 | if (dev->pm_domain != clknb->pm_domain) |
85eb8c8d RW |
654 | break; |
655 | ||
989561de | 656 | dev_pm_domain_set(dev, NULL); |
3d5c3036 | 657 | pm_clk_destroy(dev); |
85eb8c8d RW |
658 | break; |
659 | } | |
660 | ||
661 | return 0; | |
662 | } | |
663 | ||
75f50400 RN |
664 | int pm_clk_runtime_suspend(struct device *dev) |
665 | { | |
666 | int ret; | |
667 | ||
668 | dev_dbg(dev, "%s\n", __func__); | |
669 | ||
670 | ret = pm_generic_runtime_suspend(dev); | |
671 | if (ret) { | |
672 | dev_err(dev, "failed to suspend device\n"); | |
673 | return ret; | |
674 | } | |
675 | ||
676 | ret = pm_clk_suspend(dev); | |
677 | if (ret) { | |
678 | dev_err(dev, "failed to suspend clock\n"); | |
679 | pm_generic_runtime_resume(dev); | |
680 | return ret; | |
681 | } | |
682 | ||
683 | return 0; | |
684 | } | |
29b968b2 | 685 | EXPORT_SYMBOL_GPL(pm_clk_runtime_suspend); |
75f50400 RN |
686 | |
687 | int pm_clk_runtime_resume(struct device *dev) | |
688 | { | |
689 | int ret; | |
690 | ||
691 | dev_dbg(dev, "%s\n", __func__); | |
692 | ||
693 | ret = pm_clk_resume(dev); | |
694 | if (ret) { | |
695 | dev_err(dev, "failed to resume clock\n"); | |
696 | return ret; | |
697 | } | |
698 | ||
699 | return pm_generic_runtime_resume(dev); | |
700 | } | |
29b968b2 | 701 | EXPORT_SYMBOL_GPL(pm_clk_runtime_resume); |
75f50400 | 702 | |
a6175616 | 703 | #else /* !CONFIG_PM_CLK */ |
b7b95920 | 704 | |
85eb8c8d RW |
705 | /** |
706 | * enable_clock - Enable a device clock. | |
707 | * @dev: Device whose clock is to be enabled. | |
708 | * @con_id: Connection ID of the clock. | |
709 | */ | |
710 | static void enable_clock(struct device *dev, const char *con_id) | |
711 | { | |
712 | struct clk *clk; | |
713 | ||
714 | clk = clk_get(dev, con_id); | |
715 | if (!IS_ERR(clk)) { | |
c122f27e | 716 | clk_prepare_enable(clk); |
85eb8c8d RW |
717 | clk_put(clk); |
718 | dev_info(dev, "Runtime PM disabled, clock forced on.\n"); | |
719 | } | |
720 | } | |
721 | ||
722 | /** | |
723 | * disable_clock - Disable a device clock. | |
724 | * @dev: Device whose clock is to be disabled. | |
725 | * @con_id: Connection ID of the clock. | |
726 | */ | |
727 | static void disable_clock(struct device *dev, const char *con_id) | |
728 | { | |
729 | struct clk *clk; | |
730 | ||
731 | clk = clk_get(dev, con_id); | |
732 | if (!IS_ERR(clk)) { | |
c122f27e | 733 | clk_disable_unprepare(clk); |
85eb8c8d RW |
734 | clk_put(clk); |
735 | dev_info(dev, "Runtime PM disabled, clock forced off.\n"); | |
736 | } | |
737 | } | |
738 | ||
739 | /** | |
3d5c3036 | 740 | * pm_clk_notify - Notify routine for device addition and removal. |
85eb8c8d RW |
741 | * @nb: Notifier block object this function is a member of. |
742 | * @action: Operation being carried out by the caller. | |
743 | * @data: Device the routine is being run for. | |
744 | * | |
745 | * For this function to work, @nb must be a member of an object of type | |
746 | * struct pm_clk_notifier_block containing all of the requisite data. | |
747 | * Specifically, the con_ids member of that object is used to enable or disable | |
748 | * the device's clocks, depending on @action. | |
749 | */ | |
3d5c3036 | 750 | static int pm_clk_notify(struct notifier_block *nb, |
85eb8c8d RW |
751 | unsigned long action, void *data) |
752 | { | |
753 | struct pm_clk_notifier_block *clknb; | |
754 | struct device *dev = data; | |
3b3eca31 | 755 | char **con_id; |
85eb8c8d RW |
756 | |
757 | dev_dbg(dev, "%s() %ld\n", __func__, action); | |
758 | ||
759 | clknb = container_of(nb, struct pm_clk_notifier_block, nb); | |
760 | ||
761 | switch (action) { | |
4d1518f5 | 762 | case BUS_NOTIFY_BIND_DRIVER: |
85eb8c8d | 763 | if (clknb->con_ids[0]) { |
3b3eca31 RW |
764 | for (con_id = clknb->con_ids; *con_id; con_id++) |
765 | enable_clock(dev, *con_id); | |
85eb8c8d RW |
766 | } else { |
767 | enable_clock(dev, NULL); | |
768 | } | |
769 | break; | |
d35818a9 | 770 | case BUS_NOTIFY_DRIVER_NOT_BOUND: |
4d1518f5 | 771 | case BUS_NOTIFY_UNBOUND_DRIVER: |
85eb8c8d | 772 | if (clknb->con_ids[0]) { |
3b3eca31 RW |
773 | for (con_id = clknb->con_ids; *con_id; con_id++) |
774 | disable_clock(dev, *con_id); | |
85eb8c8d RW |
775 | } else { |
776 | disable_clock(dev, NULL); | |
777 | } | |
778 | break; | |
779 | } | |
780 | ||
781 | return 0; | |
782 | } | |
783 | ||
a6175616 | 784 | #endif /* !CONFIG_PM_CLK */ |
85eb8c8d RW |
785 | |
786 | /** | |
3d5c3036 | 787 | * pm_clk_add_notifier - Add bus type notifier for power management clocks. |
85eb8c8d RW |
788 | * @bus: Bus type to add the notifier to. |
789 | * @clknb: Notifier to be added to the given bus type. | |
790 | * | |
791 | * The nb member of @clknb is not expected to be initialized and its | |
3d5c3036 | 792 | * notifier_call member will be replaced with pm_clk_notify(). However, |
85eb8c8d RW |
793 | * the remaining members of @clknb should be populated prior to calling this |
794 | * routine. | |
795 | */ | |
3d5c3036 | 796 | void pm_clk_add_notifier(struct bus_type *bus, |
85eb8c8d RW |
797 | struct pm_clk_notifier_block *clknb) |
798 | { | |
799 | if (!bus || !clknb) | |
800 | return; | |
801 | ||
3d5c3036 | 802 | clknb->nb.notifier_call = pm_clk_notify; |
85eb8c8d RW |
803 | bus_register_notifier(bus, &clknb->nb); |
804 | } | |
29b968b2 | 805 | EXPORT_SYMBOL_GPL(pm_clk_add_notifier); |