152a65ff5SThomas Gleixner // SPDX-License-Identifier: GPL-2.0 20a0c5168SRafael J. Wysocki /* 30a0c5168SRafael J. Wysocki * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 40a0c5168SRafael J. Wysocki * 50a0c5168SRafael J. Wysocki * This file contains power management functions related to interrupts. 60a0c5168SRafael J. Wysocki */ 70a0c5168SRafael J. Wysocki 80a0c5168SRafael J. Wysocki #include <linux/irq.h> 90a0c5168SRafael J. Wysocki #include <linux/module.h> 100a0c5168SRafael J. Wysocki #include <linux/interrupt.h> 119ce7a258SThomas Gleixner #include <linux/suspend.h> 129bab0b7fSIan Campbell #include <linux/syscore_ops.h> 130a0c5168SRafael J. Wysocki 140a0c5168SRafael J. Wysocki #include "internals.h" 150a0c5168SRafael J. Wysocki 169ce7a258SThomas Gleixner bool irq_pm_check_wakeup(struct irq_desc *desc) 179ce7a258SThomas Gleixner { 189ce7a258SThomas Gleixner if (irqd_is_wakeup_armed(&desc->irq_data)) { 199ce7a258SThomas Gleixner irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 209ce7a258SThomas Gleixner desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 219ce7a258SThomas Gleixner desc->depth++; 229ce7a258SThomas Gleixner irq_disable(desc); 23a6f5f0ddSAlexandra Yates pm_system_irq_wakeup(irq_desc_get_irq(desc)); 249ce7a258SThomas Gleixner return true; 259ce7a258SThomas Gleixner } 269ce7a258SThomas Gleixner return false; 279ce7a258SThomas Gleixner } 289ce7a258SThomas Gleixner 29cab303beSThomas Gleixner /* 30cab303beSThomas Gleixner * Called from __setup_irq() with desc->lock held after @action has 31cab303beSThomas Gleixner * been installed in the action chain. 32cab303beSThomas Gleixner */ 33cab303beSThomas Gleixner void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) 34cab303beSThomas Gleixner { 35cab303beSThomas Gleixner desc->nr_actions++; 36cab303beSThomas Gleixner 37cab303beSThomas Gleixner if (action->flags & IRQF_FORCE_RESUME) 38cab303beSThomas Gleixner desc->force_resume_depth++; 39cab303beSThomas Gleixner 40cab303beSThomas Gleixner WARN_ON_ONCE(desc->force_resume_depth && 41cab303beSThomas Gleixner desc->force_resume_depth != desc->nr_actions); 42cab303beSThomas Gleixner 43cab303beSThomas Gleixner if (action->flags & IRQF_NO_SUSPEND) 44cab303beSThomas Gleixner desc->no_suspend_depth++; 4517f48034SRafael J. Wysocki else if (action->flags & IRQF_COND_SUSPEND) 4617f48034SRafael J. Wysocki desc->cond_suspend_depth++; 47cab303beSThomas Gleixner 48cab303beSThomas Gleixner WARN_ON_ONCE(desc->no_suspend_depth && 4917f48034SRafael J. Wysocki (desc->no_suspend_depth + 5017f48034SRafael J. Wysocki desc->cond_suspend_depth) != desc->nr_actions); 51cab303beSThomas Gleixner } 52cab303beSThomas Gleixner 53cab303beSThomas Gleixner /* 54cab303beSThomas Gleixner * Called from __free_irq() with desc->lock held after @action has 55cab303beSThomas Gleixner * been removed from the action chain. 56cab303beSThomas Gleixner */ 57cab303beSThomas Gleixner void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 58cab303beSThomas Gleixner { 59cab303beSThomas Gleixner desc->nr_actions--; 60cab303beSThomas Gleixner 61cab303beSThomas Gleixner if (action->flags & IRQF_FORCE_RESUME) 62cab303beSThomas Gleixner desc->force_resume_depth--; 63cab303beSThomas Gleixner 64cab303beSThomas Gleixner if (action->flags & IRQF_NO_SUSPEND) 65cab303beSThomas Gleixner desc->no_suspend_depth--; 6617f48034SRafael J. Wysocki else if (action->flags & IRQF_COND_SUSPEND) 6717f48034SRafael J. Wysocki desc->cond_suspend_depth--; 68cab303beSThomas Gleixner } 69cab303beSThomas Gleixner 70b80f5f3fSJiang Liu static bool suspend_device_irq(struct irq_desc *desc) 718df2e02cSThomas Gleixner { 724717f133SGrygorii Strashko if (!desc->action || irq_desc_is_chained(desc) || 734717f133SGrygorii Strashko desc->no_suspend_depth) 74c4df606cSThomas Gleixner return false; 758df2e02cSThomas Gleixner 769ce7a258SThomas Gleixner if (irqd_is_wakeup_set(&desc->irq_data)) { 77b76f1674SThomas Gleixner irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 789ce7a258SThomas Gleixner /* 799ce7a258SThomas Gleixner * We return true here to force the caller to issue 809ce7a258SThomas Gleixner * synchronize_irq(). We need to make sure that the 819ce7a258SThomas Gleixner * IRQD_WAKEUP_ARMED is visible before we return from 829ce7a258SThomas Gleixner * suspend_device_irqs(). 839ce7a258SThomas Gleixner */ 849ce7a258SThomas Gleixner return true; 859ce7a258SThomas Gleixner } 86b76f1674SThomas Gleixner 878df2e02cSThomas Gleixner desc->istate |= IRQS_SUSPENDED; 8879ff1cdaSJiang Liu __disable_irq(desc); 89092fadd5SThomas Gleixner 90092fadd5SThomas Gleixner /* 91092fadd5SThomas Gleixner * Hardware which has no wakeup source configuration facility 92092fadd5SThomas Gleixner * requires that the non wakeup interrupts are masked at the 93092fadd5SThomas Gleixner * chip level. The chip implementation indicates that with 94092fadd5SThomas Gleixner * IRQCHIP_MASK_ON_SUSPEND. 95092fadd5SThomas Gleixner */ 96092fadd5SThomas Gleixner if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND) 97092fadd5SThomas Gleixner mask_irq(desc); 98c4df606cSThomas Gleixner return true; 998df2e02cSThomas Gleixner } 1008df2e02cSThomas Gleixner 1010a0c5168SRafael J. Wysocki /** 1020a0c5168SRafael J. Wysocki * suspend_device_irqs - disable all currently enabled interrupt lines 1030a0c5168SRafael J. Wysocki * 1048df2e02cSThomas Gleixner * During system-wide suspend or hibernation device drivers need to be 1058df2e02cSThomas Gleixner * prevented from receiving interrupts and this function is provided 1068df2e02cSThomas Gleixner * for this purpose. 1078df2e02cSThomas Gleixner * 1088df2e02cSThomas Gleixner * So we disable all interrupts and mark them IRQS_SUSPENDED except 1099ce7a258SThomas Gleixner * for those which are unused, those which are marked as not 1108df2e02cSThomas Gleixner * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND 1119ce7a258SThomas Gleixner * set and those which are marked as active wakeup sources. 1129ce7a258SThomas Gleixner * 1139ce7a258SThomas Gleixner * The active wakeup sources are handled by the flow handler entry 1149ce7a258SThomas Gleixner * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the 1159ce7a258SThomas Gleixner * interrupt and notifies the pm core about the wakeup. 1160a0c5168SRafael J. Wysocki */ 1170a0c5168SRafael J. Wysocki void suspend_device_irqs(void) 1180a0c5168SRafael J. Wysocki { 1190a0c5168SRafael J. Wysocki struct irq_desc *desc; 1200a0c5168SRafael J. Wysocki int irq; 1210a0c5168SRafael J. Wysocki 1220a0c5168SRafael J. Wysocki for_each_irq_desc(irq, desc) { 1230a0c5168SRafael J. Wysocki unsigned long flags; 124c4df606cSThomas Gleixner bool sync; 1250a0c5168SRafael J. Wysocki 1263c646f2cSNeilBrown if (irq_settings_is_nested_thread(desc)) 1273c646f2cSNeilBrown continue; 128239007b8SThomas Gleixner raw_spin_lock_irqsave(&desc->lock, flags); 129b80f5f3fSJiang Liu sync = suspend_device_irq(desc); 130239007b8SThomas Gleixner raw_spin_unlock_irqrestore(&desc->lock, flags); 1310a0c5168SRafael J. Wysocki 132c4df606cSThomas Gleixner if (sync) 1330a0c5168SRafael J. Wysocki synchronize_irq(irq); 1340a0c5168SRafael J. Wysocki } 135c4df606cSThomas Gleixner } 1360a0c5168SRafael J. Wysocki EXPORT_SYMBOL_GPL(suspend_device_irqs); 1370a0c5168SRafael J. Wysocki 138b80f5f3fSJiang Liu static void resume_irq(struct irq_desc *desc) 1398df2e02cSThomas Gleixner { 140b76f1674SThomas Gleixner irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 141b76f1674SThomas Gleixner 1428df2e02cSThomas Gleixner if (desc->istate & IRQS_SUSPENDED) 1438df2e02cSThomas Gleixner goto resume; 1448df2e02cSThomas Gleixner 1455417de22SThomas Gleixner /* Force resume the interrupt? */ 1465417de22SThomas Gleixner if (!desc->force_resume_depth) 1478df2e02cSThomas Gleixner return; 1488df2e02cSThomas Gleixner 1498df2e02cSThomas Gleixner /* Pretend that it got disabled ! */ 1508df2e02cSThomas Gleixner desc->depth++; 151a696712cSJuergen Gross irq_state_set_disabled(desc); 152a696712cSJuergen Gross irq_state_set_masked(desc); 1538df2e02cSThomas Gleixner resume: 1548df2e02cSThomas Gleixner desc->istate &= ~IRQS_SUSPENDED; 15579ff1cdaSJiang Liu __enable_irq(desc); 1568df2e02cSThomas Gleixner } 1578df2e02cSThomas Gleixner 1589bab0b7fSIan Campbell static void resume_irqs(bool want_early) 1590a0c5168SRafael J. Wysocki { 1600a0c5168SRafael J. Wysocki struct irq_desc *desc; 1610a0c5168SRafael J. Wysocki int irq; 1620a0c5168SRafael J. Wysocki 1630a0c5168SRafael J. Wysocki for_each_irq_desc(irq, desc) { 1640a0c5168SRafael J. Wysocki unsigned long flags; 1659bab0b7fSIan Campbell bool is_early = desc->action && 1669bab0b7fSIan Campbell desc->action->flags & IRQF_EARLY_RESUME; 1679bab0b7fSIan Campbell 168ac01810cSLaxman Dewangan if (!is_early && want_early) 1699bab0b7fSIan Campbell continue; 1703c646f2cSNeilBrown if (irq_settings_is_nested_thread(desc)) 1713c646f2cSNeilBrown continue; 1720a0c5168SRafael J. Wysocki 173239007b8SThomas Gleixner raw_spin_lock_irqsave(&desc->lock, flags); 174b80f5f3fSJiang Liu resume_irq(desc); 175239007b8SThomas Gleixner raw_spin_unlock_irqrestore(&desc->lock, flags); 1760a0c5168SRafael J. Wysocki } 1770a0c5168SRafael J. Wysocki } 1789bab0b7fSIan Campbell 1799bab0b7fSIan Campbell /** 1803a79bc63SRafael J. Wysocki * rearm_wake_irq - rearm a wakeup interrupt line after signaling wakeup 1813a79bc63SRafael J. Wysocki * @irq: Interrupt to rearm 1823a79bc63SRafael J. Wysocki */ 1833a79bc63SRafael J. Wysocki void rearm_wake_irq(unsigned int irq) 1843a79bc63SRafael J. Wysocki { 1853a79bc63SRafael J. Wysocki unsigned long flags; 1863a79bc63SRafael J. Wysocki struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL); 1873a79bc63SRafael J. Wysocki 1883a79bc63SRafael J. Wysocki if (!desc || !(desc->istate & IRQS_SUSPENDED) || 1893a79bc63SRafael J. Wysocki !irqd_is_wakeup_set(&desc->irq_data)) 1903a79bc63SRafael J. Wysocki return; 1913a79bc63SRafael J. Wysocki 1923a79bc63SRafael J. Wysocki desc->istate &= ~IRQS_SUSPENDED; 1933a79bc63SRafael J. Wysocki irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 1943a79bc63SRafael J. Wysocki __enable_irq(desc); 1953a79bc63SRafael J. Wysocki 1963a79bc63SRafael J. Wysocki irq_put_desc_busunlock(desc, flags); 1973a79bc63SRafael J. Wysocki } 1983a79bc63SRafael J. Wysocki 1993a79bc63SRafael J. Wysocki /** 2009bab0b7fSIan Campbell * irq_pm_syscore_ops - enable interrupt lines early 2019bab0b7fSIan Campbell * 2029bab0b7fSIan Campbell * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 2039bab0b7fSIan Campbell */ 2049bab0b7fSIan Campbell static void irq_pm_syscore_resume(void) 2059bab0b7fSIan Campbell { 2069bab0b7fSIan Campbell resume_irqs(true); 2079bab0b7fSIan Campbell } 2089bab0b7fSIan Campbell 2099bab0b7fSIan Campbell static struct syscore_ops irq_pm_syscore_ops = { 2109bab0b7fSIan Campbell .resume = irq_pm_syscore_resume, 2119bab0b7fSIan Campbell }; 2129bab0b7fSIan Campbell 2139bab0b7fSIan Campbell static int __init irq_pm_init_ops(void) 2149bab0b7fSIan Campbell { 2159bab0b7fSIan Campbell register_syscore_ops(&irq_pm_syscore_ops); 2169bab0b7fSIan Campbell return 0; 2179bab0b7fSIan Campbell } 2189bab0b7fSIan Campbell 2199bab0b7fSIan Campbell device_initcall(irq_pm_init_ops); 2209bab0b7fSIan Campbell 2219bab0b7fSIan Campbell /** 2229bab0b7fSIan Campbell * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 2239bab0b7fSIan Campbell * 2249bab0b7fSIan Campbell * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 2259bab0b7fSIan Campbell * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 2269bab0b7fSIan Campbell * set as well as those with %IRQF_FORCE_RESUME. 2279bab0b7fSIan Campbell */ 2289bab0b7fSIan Campbell void resume_device_irqs(void) 2299bab0b7fSIan Campbell { 2309bab0b7fSIan Campbell resume_irqs(false); 2319bab0b7fSIan Campbell } 2320a0c5168SRafael J. Wysocki EXPORT_SYMBOL_GPL(resume_device_irqs); 233