Blame view

kernel/irq/pm.c 6.39 KB
52a65ff56   Thomas Gleixner   genirq: Add missi...
1
  // SPDX-License-Identifier: GPL-2.0
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
2
  /*
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
3
4
5
6
7
8
9
10
   * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc.
   *
   * This file contains power management functions related to interrupts.
   */
  
  #include <linux/irq.h>
  #include <linux/module.h>
  #include <linux/interrupt.h>
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
11
  #include <linux/suspend.h>
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
12
  #include <linux/syscore_ops.h>
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
13
14
  
  #include "internals.h"
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
15
16
17
18
19
20
21
  bool irq_pm_check_wakeup(struct irq_desc *desc)
  {
  	if (irqd_is_wakeup_armed(&desc->irq_data)) {
  		irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED);
  		desc->istate |= IRQS_SUSPENDED | IRQS_PENDING;
  		desc->depth++;
  		irq_disable(desc);
a6f5f0dd4   Alexandra Yates   PM / sleep: Repor...
22
  		pm_system_irq_wakeup(irq_desc_get_irq(desc));
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
23
24
25
26
  		return true;
  	}
  	return false;
  }
cab303be9   Thomas Gleixner   genirq: Add sanit...
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
  /*
   * Called from __setup_irq() with desc->lock held after @action has
   * been installed in the action chain.
   */
  void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action)
  {
  	desc->nr_actions++;
  
  	if (action->flags & IRQF_FORCE_RESUME)
  		desc->force_resume_depth++;
  
  	WARN_ON_ONCE(desc->force_resume_depth &&
  		     desc->force_resume_depth != desc->nr_actions);
  
  	if (action->flags & IRQF_NO_SUSPEND)
  		desc->no_suspend_depth++;
17f480342   Rafael J. Wysocki   genirq / PM: Add ...
43
44
  	else if (action->flags & IRQF_COND_SUSPEND)
  		desc->cond_suspend_depth++;
cab303be9   Thomas Gleixner   genirq: Add sanit...
45
46
  
  	WARN_ON_ONCE(desc->no_suspend_depth &&
17f480342   Rafael J. Wysocki   genirq / PM: Add ...
47
48
  		     (desc->no_suspend_depth +
  			desc->cond_suspend_depth) != desc->nr_actions);
cab303be9   Thomas Gleixner   genirq: Add sanit...
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
  }
  
  /*
   * Called from __free_irq() with desc->lock held after @action has
   * been removed from the action chain.
   */
  void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action)
  {
  	desc->nr_actions--;
  
  	if (action->flags & IRQF_FORCE_RESUME)
  		desc->force_resume_depth--;
  
  	if (action->flags & IRQF_NO_SUSPEND)
  		desc->no_suspend_depth--;
17f480342   Rafael J. Wysocki   genirq / PM: Add ...
64
65
  	else if (action->flags & IRQF_COND_SUSPEND)
  		desc->cond_suspend_depth--;
cab303be9   Thomas Gleixner   genirq: Add sanit...
66
  }
b80f5f3fc   Jiang Liu   genirq: Remove ir...
67
  static bool suspend_device_irq(struct irq_desc *desc)
8df2e02c5   Thomas Gleixner   genirq: Move susp...
68
  {
90428a8eb   Maulik Shah   genirq/PM: Introd...
69
70
  	unsigned long chipflags = irq_desc_get_chip(desc)->flags;
  	struct irq_data *irqd = &desc->irq_data;
4717f1337   Grygorii Strashko   genirq/PM: Restor...
71
72
  	if (!desc->action || irq_desc_is_chained(desc) ||
  	    desc->no_suspend_depth)
c4df606c4   Thomas Gleixner   genirq: Avoid dou...
73
  		return false;
8df2e02c5   Thomas Gleixner   genirq: Move susp...
74

90428a8eb   Maulik Shah   genirq/PM: Introd...
75
76
77
78
79
80
81
82
83
84
85
86
87
  	if (irqd_is_wakeup_set(irqd)) {
  		irqd_set(irqd, IRQD_WAKEUP_ARMED);
  
  		if ((chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) &&
  		     irqd_irq_disabled(irqd)) {
  			/*
  			 * Interrupt marked for wakeup is in disabled state.
  			 * Enable interrupt here to unmask/enable in irqchip
  			 * to be able to resume with such interrupts.
  			 */
  			__enable_irq(desc);
  			irqd_set(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND);
  		}
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
88
89
90
91
92
93
94
95
  		/*
  		 * We return true here to force the caller to issue
  		 * synchronize_irq(). We need to make sure that the
  		 * IRQD_WAKEUP_ARMED is visible before we return from
  		 * suspend_device_irqs().
  		 */
  		return true;
  	}
b76f16748   Thomas Gleixner   genirq: Mark wake...
96

8df2e02c5   Thomas Gleixner   genirq: Move susp...
97
  	desc->istate |= IRQS_SUSPENDED;
79ff1cda3   Jiang Liu   genirq: Remove ir...
98
  	__disable_irq(desc);
092fadd59   Thomas Gleixner   genirq: Move MASK...
99
100
101
102
103
104
105
  
  	/*
  	 * Hardware which has no wakeup source configuration facility
  	 * requires that the non wakeup interrupts are masked at the
  	 * chip level. The chip implementation indicates that with
  	 * IRQCHIP_MASK_ON_SUSPEND.
  	 */
90428a8eb   Maulik Shah   genirq/PM: Introd...
106
  	if (chipflags & IRQCHIP_MASK_ON_SUSPEND)
092fadd59   Thomas Gleixner   genirq: Move MASK...
107
  		mask_irq(desc);
c4df606c4   Thomas Gleixner   genirq: Avoid dou...
108
  	return true;
8df2e02c5   Thomas Gleixner   genirq: Move susp...
109
  }
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
110
111
112
  /**
   * suspend_device_irqs - disable all currently enabled interrupt lines
   *
8df2e02c5   Thomas Gleixner   genirq: Move susp...
113
114
115
116
117
   * During system-wide suspend or hibernation device drivers need to be
   * prevented from receiving interrupts and this function is provided
   * for this purpose.
   *
   * So we disable all interrupts and mark them IRQS_SUSPENDED except
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
118
   * for those which are unused, those which are marked as not
8df2e02c5   Thomas Gleixner   genirq: Move susp...
119
   * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND
9ce7a2584   Thomas Gleixner   genirq: Simplify ...
120
121
122
123
124
   * set and those which are marked as active wakeup sources.
   *
   * The active wakeup sources are handled by the flow handler entry
   * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the
   * interrupt and notifies the pm core about the wakeup.
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
125
126
127
128
129
130
131
132
   */
  void suspend_device_irqs(void)
  {
  	struct irq_desc *desc;
  	int irq;
  
  	for_each_irq_desc(irq, desc) {
  		unsigned long flags;
c4df606c4   Thomas Gleixner   genirq: Avoid dou...
133
  		bool sync;
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
134

3c646f2c6   NeilBrown   genirq: Don't sus...
135
136
  		if (irq_settings_is_nested_thread(desc))
  			continue;
239007b84   Thomas Gleixner   genirq: Convert i...
137
  		raw_spin_lock_irqsave(&desc->lock, flags);
b80f5f3fc   Jiang Liu   genirq: Remove ir...
138
  		sync = suspend_device_irq(desc);
239007b84   Thomas Gleixner   genirq: Convert i...
139
  		raw_spin_unlock_irqrestore(&desc->lock, flags);
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
140

c4df606c4   Thomas Gleixner   genirq: Avoid dou...
141
  		if (sync)
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
142
  			synchronize_irq(irq);
c4df606c4   Thomas Gleixner   genirq: Avoid dou...
143
  	}
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
144
145
  }
  EXPORT_SYMBOL_GPL(suspend_device_irqs);
b80f5f3fc   Jiang Liu   genirq: Remove ir...
146
  static void resume_irq(struct irq_desc *desc)
8df2e02c5   Thomas Gleixner   genirq: Move susp...
147
  {
90428a8eb   Maulik Shah   genirq/PM: Introd...
148
149
150
151
152
153
154
155
156
157
158
159
160
  	struct irq_data *irqd = &desc->irq_data;
  
  	irqd_clear(irqd, IRQD_WAKEUP_ARMED);
  
  	if (irqd_is_enabled_on_suspend(irqd)) {
  		/*
  		 * Interrupt marked for wakeup was enabled during suspend
  		 * entry. Disable such interrupts to restore them back to
  		 * original state.
  		 */
  		__disable_irq(desc);
  		irqd_clear(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND);
  	}
b76f16748   Thomas Gleixner   genirq: Mark wake...
161

8df2e02c5   Thomas Gleixner   genirq: Move susp...
162
163
  	if (desc->istate & IRQS_SUSPENDED)
  		goto resume;
5417de222   Thomas Gleixner   genirq: Make use ...
164
165
  	/* Force resume the interrupt? */
  	if (!desc->force_resume_depth)
8df2e02c5   Thomas Gleixner   genirq: Move susp...
166
167
168
169
  		return;
  
  	/* Pretend that it got disabled ! */
  	desc->depth++;
a696712c3   Juergen Gross   genirq/PM: Proper...
170
171
  	irq_state_set_disabled(desc);
  	irq_state_set_masked(desc);
8df2e02c5   Thomas Gleixner   genirq: Move susp...
172
173
  resume:
  	desc->istate &= ~IRQS_SUSPENDED;
79ff1cda3   Jiang Liu   genirq: Remove ir...
174
  	__enable_irq(desc);
8df2e02c5   Thomas Gleixner   genirq: Move susp...
175
  }
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
176
  static void resume_irqs(bool want_early)
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
177
178
179
180
181
182
  {
  	struct irq_desc *desc;
  	int irq;
  
  	for_each_irq_desc(irq, desc) {
  		unsigned long flags;
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
183
184
  		bool is_early = desc->action &&
  			desc->action->flags & IRQF_EARLY_RESUME;
ac01810c9   Laxman Dewangan   irq: Enable all i...
185
  		if (!is_early && want_early)
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
186
  			continue;
3c646f2c6   NeilBrown   genirq: Don't sus...
187
188
  		if (irq_settings_is_nested_thread(desc))
  			continue;
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
189

239007b84   Thomas Gleixner   genirq: Convert i...
190
  		raw_spin_lock_irqsave(&desc->lock, flags);
b80f5f3fc   Jiang Liu   genirq: Remove ir...
191
  		resume_irq(desc);
239007b84   Thomas Gleixner   genirq: Convert i...
192
  		raw_spin_unlock_irqrestore(&desc->lock, flags);
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
193
194
  	}
  }
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
195
196
  
  /**
3a79bc63d   Rafael J. Wysocki   PCI: irq: Introdu...
197
198
199
200
201
202
203
   * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup
   * @irq: Interrupt to rearm
   */
  void rearm_wake_irq(unsigned int irq)
  {
  	unsigned long flags;
  	struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
e27b1636e   Guenter Roeck   genirq/PM: Always...
204
  	if (!desc)
3a79bc63d   Rafael J. Wysocki   PCI: irq: Introdu...
205
  		return;
e27b1636e   Guenter Roeck   genirq/PM: Always...
206
207
208
  	if (!(desc->istate & IRQS_SUSPENDED) ||
  	    !irqd_is_wakeup_set(&desc->irq_data))
  		goto unlock;
3a79bc63d   Rafael J. Wysocki   PCI: irq: Introdu...
209
210
211
  	desc->istate &= ~IRQS_SUSPENDED;
  	irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED);
  	__enable_irq(desc);
e27b1636e   Guenter Roeck   genirq/PM: Always...
212
  unlock:
3a79bc63d   Rafael J. Wysocki   PCI: irq: Introdu...
213
214
215
216
  	irq_put_desc_busunlock(desc, flags);
  }
  
  /**
9bab0b7fb   Ian Campbell   genirq: Add IRQF_...
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
   * irq_pm_syscore_ops - enable interrupt lines early
   *
   * Enable all interrupt lines with %IRQF_EARLY_RESUME set.
   */
  static void irq_pm_syscore_resume(void)
  {
  	resume_irqs(true);
  }
  
  static struct syscore_ops irq_pm_syscore_ops = {
  	.resume		= irq_pm_syscore_resume,
  };
  
  static int __init irq_pm_init_ops(void)
  {
  	register_syscore_ops(&irq_pm_syscore_ops);
  	return 0;
  }
  
  device_initcall(irq_pm_init_ops);
  
  /**
   * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs()
   *
   * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously
   * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag
   * set as well as those with %IRQF_FORCE_RESUME.
   */
  void resume_device_irqs(void)
  {
  	resume_irqs(false);
  }
0a0c5168d   Rafael J. Wysocki   PM: Introduce fun...
249
  EXPORT_SYMBOL_GPL(resume_device_irqs);