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 : 151 0 : static void resume_irq(struct irq_desc *desc) 152 : { 153 0 : struct irq_data *irqd = &desc->irq_data; 154 : 155 0 : irqd_clear(irqd, IRQD_WAKEUP_ARMED); 156 : 157 0 : if (irqd_is_enabled_on_suspend(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 0 : __disable_irq(desc); 164 0 : irqd_clear(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND); 165 : } 166 : 167 0 : if (desc->istate & IRQS_SUSPENDED) 168 : goto resume; 169 : 170 : /* Force resume the interrupt? */ 171 0 : if (!desc->force_resume_depth) 172 : return; 173 : 174 : /* Pretend that it got disabled ! */ 175 0 : desc->depth++; 176 0 : irq_state_set_disabled(desc); 177 : irq_state_set_masked(desc); 178 : resume: 179 0 : desc->istate &= ~IRQS_SUSPENDED; 180 0 : __enable_irq(desc); 181 : } 182 : 183 0 : static void resume_irqs(bool want_early) 184 : { 185 : struct irq_desc *desc; 186 : int irq; 187 : 188 0 : for_each_irq_desc(irq, desc) { 189 : unsigned long flags; 190 0 : bool is_early = desc->action && 191 0 : desc->action->flags & IRQF_EARLY_RESUME; 192 : 193 0 : if (!is_early && want_early) 194 0 : continue; 195 0 : if (irq_settings_is_nested_thread(desc)) 196 0 : continue; 197 : 198 0 : raw_spin_lock_irqsave(&desc->lock, flags); 199 0 : resume_irq(desc); 200 0 : raw_spin_unlock_irqrestore(&desc->lock, flags); 201 : } 202 0 : } 203 : 204 : /** 205 : * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup 206 : * @irq: Interrupt to rearm 207 : */ 208 0 : void rearm_wake_irq(unsigned int irq) 209 : { 210 : unsigned long flags; 211 0 : struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); 212 : 213 0 : if (!desc) 214 0 : return; 215 : 216 0 : if (!(desc->istate & IRQS_SUSPENDED) || 217 0 : !irqd_is_wakeup_set(&desc->irq_data)) 218 : goto unlock; 219 : 220 0 : desc->istate &= ~IRQS_SUSPENDED; 221 0 : irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 222 0 : __enable_irq(desc); 223 : 224 : unlock: 225 0 : 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 0 : static void irq_pm_syscore_resume(void) 234 : { 235 0 : resume_irqs(true); 236 0 : } 237 : 238 : static struct syscore_ops irq_pm_syscore_ops = { 239 : .resume = irq_pm_syscore_resume, 240 : }; 241 : 242 1 : static int __init irq_pm_init_ops(void) 243 : { 244 1 : register_syscore_ops(&irq_pm_syscore_ops); 245 1 : 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 0 : void resume_device_irqs(void) 258 : { 259 0 : resume_irqs(false); 260 0 : }