Line data Source code
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 0 : bool irq_pm_check_wakeup(struct irq_desc *desc) 17 : { 18 0 : if (irqd_is_wakeup_armed(&desc->irq_data)) { 19 0 : irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 20 0 : desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 21 0 : desc->depth++; 22 0 : irq_disable(desc); 23 0 : pm_system_irq_wakeup(irq_desc_get_irq(desc)); 24 0 : 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 2 : void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) 34 : { 35 2 : desc->nr_actions++; 36 : 37 2 : if (action->flags & IRQF_FORCE_RESUME) 38 0 : desc->force_resume_depth++; 39 : 40 2 : WARN_ON_ONCE(desc->force_resume_depth && 41 : desc->force_resume_depth != desc->nr_actions); 42 : 43 2 : if (action->flags & IRQF_NO_SUSPEND) 44 1 : desc->no_suspend_depth++; 45 1 : else if (action->flags & IRQF_COND_SUSPEND) 46 0 : desc->cond_suspend_depth++; 47 : 48 2 : WARN_ON_ONCE(desc->no_suspend_depth && 49 : (desc->no_suspend_depth + 50 : desc->cond_suspend_depth) != desc->nr_actions); 51 2 : } 52 : 53 : /* 54 : * Called from __free_irq() with desc->lock held after @action has 55 : * been removed from the action chain. 56 : */ 57 0 : void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 58 : { 59 0 : desc->nr_actions--; 60 : 61 0 : if (action->flags & IRQF_FORCE_RESUME) 62 0 : desc->force_resume_depth--; 63 : 64 0 : if (action->flags & IRQF_NO_SUSPEND) 65 0 : desc->no_suspend_depth--; 66 0 : else if (action->flags & IRQF_COND_SUSPEND) 67 0 : desc->cond_suspend_depth--; 68 0 : } 69 : 70 0 : static bool suspend_device_irq(struct irq_desc *desc) 71 : { 72 0 : unsigned long chipflags = irq_desc_get_chip(desc)->flags; 73 0 : struct irq_data *irqd = &desc->irq_data; 74 : 75 0 : if (!desc->action || irq_desc_is_chained(desc) || 76 0 : desc->no_suspend_depth) 77 : return false; 78 : 79 0 : if (irqd_is_wakeup_set(irqd)) { 80 0 : irqd_set(irqd, IRQD_WAKEUP_ARMED); 81 : 82 0 : if ((chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) && 83 0 : irqd_irq_disabled(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 0 : __enable_irq(desc); 90 0 : irqd_set(irqd, 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 0 : desc->istate |= IRQS_SUSPENDED; 102 0 : __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 0 : if (chipflags & IRQCHIP_MASK_ON_SUSPEND) 111 0 : 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 0 : void suspend_device_irqs(void) 132 : { 133 : struct irq_desc *desc; 134 : int irq; 135 : 136 0 : for_each_irq_desc(irq, desc) { 137 : unsigned long flags; 138 : bool sync; 139 : 140 0 : if (irq_settings_is_nested_thread(desc)) 141 0 : continue; 142 0 : raw_spin_lock_irqsave(&desc->lock, flags); 143 0 : sync = suspend_device_irq(desc); 144 0 : raw_spin_unlock_irqrestore(&desc->lock, flags); 145 : 146 0 : if (sync) 147 0 : synchronize_irq(irq); 148 : } 149 0 : } 150 : EXPORT_SYMBOL_GPL(suspend_device_irqs); 151 : 152 0 : static void resume_irq(struct irq_desc *desc) 153 : { 154 0 : struct irq_data *irqd = &desc->irq_data; 155 : 156 0 : irqd_clear(irqd, IRQD_WAKEUP_ARMED); 157 : 158 0 : if (irqd_is_enabled_on_suspend(irqd)) { 159 : /* 160 : * Interrupt marked for wakeup was enabled during suspend 161 : * entry. Disable such interrupts to restore them back to 162 : * original state. 163 : */ 164 0 : __disable_irq(desc); 165 0 : irqd_clear(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND); 166 : } 167 : 168 0 : if (desc->istate & IRQS_SUSPENDED) 169 : goto resume; 170 : 171 : /* Force resume the interrupt? */ 172 0 : if (!desc->force_resume_depth) 173 : return; 174 : 175 : /* Pretend that it got disabled ! */ 176 0 : desc->depth++; 177 0 : irq_state_set_disabled(desc); 178 : irq_state_set_masked(desc); 179 : resume: 180 0 : desc->istate &= ~IRQS_SUSPENDED; 181 0 : __enable_irq(desc); 182 : } 183 : 184 0 : static void resume_irqs(bool want_early) 185 : { 186 : struct irq_desc *desc; 187 : int irq; 188 : 189 0 : for_each_irq_desc(irq, desc) { 190 : unsigned long flags; 191 0 : bool is_early = desc->action && 192 0 : desc->action->flags & IRQF_EARLY_RESUME; 193 : 194 0 : if (!is_early && want_early) 195 0 : continue; 196 0 : if (irq_settings_is_nested_thread(desc)) 197 0 : continue; 198 : 199 0 : raw_spin_lock_irqsave(&desc->lock, flags); 200 0 : resume_irq(desc); 201 0 : raw_spin_unlock_irqrestore(&desc->lock, flags); 202 : } 203 0 : } 204 : 205 : /** 206 : * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup 207 : * @irq: Interrupt to rearm 208 : */ 209 0 : void rearm_wake_irq(unsigned int irq) 210 : { 211 : unsigned long flags; 212 0 : struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); 213 : 214 0 : if (!desc) 215 0 : return; 216 : 217 0 : if (!(desc->istate & IRQS_SUSPENDED) || 218 0 : !irqd_is_wakeup_set(&desc->irq_data)) 219 : goto unlock; 220 : 221 0 : desc->istate &= ~IRQS_SUSPENDED; 222 0 : irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 223 0 : __enable_irq(desc); 224 : 225 : unlock: 226 0 : irq_put_desc_busunlock(desc, flags); 227 : } 228 : 229 : /** 230 : * irq_pm_syscore_resume - enable interrupt lines early 231 : * 232 : * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 233 : */ 234 0 : static void irq_pm_syscore_resume(void) 235 : { 236 0 : resume_irqs(true); 237 0 : } 238 : 239 : static struct syscore_ops irq_pm_syscore_ops = { 240 : .resume = irq_pm_syscore_resume, 241 : }; 242 : 243 1 : static int __init irq_pm_init_ops(void) 244 : { 245 1 : register_syscore_ops(&irq_pm_syscore_ops); 246 1 : return 0; 247 : } 248 : 249 : device_initcall(irq_pm_init_ops); 250 : 251 : /** 252 : * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 253 : * 254 : * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 255 : * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 256 : * set as well as those with %IRQF_FORCE_RESUME. 257 : */ 258 0 : void resume_device_irqs(void) 259 : { 260 0 : resume_irqs(false); 261 0 : } 262 : EXPORT_SYMBOL_GPL(resume_device_irqs);