1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. |
4 | * |
5 | * This file contains power management functions related to interrupts. |
6 | */ |
7 | |
8 | #include <linux/irq.h> |
9 | #include <linux/module.h> |
10 | #include <linux/interrupt.h> |
11 | #include <linux/suspend.h> |
12 | #include <linux/syscore_ops.h> |
13 | |
14 | #include "internals.h" |
15 | |
16 | bool irq_pm_check_wakeup(struct irq_desc *desc) |
17 | { |
18 | if (irqd_is_wakeup_armed(d: &desc->irq_data)) { |
19 | irqd_clear(d: &desc->irq_data, mask: IRQD_WAKEUP_ARMED); |
20 | desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; |
21 | desc->depth++; |
22 | irq_disable(desc); |
23 | pm_system_irq_wakeup(irq_number: irq_desc_get_irq(desc)); |
24 | return true; |
25 | } |
26 | return false; |
27 | } |
28 | |
29 | /* |
30 | * Called from __setup_irq() with desc->lock held after @action has |
31 | * been installed in the action chain. |
32 | */ |
33 | void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) |
34 | { |
35 | desc->nr_actions++; |
36 | |
37 | if (action->flags & IRQF_FORCE_RESUME) |
38 | desc->force_resume_depth++; |
39 | |
40 | WARN_ON_ONCE(desc->force_resume_depth && |
41 | desc->force_resume_depth != desc->nr_actions); |
42 | |
43 | if (action->flags & IRQF_NO_SUSPEND) |
44 | desc->no_suspend_depth++; |
45 | else if (action->flags & IRQF_COND_SUSPEND) |
46 | desc->cond_suspend_depth++; |
47 | |
48 | WARN_ON_ONCE(desc->no_suspend_depth && |
49 | (desc->no_suspend_depth + |
50 | desc->cond_suspend_depth) != desc->nr_actions); |
51 | } |
52 | |
53 | /* |
54 | * Called from __free_irq() with desc->lock held after @action has |
55 | * been removed from the action chain. |
56 | */ |
57 | void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) |
58 | { |
59 | desc->nr_actions--; |
60 | |
61 | if (action->flags & IRQF_FORCE_RESUME) |
62 | desc->force_resume_depth--; |
63 | |
64 | if (action->flags & IRQF_NO_SUSPEND) |
65 | desc->no_suspend_depth--; |
66 | else if (action->flags & IRQF_COND_SUSPEND) |
67 | desc->cond_suspend_depth--; |
68 | } |
69 | |
70 | static bool suspend_device_irq(struct irq_desc *desc) |
71 | { |
72 | unsigned long chipflags = irq_desc_get_chip(desc)->flags; |
73 | struct irq_data *irqd = &desc->irq_data; |
74 | |
75 | if (!desc->action || irq_desc_is_chained(desc) || |
76 | desc->no_suspend_depth) |
77 | return false; |
78 | |
79 | if (irqd_is_wakeup_set(d: irqd)) { |
80 | irqd_set(d: irqd, mask: IRQD_WAKEUP_ARMED); |
81 | |
82 | if ((chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) && |
83 | irqd_irq_disabled(d: irqd)) { |
84 | /* |
85 | * Interrupt marked for wakeup is in disabled state. |
86 | * Enable interrupt here to unmask/enable in irqchip |
87 | * to be able to resume with such interrupts. |
88 | */ |
89 | __enable_irq(desc); |
90 | irqd_set(d: irqd, mask: IRQD_IRQ_ENABLED_ON_SUSPEND); |
91 | } |
92 | /* |
93 | * We return true here to force the caller to issue |
94 | * synchronize_irq(). We need to make sure that the |
95 | * IRQD_WAKEUP_ARMED is visible before we return from |
96 | * suspend_device_irqs(). |
97 | */ |
98 | return true; |
99 | } |
100 | |
101 | desc->istate |= IRQS_SUSPENDED; |
102 | __disable_irq(desc); |
103 | |
104 | /* |
105 | * Hardware which has no wakeup source configuration facility |
106 | * requires that the non wakeup interrupts are masked at the |
107 | * chip level. The chip implementation indicates that with |
108 | * IRQCHIP_MASK_ON_SUSPEND. |
109 | */ |
110 | if (chipflags & IRQCHIP_MASK_ON_SUSPEND) |
111 | mask_irq(desc); |
112 | return true; |
113 | } |
114 | |
115 | /** |
116 | * suspend_device_irqs - disable all currently enabled interrupt lines |
117 | * |
118 | * During system-wide suspend or hibernation device drivers need to be |
119 | * prevented from receiving interrupts and this function is provided |
120 | * for this purpose. |
121 | * |
122 | * So we disable all interrupts and mark them IRQS_SUSPENDED except |
123 | * for those which are unused, those which are marked as not |
124 | * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND |
125 | * set and those which are marked as active wakeup sources. |
126 | * |
127 | * The active wakeup sources are handled by the flow handler entry |
128 | * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the |
129 | * interrupt and notifies the pm core about the wakeup. |
130 | */ |
131 | void suspend_device_irqs(void) |
132 | { |
133 | struct irq_desc *desc; |
134 | int irq; |
135 | |
136 | for_each_irq_desc(irq, desc) { |
137 | unsigned long flags; |
138 | bool sync; |
139 | |
140 | if (irq_settings_is_nested_thread(desc)) |
141 | continue; |
142 | raw_spin_lock_irqsave(&desc->lock, flags); |
143 | sync = suspend_device_irq(desc); |
144 | raw_spin_unlock_irqrestore(&desc->lock, flags); |
145 | |
146 | if (sync) |
147 | synchronize_irq(irq); |
148 | } |
149 | } |
150 | |
151 | static void resume_irq(struct irq_desc *desc) |
152 | { |
153 | struct irq_data *irqd = &desc->irq_data; |
154 | |
155 | irqd_clear(d: irqd, mask: IRQD_WAKEUP_ARMED); |
156 | |
157 | if (irqd_is_enabled_on_suspend(d: irqd)) { |
158 | /* |
159 | * Interrupt marked for wakeup was enabled during suspend |
160 | * entry. Disable such interrupts to restore them back to |
161 | * original state. |
162 | */ |
163 | __disable_irq(desc); |
164 | irqd_clear(d: irqd, mask: IRQD_IRQ_ENABLED_ON_SUSPEND); |
165 | } |
166 | |
167 | if (desc->istate & IRQS_SUSPENDED) |
168 | goto resume; |
169 | |
170 | /* Force resume the interrupt? */ |
171 | if (!desc->force_resume_depth) |
172 | return; |
173 | |
174 | /* Pretend that it got disabled ! */ |
175 | desc->depth++; |
176 | irq_state_set_disabled(desc); |
177 | irq_state_set_masked(desc); |
178 | resume: |
179 | desc->istate &= ~IRQS_SUSPENDED; |
180 | __enable_irq(desc); |
181 | } |
182 | |
183 | static void resume_irqs(bool want_early) |
184 | { |
185 | struct irq_desc *desc; |
186 | int irq; |
187 | |
188 | for_each_irq_desc(irq, desc) { |
189 | unsigned long flags; |
190 | bool is_early = desc->action && |
191 | desc->action->flags & IRQF_EARLY_RESUME; |
192 | |
193 | if (!is_early && want_early) |
194 | continue; |
195 | if (irq_settings_is_nested_thread(desc)) |
196 | continue; |
197 | |
198 | raw_spin_lock_irqsave(&desc->lock, flags); |
199 | resume_irq(desc); |
200 | raw_spin_unlock_irqrestore(&desc->lock, flags); |
201 | } |
202 | } |
203 | |
204 | /** |
205 | * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup |
206 | * @irq: Interrupt to rearm |
207 | */ |
208 | void rearm_wake_irq(unsigned int irq) |
209 | { |
210 | unsigned long flags; |
211 | struct irq_desc *desc = irq_get_desc_buslock(irq, flags: &flags, IRQ_GET_DESC_CHECK_GLOBAL); |
212 | |
213 | if (!desc) |
214 | return; |
215 | |
216 | if (!(desc->istate & IRQS_SUSPENDED) || |
217 | !irqd_is_wakeup_set(d: &desc->irq_data)) |
218 | goto unlock; |
219 | |
220 | desc->istate &= ~IRQS_SUSPENDED; |
221 | irqd_set(d: &desc->irq_data, mask: IRQD_WAKEUP_ARMED); |
222 | __enable_irq(desc); |
223 | |
224 | unlock: |
225 | irq_put_desc_busunlock(desc, flags); |
226 | } |
227 | |
228 | /** |
229 | * irq_pm_syscore_resume - enable interrupt lines early |
230 | * |
231 | * Enable all interrupt lines with %IRQF_EARLY_RESUME set. |
232 | */ |
233 | static void irq_pm_syscore_resume(void) |
234 | { |
235 | resume_irqs(want_early: true); |
236 | } |
237 | |
238 | static struct syscore_ops irq_pm_syscore_ops = { |
239 | .resume = irq_pm_syscore_resume, |
240 | }; |
241 | |
242 | static int __init irq_pm_init_ops(void) |
243 | { |
244 | register_syscore_ops(ops: &irq_pm_syscore_ops); |
245 | return 0; |
246 | } |
247 | |
248 | device_initcall(irq_pm_init_ops); |
249 | |
250 | /** |
251 | * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() |
252 | * |
253 | * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously |
254 | * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag |
255 | * set as well as those with %IRQF_FORCE_RESUME. |
256 | */ |
257 | void resume_device_irqs(void) |
258 | { |
259 | resume_irqs(want_early: false); |
260 | } |
261 | |