18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * This file contains power management functions related to interrupts. 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/irq.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 118c2ecf20Sopenharmony_ci#include <linux/suspend.h> 128c2ecf20Sopenharmony_ci#include <linux/syscore_ops.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include "internals.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cibool irq_pm_check_wakeup(struct irq_desc *desc) 178c2ecf20Sopenharmony_ci{ 188c2ecf20Sopenharmony_ci if (irqd_is_wakeup_armed(&desc->irq_data)) { 198c2ecf20Sopenharmony_ci irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 208c2ecf20Sopenharmony_ci desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 218c2ecf20Sopenharmony_ci desc->depth++; 228c2ecf20Sopenharmony_ci irq_disable(desc); 238c2ecf20Sopenharmony_ci pm_system_irq_wakeup(irq_desc_get_irq(desc)); 248c2ecf20Sopenharmony_ci return true; 258c2ecf20Sopenharmony_ci } 268c2ecf20Sopenharmony_ci return false; 278c2ecf20Sopenharmony_ci} 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/* 308c2ecf20Sopenharmony_ci * Called from __setup_irq() with desc->lock held after @action has 318c2ecf20Sopenharmony_ci * been installed in the action chain. 328c2ecf20Sopenharmony_ci */ 338c2ecf20Sopenharmony_civoid irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) 348c2ecf20Sopenharmony_ci{ 358c2ecf20Sopenharmony_ci desc->nr_actions++; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci if (action->flags & IRQF_FORCE_RESUME) 388c2ecf20Sopenharmony_ci desc->force_resume_depth++; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci WARN_ON_ONCE(desc->force_resume_depth && 418c2ecf20Sopenharmony_ci desc->force_resume_depth != desc->nr_actions); 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci if (action->flags & IRQF_NO_SUSPEND) 448c2ecf20Sopenharmony_ci desc->no_suspend_depth++; 458c2ecf20Sopenharmony_ci else if (action->flags & IRQF_COND_SUSPEND) 468c2ecf20Sopenharmony_ci desc->cond_suspend_depth++; 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci WARN_ON_ONCE(desc->no_suspend_depth && 498c2ecf20Sopenharmony_ci (desc->no_suspend_depth + 508c2ecf20Sopenharmony_ci desc->cond_suspend_depth) != desc->nr_actions); 518c2ecf20Sopenharmony_ci} 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci/* 548c2ecf20Sopenharmony_ci * Called from __free_irq() with desc->lock held after @action has 558c2ecf20Sopenharmony_ci * been removed from the action chain. 568c2ecf20Sopenharmony_ci */ 578c2ecf20Sopenharmony_civoid irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci desc->nr_actions--; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci if (action->flags & IRQF_FORCE_RESUME) 628c2ecf20Sopenharmony_ci desc->force_resume_depth--; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci if (action->flags & IRQF_NO_SUSPEND) 658c2ecf20Sopenharmony_ci desc->no_suspend_depth--; 668c2ecf20Sopenharmony_ci else if (action->flags & IRQF_COND_SUSPEND) 678c2ecf20Sopenharmony_ci desc->cond_suspend_depth--; 688c2ecf20Sopenharmony_ci} 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_cistatic bool suspend_device_irq(struct irq_desc *desc) 718c2ecf20Sopenharmony_ci{ 728c2ecf20Sopenharmony_ci unsigned long chipflags = irq_desc_get_chip(desc)->flags; 738c2ecf20Sopenharmony_ci struct irq_data *irqd = &desc->irq_data; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (!desc->action || irq_desc_is_chained(desc) || 768c2ecf20Sopenharmony_ci desc->no_suspend_depth) 778c2ecf20Sopenharmony_ci return false; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (irqd_is_wakeup_set(irqd)) { 808c2ecf20Sopenharmony_ci irqd_set(irqd, IRQD_WAKEUP_ARMED); 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci if ((chipflags & IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND) && 838c2ecf20Sopenharmony_ci irqd_irq_disabled(irqd)) { 848c2ecf20Sopenharmony_ci /* 858c2ecf20Sopenharmony_ci * Interrupt marked for wakeup is in disabled state. 868c2ecf20Sopenharmony_ci * Enable interrupt here to unmask/enable in irqchip 878c2ecf20Sopenharmony_ci * to be able to resume with such interrupts. 888c2ecf20Sopenharmony_ci */ 898c2ecf20Sopenharmony_ci __enable_irq(desc); 908c2ecf20Sopenharmony_ci irqd_set(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND); 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci /* 938c2ecf20Sopenharmony_ci * We return true here to force the caller to issue 948c2ecf20Sopenharmony_ci * synchronize_irq(). We need to make sure that the 958c2ecf20Sopenharmony_ci * IRQD_WAKEUP_ARMED is visible before we return from 968c2ecf20Sopenharmony_ci * suspend_device_irqs(). 978c2ecf20Sopenharmony_ci */ 988c2ecf20Sopenharmony_ci return true; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci desc->istate |= IRQS_SUSPENDED; 1028c2ecf20Sopenharmony_ci __disable_irq(desc); 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_ci /* 1058c2ecf20Sopenharmony_ci * Hardware which has no wakeup source configuration facility 1068c2ecf20Sopenharmony_ci * requires that the non wakeup interrupts are masked at the 1078c2ecf20Sopenharmony_ci * chip level. The chip implementation indicates that with 1088c2ecf20Sopenharmony_ci * IRQCHIP_MASK_ON_SUSPEND. 1098c2ecf20Sopenharmony_ci */ 1108c2ecf20Sopenharmony_ci if (chipflags & IRQCHIP_MASK_ON_SUSPEND) 1118c2ecf20Sopenharmony_ci mask_irq(desc); 1128c2ecf20Sopenharmony_ci return true; 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci/** 1168c2ecf20Sopenharmony_ci * suspend_device_irqs - disable all currently enabled interrupt lines 1178c2ecf20Sopenharmony_ci * 1188c2ecf20Sopenharmony_ci * During system-wide suspend or hibernation device drivers need to be 1198c2ecf20Sopenharmony_ci * prevented from receiving interrupts and this function is provided 1208c2ecf20Sopenharmony_ci * for this purpose. 1218c2ecf20Sopenharmony_ci * 1228c2ecf20Sopenharmony_ci * So we disable all interrupts and mark them IRQS_SUSPENDED except 1238c2ecf20Sopenharmony_ci * for those which are unused, those which are marked as not 1248c2ecf20Sopenharmony_ci * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND 1258c2ecf20Sopenharmony_ci * set and those which are marked as active wakeup sources. 1268c2ecf20Sopenharmony_ci * 1278c2ecf20Sopenharmony_ci * The active wakeup sources are handled by the flow handler entry 1288c2ecf20Sopenharmony_ci * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the 1298c2ecf20Sopenharmony_ci * interrupt and notifies the pm core about the wakeup. 1308c2ecf20Sopenharmony_ci */ 1318c2ecf20Sopenharmony_civoid suspend_device_irqs(void) 1328c2ecf20Sopenharmony_ci{ 1338c2ecf20Sopenharmony_ci struct irq_desc *desc; 1348c2ecf20Sopenharmony_ci int irq; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci for_each_irq_desc(irq, desc) { 1378c2ecf20Sopenharmony_ci unsigned long flags; 1388c2ecf20Sopenharmony_ci bool sync; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (irq_settings_is_nested_thread(desc)) 1418c2ecf20Sopenharmony_ci continue; 1428c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&desc->lock, flags); 1438c2ecf20Sopenharmony_ci sync = suspend_device_irq(desc); 1448c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&desc->lock, flags); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci if (sync) 1478c2ecf20Sopenharmony_ci synchronize_irq(irq); 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci} 1508c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(suspend_device_irqs); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic void resume_irq(struct irq_desc *desc) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci struct irq_data *irqd = &desc->irq_data; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci irqd_clear(irqd, IRQD_WAKEUP_ARMED); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci if (irqd_is_enabled_on_suspend(irqd)) { 1598c2ecf20Sopenharmony_ci /* 1608c2ecf20Sopenharmony_ci * Interrupt marked for wakeup was enabled during suspend 1618c2ecf20Sopenharmony_ci * entry. Disable such interrupts to restore them back to 1628c2ecf20Sopenharmony_ci * original state. 1638c2ecf20Sopenharmony_ci */ 1648c2ecf20Sopenharmony_ci __disable_irq(desc); 1658c2ecf20Sopenharmony_ci irqd_clear(irqd, IRQD_IRQ_ENABLED_ON_SUSPEND); 1668c2ecf20Sopenharmony_ci } 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (desc->istate & IRQS_SUSPENDED) 1698c2ecf20Sopenharmony_ci goto resume; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci /* Force resume the interrupt? */ 1728c2ecf20Sopenharmony_ci if (!desc->force_resume_depth) 1738c2ecf20Sopenharmony_ci return; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci /* Pretend that it got disabled ! */ 1768c2ecf20Sopenharmony_ci desc->depth++; 1778c2ecf20Sopenharmony_ci irq_state_set_disabled(desc); 1788c2ecf20Sopenharmony_ci irq_state_set_masked(desc); 1798c2ecf20Sopenharmony_ciresume: 1808c2ecf20Sopenharmony_ci desc->istate &= ~IRQS_SUSPENDED; 1818c2ecf20Sopenharmony_ci __enable_irq(desc); 1828c2ecf20Sopenharmony_ci} 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_cistatic void resume_irqs(bool want_early) 1858c2ecf20Sopenharmony_ci{ 1868c2ecf20Sopenharmony_ci struct irq_desc *desc; 1878c2ecf20Sopenharmony_ci int irq; 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci for_each_irq_desc(irq, desc) { 1908c2ecf20Sopenharmony_ci unsigned long flags; 1918c2ecf20Sopenharmony_ci bool is_early = desc->action && 1928c2ecf20Sopenharmony_ci desc->action->flags & IRQF_EARLY_RESUME; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (!is_early && want_early) 1958c2ecf20Sopenharmony_ci continue; 1968c2ecf20Sopenharmony_ci if (irq_settings_is_nested_thread(desc)) 1978c2ecf20Sopenharmony_ci continue; 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci raw_spin_lock_irqsave(&desc->lock, flags); 2008c2ecf20Sopenharmony_ci resume_irq(desc); 2018c2ecf20Sopenharmony_ci raw_spin_unlock_irqrestore(&desc->lock, flags); 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci} 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci/** 2068c2ecf20Sopenharmony_ci * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup 2078c2ecf20Sopenharmony_ci * @irq: Interrupt to rearm 2088c2ecf20Sopenharmony_ci */ 2098c2ecf20Sopenharmony_civoid rearm_wake_irq(unsigned int irq) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci unsigned long flags; 2128c2ecf20Sopenharmony_ci struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci if (!desc) 2158c2ecf20Sopenharmony_ci return; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci if (!(desc->istate & IRQS_SUSPENDED) || 2188c2ecf20Sopenharmony_ci !irqd_is_wakeup_set(&desc->irq_data)) 2198c2ecf20Sopenharmony_ci goto unlock; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci desc->istate &= ~IRQS_SUSPENDED; 2228c2ecf20Sopenharmony_ci irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 2238c2ecf20Sopenharmony_ci __enable_irq(desc); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ciunlock: 2268c2ecf20Sopenharmony_ci irq_put_desc_busunlock(desc, flags); 2278c2ecf20Sopenharmony_ci} 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci/** 2308c2ecf20Sopenharmony_ci * irq_pm_syscore_ops - enable interrupt lines early 2318c2ecf20Sopenharmony_ci * 2328c2ecf20Sopenharmony_ci * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 2338c2ecf20Sopenharmony_ci */ 2348c2ecf20Sopenharmony_cistatic void irq_pm_syscore_resume(void) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci resume_irqs(true); 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_cistatic struct syscore_ops irq_pm_syscore_ops = { 2408c2ecf20Sopenharmony_ci .resume = irq_pm_syscore_resume, 2418c2ecf20Sopenharmony_ci}; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_cistatic int __init irq_pm_init_ops(void) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci register_syscore_ops(&irq_pm_syscore_ops); 2468c2ecf20Sopenharmony_ci return 0; 2478c2ecf20Sopenharmony_ci} 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_cidevice_initcall(irq_pm_init_ops); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci/** 2528c2ecf20Sopenharmony_ci * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 2538c2ecf20Sopenharmony_ci * 2548c2ecf20Sopenharmony_ci * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 2558c2ecf20Sopenharmony_ci * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 2568c2ecf20Sopenharmony_ci * set as well as those with %IRQF_FORCE_RESUME. 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_civoid resume_device_irqs(void) 2598c2ecf20Sopenharmony_ci{ 2608c2ecf20Sopenharmony_ci resume_irqs(false); 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(resume_device_irqs); 263