1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * AM33XX Power Management Routines |
4 | * |
5 | * Copyright (C) 2012-2018 Texas Instruments Incorporated - http://www.ti.com/ |
6 | * Vaibhav Bedia, Dave Gerlach |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/cpu.h> |
11 | #include <linux/err.h> |
12 | #include <linux/genalloc.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/init.h> |
15 | #include <linux/io.h> |
16 | #include <linux/module.h> |
17 | #include <linux/nvmem-consumer.h> |
18 | #include <linux/of.h> |
19 | #include <linux/of_address.h> |
20 | #include <linux/platform_data/pm33xx.h> |
21 | #include <linux/platform_device.h> |
22 | #include <linux/pm_runtime.h> |
23 | #include <linux/rtc.h> |
24 | #include <linux/rtc/rtc-omap.h> |
25 | #include <linux/sizes.h> |
26 | #include <linux/sram.h> |
27 | #include <linux/suspend.h> |
28 | #include <linux/ti-emif-sram.h> |
29 | #include <linux/wkup_m3_ipc.h> |
30 | |
31 | #include <asm/proc-fns.h> |
32 | #include <asm/suspend.h> |
33 | #include <asm/system_misc.h> |
34 | |
35 | #define AMX3_PM_SRAM_SYMBOL_OFFSET(sym) ((unsigned long)(sym) - \ |
36 | (unsigned long)pm_sram->do_wfi) |
37 | |
38 | #define RTC_SCRATCH_RESUME_REG 0 |
39 | #define RTC_SCRATCH_MAGIC_REG 1 |
40 | #define RTC_REG_BOOT_MAGIC 0x8cd0 /* RTC */ |
41 | #define GIC_INT_SET_PENDING_BASE 0x200 |
42 | #define AM43XX_GIC_DIST_BASE 0x48241000 |
43 | |
44 | static void __iomem *rtc_base_virt; |
45 | static struct clk *rtc_fck; |
46 | static u32 rtc_magic_val; |
47 | |
48 | static int (*am33xx_do_wfi_sram)(unsigned long unused); |
49 | static phys_addr_t am33xx_do_wfi_sram_phys; |
50 | |
51 | static struct gen_pool *sram_pool, *sram_pool_data; |
52 | static unsigned long ocmcram_location, ocmcram_location_data; |
53 | |
54 | static struct rtc_device *omap_rtc; |
55 | static void __iomem *gic_dist_base; |
56 | |
57 | static struct am33xx_pm_platform_data *pm_ops; |
58 | static struct am33xx_pm_sram_addr *pm_sram; |
59 | |
60 | static struct device *pm33xx_dev; |
61 | static struct wkup_m3_ipc *m3_ipc; |
62 | |
63 | #ifdef CONFIG_SUSPEND |
64 | static int rtc_only_idle; |
65 | static int retrigger_irq; |
66 | static unsigned long suspend_wfi_flags; |
67 | |
68 | static struct wkup_m3_wakeup_src wakeup_src = {.irq_nr = 0, |
69 | .src = "Unknown" , |
70 | }; |
71 | |
72 | static struct wkup_m3_wakeup_src rtc_alarm_wakeup = { |
73 | .irq_nr = 108, .src = "RTC Alarm" , |
74 | }; |
75 | |
76 | static struct wkup_m3_wakeup_src rtc_ext_wakeup = { |
77 | .irq_nr = 0, .src = "Ext wakeup" , |
78 | }; |
79 | #endif |
80 | |
81 | static u32 sram_suspend_address(unsigned long addr) |
82 | { |
83 | return ((unsigned long)am33xx_do_wfi_sram + |
84 | AMX3_PM_SRAM_SYMBOL_OFFSET(addr)); |
85 | } |
86 | |
87 | static int am33xx_push_sram_idle(void) |
88 | { |
89 | struct am33xx_pm_ro_sram_data ro_sram_data; |
90 | int ret; |
91 | u32 table_addr, ro_data_addr; |
92 | void *copy_addr; |
93 | |
94 | ro_sram_data.amx3_pm_sram_data_virt = ocmcram_location_data; |
95 | ro_sram_data.amx3_pm_sram_data_phys = |
96 | gen_pool_virt_to_phys(pool: sram_pool_data, ocmcram_location_data); |
97 | ro_sram_data.rtc_base_virt = rtc_base_virt; |
98 | |
99 | /* Save physical address to calculate resume offset during pm init */ |
100 | am33xx_do_wfi_sram_phys = gen_pool_virt_to_phys(pool: sram_pool, |
101 | ocmcram_location); |
102 | |
103 | am33xx_do_wfi_sram = sram_exec_copy(pool: sram_pool, dst: (void *)ocmcram_location, |
104 | src: pm_sram->do_wfi, |
105 | size: *pm_sram->do_wfi_sz); |
106 | if (!am33xx_do_wfi_sram) { |
107 | dev_err(pm33xx_dev, |
108 | "PM: %s: am33xx_do_wfi copy to sram failed\n" , |
109 | __func__); |
110 | return -ENODEV; |
111 | } |
112 | |
113 | table_addr = |
114 | sram_suspend_address(addr: (unsigned long)pm_sram->emif_sram_table); |
115 | ret = ti_emif_copy_pm_function_table(sram_pool, dst: (void *)table_addr); |
116 | if (ret) { |
117 | dev_dbg(pm33xx_dev, |
118 | "PM: %s: EMIF function copy failed\n" , __func__); |
119 | return -EPROBE_DEFER; |
120 | } |
121 | |
122 | ro_data_addr = |
123 | sram_suspend_address(addr: (unsigned long)pm_sram->ro_sram_data); |
124 | copy_addr = sram_exec_copy(pool: sram_pool, dst: (void *)ro_data_addr, |
125 | src: &ro_sram_data, |
126 | size: sizeof(ro_sram_data)); |
127 | if (!copy_addr) { |
128 | dev_err(pm33xx_dev, |
129 | "PM: %s: ro_sram_data copy to sram failed\n" , |
130 | __func__); |
131 | return -ENODEV; |
132 | } |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | static int am33xx_do_sram_idle(u32 wfi_flags) |
138 | { |
139 | if (!m3_ipc || !pm_ops) |
140 | return 0; |
141 | |
142 | if (wfi_flags & WFI_FLAG_WAKE_M3) |
143 | m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_IDLE); |
144 | |
145 | return pm_ops->cpu_suspend(am33xx_do_wfi_sram, wfi_flags); |
146 | } |
147 | |
148 | static int __init am43xx_map_gic(void) |
149 | { |
150 | gic_dist_base = ioremap(AM43XX_GIC_DIST_BASE, SZ_4K); |
151 | |
152 | if (!gic_dist_base) |
153 | return -ENOMEM; |
154 | |
155 | return 0; |
156 | } |
157 | |
158 | #ifdef CONFIG_SUSPEND |
159 | static struct wkup_m3_wakeup_src rtc_wake_src(void) |
160 | { |
161 | u32 i; |
162 | |
163 | i = __raw_readl(addr: rtc_base_virt + 0x44) & 0x40; |
164 | |
165 | if (i) { |
166 | retrigger_irq = rtc_alarm_wakeup.irq_nr; |
167 | return rtc_alarm_wakeup; |
168 | } |
169 | |
170 | retrigger_irq = rtc_ext_wakeup.irq_nr; |
171 | |
172 | return rtc_ext_wakeup; |
173 | } |
174 | |
175 | static int am33xx_rtc_only_idle(unsigned long wfi_flags) |
176 | { |
177 | omap_rtc_power_off_program(dev: &omap_rtc->dev); |
178 | am33xx_do_wfi_sram(wfi_flags); |
179 | return 0; |
180 | } |
181 | |
182 | /* |
183 | * Note that the RTC module clock must be re-enabled only for rtc+ddr suspend. |
184 | * And looks like the module can stay in SYSC_IDLE_SMART_WKUP mode configured |
185 | * by the interconnect code just fine for both rtc+ddr suspend and retention |
186 | * suspend. |
187 | */ |
188 | static int am33xx_pm_suspend(suspend_state_t suspend_state) |
189 | { |
190 | int i, ret = 0; |
191 | |
192 | if (suspend_state == PM_SUSPEND_MEM && |
193 | pm_ops->check_off_mode_enable()) { |
194 | ret = clk_prepare_enable(clk: rtc_fck); |
195 | if (ret) { |
196 | dev_err(pm33xx_dev, "Failed to enable clock: %i\n" , ret); |
197 | return ret; |
198 | } |
199 | |
200 | pm_ops->save_context(); |
201 | suspend_wfi_flags |= WFI_FLAG_RTC_ONLY; |
202 | clk_save_context(); |
203 | ret = pm_ops->soc_suspend(suspend_state, am33xx_rtc_only_idle, |
204 | suspend_wfi_flags); |
205 | |
206 | suspend_wfi_flags &= ~WFI_FLAG_RTC_ONLY; |
207 | dev_info(pm33xx_dev, "Entering RTC Only mode with DDR in self-refresh\n" ); |
208 | |
209 | if (!ret) { |
210 | clk_restore_context(); |
211 | pm_ops->restore_context(); |
212 | m3_ipc->ops->set_rtc_only(m3_ipc); |
213 | am33xx_push_sram_idle(); |
214 | } |
215 | } else { |
216 | ret = pm_ops->soc_suspend(suspend_state, am33xx_do_wfi_sram, |
217 | suspend_wfi_flags); |
218 | } |
219 | |
220 | if (ret) { |
221 | dev_err(pm33xx_dev, "PM: Kernel suspend failure\n" ); |
222 | } else { |
223 | i = m3_ipc->ops->request_pm_status(m3_ipc); |
224 | |
225 | switch (i) { |
226 | case 0: |
227 | dev_info(pm33xx_dev, |
228 | "PM: Successfully put all powerdomains to target state\n" ); |
229 | break; |
230 | case 1: |
231 | dev_err(pm33xx_dev, |
232 | "PM: Could not transition all powerdomains to target state\n" ); |
233 | ret = -1; |
234 | break; |
235 | default: |
236 | dev_err(pm33xx_dev, |
237 | "PM: CM3 returned unknown result = %d\n" , i); |
238 | ret = -1; |
239 | } |
240 | |
241 | /* print the wakeup reason */ |
242 | if (rtc_only_idle) { |
243 | wakeup_src = rtc_wake_src(); |
244 | pr_info("PM: Wakeup source %s\n" , wakeup_src.src); |
245 | } else { |
246 | pr_info("PM: Wakeup source %s\n" , |
247 | m3_ipc->ops->request_wake_src(m3_ipc)); |
248 | } |
249 | } |
250 | |
251 | if (suspend_state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) |
252 | clk_disable_unprepare(clk: rtc_fck); |
253 | |
254 | return ret; |
255 | } |
256 | |
257 | static int am33xx_pm_enter(suspend_state_t suspend_state) |
258 | { |
259 | int ret = 0; |
260 | |
261 | switch (suspend_state) { |
262 | case PM_SUSPEND_MEM: |
263 | case PM_SUSPEND_STANDBY: |
264 | ret = am33xx_pm_suspend(suspend_state); |
265 | break; |
266 | default: |
267 | ret = -EINVAL; |
268 | } |
269 | |
270 | return ret; |
271 | } |
272 | |
273 | static int am33xx_pm_begin(suspend_state_t state) |
274 | { |
275 | int ret = -EINVAL; |
276 | struct nvmem_device *nvmem; |
277 | |
278 | if (state == PM_SUSPEND_MEM && pm_ops->check_off_mode_enable()) { |
279 | nvmem = devm_nvmem_device_get(dev: &omap_rtc->dev, |
280 | name: "omap_rtc_scratch0" ); |
281 | if (!IS_ERR(ptr: nvmem)) |
282 | nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, bytes: 4, |
283 | buf: (void *)&rtc_magic_val); |
284 | rtc_only_idle = 1; |
285 | } else { |
286 | rtc_only_idle = 0; |
287 | } |
288 | |
289 | pm_ops->begin_suspend(); |
290 | |
291 | switch (state) { |
292 | case PM_SUSPEND_MEM: |
293 | ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_DEEPSLEEP); |
294 | break; |
295 | case PM_SUSPEND_STANDBY: |
296 | ret = m3_ipc->ops->prepare_low_power(m3_ipc, WKUP_M3_STANDBY); |
297 | break; |
298 | } |
299 | |
300 | return ret; |
301 | } |
302 | |
303 | static void am33xx_pm_end(void) |
304 | { |
305 | u32 val = 0; |
306 | struct nvmem_device *nvmem; |
307 | |
308 | nvmem = devm_nvmem_device_get(dev: &omap_rtc->dev, name: "omap_rtc_scratch0" ); |
309 | if (IS_ERR(ptr: nvmem)) |
310 | return; |
311 | |
312 | m3_ipc->ops->finish_low_power(m3_ipc); |
313 | if (rtc_only_idle) { |
314 | if (retrigger_irq) { |
315 | /* |
316 | * 32 bits of Interrupt Set-Pending correspond to 32 |
317 | * 32 interrupts. Compute the bit offset of the |
318 | * Interrupt and set that particular bit |
319 | * Compute the register offset by dividing interrupt |
320 | * number by 32 and mutiplying by 4 |
321 | */ |
322 | writel_relaxed(1 << (retrigger_irq & 31), |
323 | gic_dist_base + GIC_INT_SET_PENDING_BASE |
324 | + retrigger_irq / 32 * 4); |
325 | } |
326 | |
327 | nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, bytes: 4, |
328 | buf: (void *)&val); |
329 | } |
330 | |
331 | rtc_only_idle = 0; |
332 | |
333 | pm_ops->finish_suspend(); |
334 | } |
335 | |
336 | static int am33xx_pm_valid(suspend_state_t state) |
337 | { |
338 | switch (state) { |
339 | case PM_SUSPEND_STANDBY: |
340 | case PM_SUSPEND_MEM: |
341 | return 1; |
342 | default: |
343 | return 0; |
344 | } |
345 | } |
346 | |
347 | static const struct platform_suspend_ops am33xx_pm_ops = { |
348 | .begin = am33xx_pm_begin, |
349 | .end = am33xx_pm_end, |
350 | .enter = am33xx_pm_enter, |
351 | .valid = am33xx_pm_valid, |
352 | }; |
353 | #endif /* CONFIG_SUSPEND */ |
354 | |
355 | static void am33xx_pm_set_ipc_ops(void) |
356 | { |
357 | u32 resume_address; |
358 | int temp; |
359 | |
360 | temp = ti_emif_get_mem_type(); |
361 | if (temp < 0) { |
362 | dev_err(pm33xx_dev, "PM: Cannot determine memory type, no PM available\n" ); |
363 | return; |
364 | } |
365 | m3_ipc->ops->set_mem_type(m3_ipc, temp); |
366 | |
367 | /* Physical resume address to be used by ROM code */ |
368 | resume_address = am33xx_do_wfi_sram_phys + |
369 | *pm_sram->resume_offset + 0x4; |
370 | |
371 | m3_ipc->ops->set_resume_address(m3_ipc, (void *)resume_address); |
372 | } |
373 | |
374 | static void am33xx_pm_free_sram(void) |
375 | { |
376 | gen_pool_free(pool: sram_pool, addr: ocmcram_location, size: *pm_sram->do_wfi_sz); |
377 | gen_pool_free(pool: sram_pool_data, addr: ocmcram_location_data, |
378 | size: sizeof(struct am33xx_pm_ro_sram_data)); |
379 | } |
380 | |
381 | /* |
382 | * Push the minimal suspend-resume code to SRAM |
383 | */ |
384 | static int am33xx_pm_alloc_sram(void) |
385 | { |
386 | struct device_node *np; |
387 | int ret = 0; |
388 | |
389 | np = of_find_compatible_node(NULL, NULL, compat: "ti,omap3-mpu" ); |
390 | if (!np) { |
391 | np = of_find_compatible_node(NULL, NULL, compat: "ti,omap4-mpu" ); |
392 | if (!np) { |
393 | dev_err(pm33xx_dev, "PM: %s: Unable to find device node for mpu\n" , |
394 | __func__); |
395 | return -ENODEV; |
396 | } |
397 | } |
398 | |
399 | sram_pool = of_gen_pool_get(np, propname: "pm-sram" , index: 0); |
400 | if (!sram_pool) { |
401 | dev_err(pm33xx_dev, "PM: %s: Unable to get sram pool for ocmcram\n" , |
402 | __func__); |
403 | ret = -ENODEV; |
404 | goto mpu_put_node; |
405 | } |
406 | |
407 | sram_pool_data = of_gen_pool_get(np, propname: "pm-sram" , index: 1); |
408 | if (!sram_pool_data) { |
409 | dev_err(pm33xx_dev, "PM: %s: Unable to get sram data pool for ocmcram\n" , |
410 | __func__); |
411 | ret = -ENODEV; |
412 | goto mpu_put_node; |
413 | } |
414 | |
415 | ocmcram_location = gen_pool_alloc(pool: sram_pool, size: *pm_sram->do_wfi_sz); |
416 | if (!ocmcram_location) { |
417 | dev_err(pm33xx_dev, "PM: %s: Unable to allocate memory from ocmcram\n" , |
418 | __func__); |
419 | ret = -ENOMEM; |
420 | goto mpu_put_node; |
421 | } |
422 | |
423 | ocmcram_location_data = gen_pool_alloc(pool: sram_pool_data, |
424 | size: sizeof(struct emif_regs_amx3)); |
425 | if (!ocmcram_location_data) { |
426 | dev_err(pm33xx_dev, "PM: Unable to allocate memory from ocmcram\n" ); |
427 | gen_pool_free(pool: sram_pool, addr: ocmcram_location, size: *pm_sram->do_wfi_sz); |
428 | ret = -ENOMEM; |
429 | } |
430 | |
431 | mpu_put_node: |
432 | of_node_put(node: np); |
433 | return ret; |
434 | } |
435 | |
436 | static int am33xx_pm_rtc_setup(void) |
437 | { |
438 | struct device_node *np; |
439 | unsigned long val = 0; |
440 | struct nvmem_device *nvmem; |
441 | int error; |
442 | |
443 | np = of_find_node_by_name(NULL, name: "rtc" ); |
444 | |
445 | if (of_device_is_available(device: np)) { |
446 | /* RTC interconnect target module clock */ |
447 | rtc_fck = of_clk_get_by_name(np: np->parent, name: "fck" ); |
448 | if (IS_ERR(ptr: rtc_fck)) |
449 | return PTR_ERR(ptr: rtc_fck); |
450 | |
451 | rtc_base_virt = of_iomap(node: np, index: 0); |
452 | if (!rtc_base_virt) { |
453 | pr_warn("PM: could not iomap rtc" ); |
454 | error = -ENODEV; |
455 | goto err_clk_put; |
456 | } |
457 | |
458 | omap_rtc = rtc_class_open(name: "rtc0" ); |
459 | if (!omap_rtc) { |
460 | pr_warn("PM: rtc0 not available" ); |
461 | error = -EPROBE_DEFER; |
462 | goto err_iounmap; |
463 | } |
464 | |
465 | nvmem = devm_nvmem_device_get(dev: &omap_rtc->dev, |
466 | name: "omap_rtc_scratch0" ); |
467 | if (!IS_ERR(ptr: nvmem)) { |
468 | nvmem_device_read(nvmem, RTC_SCRATCH_MAGIC_REG * 4, |
469 | bytes: 4, buf: (void *)&rtc_magic_val); |
470 | if ((rtc_magic_val & 0xffff) != RTC_REG_BOOT_MAGIC) |
471 | pr_warn("PM: bootloader does not support rtc-only!\n" ); |
472 | |
473 | nvmem_device_write(nvmem, RTC_SCRATCH_MAGIC_REG * 4, |
474 | bytes: 4, buf: (void *)&val); |
475 | val = pm_sram->resume_address; |
476 | nvmem_device_write(nvmem, RTC_SCRATCH_RESUME_REG * 4, |
477 | bytes: 4, buf: (void *)&val); |
478 | } |
479 | } else { |
480 | pr_warn("PM: no-rtc available, rtc-only mode disabled.\n" ); |
481 | } |
482 | |
483 | return 0; |
484 | |
485 | err_iounmap: |
486 | iounmap(addr: rtc_base_virt); |
487 | err_clk_put: |
488 | clk_put(clk: rtc_fck); |
489 | |
490 | return error; |
491 | } |
492 | |
493 | static int am33xx_pm_probe(struct platform_device *pdev) |
494 | { |
495 | struct device *dev = &pdev->dev; |
496 | int ret; |
497 | |
498 | if (!of_machine_is_compatible(compat: "ti,am33xx" ) && |
499 | !of_machine_is_compatible(compat: "ti,am43" )) |
500 | return -ENODEV; |
501 | |
502 | pm_ops = dev->platform_data; |
503 | if (!pm_ops) { |
504 | dev_err(dev, "PM: Cannot get core PM ops!\n" ); |
505 | return -ENODEV; |
506 | } |
507 | |
508 | ret = am43xx_map_gic(); |
509 | if (ret) { |
510 | pr_err("PM: Could not ioremap GIC base\n" ); |
511 | return ret; |
512 | } |
513 | |
514 | pm_sram = pm_ops->get_sram_addrs(); |
515 | if (!pm_sram) { |
516 | dev_err(dev, "PM: Cannot get PM asm function addresses!!\n" ); |
517 | return -ENODEV; |
518 | } |
519 | |
520 | m3_ipc = wkup_m3_ipc_get(); |
521 | if (!m3_ipc) { |
522 | pr_err("PM: Cannot get wkup_m3_ipc handle\n" ); |
523 | return -EPROBE_DEFER; |
524 | } |
525 | |
526 | pm33xx_dev = dev; |
527 | |
528 | ret = am33xx_pm_alloc_sram(); |
529 | if (ret) |
530 | goto err_wkup_m3_ipc_put; |
531 | |
532 | ret = am33xx_pm_rtc_setup(); |
533 | if (ret) |
534 | goto err_free_sram; |
535 | |
536 | ret = am33xx_push_sram_idle(); |
537 | if (ret) |
538 | goto err_unsetup_rtc; |
539 | |
540 | am33xx_pm_set_ipc_ops(); |
541 | |
542 | #ifdef CONFIG_SUSPEND |
543 | suspend_set_ops(ops: &am33xx_pm_ops); |
544 | |
545 | /* |
546 | * For a system suspend we must flush the caches, we want |
547 | * the DDR in self-refresh, we want to save the context |
548 | * of the EMIF, and we want the wkup_m3 to handle low-power |
549 | * transition. |
550 | */ |
551 | suspend_wfi_flags |= WFI_FLAG_FLUSH_CACHE; |
552 | suspend_wfi_flags |= WFI_FLAG_SELF_REFRESH; |
553 | suspend_wfi_flags |= WFI_FLAG_SAVE_EMIF; |
554 | suspend_wfi_flags |= WFI_FLAG_WAKE_M3; |
555 | #endif /* CONFIG_SUSPEND */ |
556 | |
557 | pm_runtime_enable(dev); |
558 | ret = pm_runtime_resume_and_get(dev); |
559 | if (ret < 0) |
560 | goto err_pm_runtime_disable; |
561 | |
562 | ret = pm_ops->init(am33xx_do_sram_idle); |
563 | if (ret) { |
564 | dev_err(dev, "Unable to call core pm init!\n" ); |
565 | ret = -ENODEV; |
566 | goto err_pm_runtime_put; |
567 | } |
568 | |
569 | return 0; |
570 | |
571 | err_pm_runtime_put: |
572 | pm_runtime_put_sync(dev); |
573 | err_pm_runtime_disable: |
574 | pm_runtime_disable(dev); |
575 | err_unsetup_rtc: |
576 | iounmap(addr: rtc_base_virt); |
577 | clk_put(clk: rtc_fck); |
578 | err_free_sram: |
579 | am33xx_pm_free_sram(); |
580 | pm33xx_dev = NULL; |
581 | err_wkup_m3_ipc_put: |
582 | wkup_m3_ipc_put(m3_ipc); |
583 | return ret; |
584 | } |
585 | |
586 | static void am33xx_pm_remove(struct platform_device *pdev) |
587 | { |
588 | pm_runtime_put_sync(dev: &pdev->dev); |
589 | pm_runtime_disable(dev: &pdev->dev); |
590 | if (pm_ops->deinit) |
591 | pm_ops->deinit(); |
592 | suspend_set_ops(NULL); |
593 | wkup_m3_ipc_put(m3_ipc); |
594 | am33xx_pm_free_sram(); |
595 | iounmap(addr: rtc_base_virt); |
596 | clk_put(clk: rtc_fck); |
597 | } |
598 | |
599 | static struct platform_driver am33xx_pm_driver = { |
600 | .driver = { |
601 | .name = "pm33xx" , |
602 | }, |
603 | .probe = am33xx_pm_probe, |
604 | .remove_new = am33xx_pm_remove, |
605 | }; |
606 | module_platform_driver(am33xx_pm_driver); |
607 | |
608 | MODULE_ALIAS("platform:pm33xx" ); |
609 | MODULE_LICENSE("GPL v2" ); |
610 | MODULE_DESCRIPTION("am33xx power management driver" ); |
611 | |