10a0c5168SRafael J. Wysocki /* 20a0c5168SRafael J. Wysocki * linux/kernel/irq/pm.c 30a0c5168SRafael J. Wysocki * 40a0c5168SRafael J. Wysocki * Copyright (C) 2009 Rafael J. Wysocki <rjw@sisk.pl>, Novell Inc. 50a0c5168SRafael J. Wysocki * 60a0c5168SRafael J. Wysocki * This file contains power management functions related to interrupts. 70a0c5168SRafael J. Wysocki */ 80a0c5168SRafael J. Wysocki 90a0c5168SRafael J. Wysocki #include <linux/irq.h> 100a0c5168SRafael J. Wysocki #include <linux/module.h> 110a0c5168SRafael J. Wysocki #include <linux/interrupt.h> 129ce7a258SThomas Gleixner #include <linux/suspend.h> 139bab0b7fSIan Campbell #include <linux/syscore_ops.h> 140a0c5168SRafael J. Wysocki 150a0c5168SRafael J. Wysocki #include "internals.h" 160a0c5168SRafael J. Wysocki 179ce7a258SThomas Gleixner bool irq_pm_check_wakeup(struct irq_desc *desc) 189ce7a258SThomas Gleixner { 199ce7a258SThomas Gleixner if (irqd_is_wakeup_armed(&desc->irq_data)) { 209ce7a258SThomas Gleixner irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 219ce7a258SThomas Gleixner desc->istate |= IRQS_SUSPENDED | IRQS_PENDING; 229ce7a258SThomas Gleixner desc->depth++; 239ce7a258SThomas Gleixner irq_disable(desc); 249ce7a258SThomas Gleixner pm_system_wakeup(); 259ce7a258SThomas Gleixner return true; 269ce7a258SThomas Gleixner } 279ce7a258SThomas Gleixner return false; 289ce7a258SThomas Gleixner } 299ce7a258SThomas Gleixner 30cab303beSThomas Gleixner /* 31cab303beSThomas Gleixner * Called from __setup_irq() with desc->lock held after @action has 32cab303beSThomas Gleixner * been installed in the action chain. 33cab303beSThomas Gleixner */ 34cab303beSThomas Gleixner void irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) 35cab303beSThomas Gleixner { 36cab303beSThomas Gleixner desc->nr_actions++; 37cab303beSThomas Gleixner 38cab303beSThomas Gleixner if (action->flags & IRQF_FORCE_RESUME) 39cab303beSThomas Gleixner desc->force_resume_depth++; 40cab303beSThomas Gleixner 41cab303beSThomas Gleixner WARN_ON_ONCE(desc->force_resume_depth && 42cab303beSThomas Gleixner desc->force_resume_depth != desc->nr_actions); 43cab303beSThomas Gleixner 44cab303beSThomas Gleixner if (action->flags & IRQF_NO_SUSPEND) 45cab303beSThomas Gleixner desc->no_suspend_depth++; 46cab303beSThomas Gleixner 47cab303beSThomas Gleixner WARN_ON_ONCE(desc->no_suspend_depth && 48cab303beSThomas Gleixner desc->no_suspend_depth != desc->nr_actions); 49cab303beSThomas Gleixner } 50cab303beSThomas Gleixner 51cab303beSThomas Gleixner /* 52cab303beSThomas Gleixner * Called from __free_irq() with desc->lock held after @action has 53cab303beSThomas Gleixner * been removed from the action chain. 54cab303beSThomas Gleixner */ 55cab303beSThomas Gleixner void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 56cab303beSThomas Gleixner { 57cab303beSThomas Gleixner desc->nr_actions--; 58cab303beSThomas Gleixner 59cab303beSThomas Gleixner if (action->flags & IRQF_FORCE_RESUME) 60cab303beSThomas Gleixner desc->force_resume_depth--; 61cab303beSThomas Gleixner 62cab303beSThomas Gleixner if (action->flags & IRQF_NO_SUSPEND) 63cab303beSThomas Gleixner desc->no_suspend_depth--; 64cab303beSThomas Gleixner } 65cab303beSThomas Gleixner 66c4df606cSThomas Gleixner static bool suspend_device_irq(struct irq_desc *desc, int irq) 678df2e02cSThomas Gleixner { 685417de22SThomas Gleixner if (!desc->action || desc->no_suspend_depth) 69c4df606cSThomas Gleixner return false; 708df2e02cSThomas Gleixner 719ce7a258SThomas Gleixner if (irqd_is_wakeup_set(&desc->irq_data)) { 72b76f1674SThomas Gleixner irqd_set(&desc->irq_data, IRQD_WAKEUP_ARMED); 739ce7a258SThomas Gleixner /* 749ce7a258SThomas Gleixner * We return true here to force the caller to issue 759ce7a258SThomas Gleixner * synchronize_irq(). We need to make sure that the 769ce7a258SThomas Gleixner * IRQD_WAKEUP_ARMED is visible before we return from 779ce7a258SThomas Gleixner * suspend_device_irqs(). 789ce7a258SThomas Gleixner */ 799ce7a258SThomas Gleixner return true; 809ce7a258SThomas Gleixner } 81b76f1674SThomas Gleixner 828df2e02cSThomas Gleixner desc->istate |= IRQS_SUSPENDED; 838df2e02cSThomas Gleixner __disable_irq(desc, irq); 84092fadd5SThomas Gleixner 85092fadd5SThomas Gleixner /* 86092fadd5SThomas Gleixner * Hardware which has no wakeup source configuration facility 87092fadd5SThomas Gleixner * requires that the non wakeup interrupts are masked at the 88092fadd5SThomas Gleixner * chip level. The chip implementation indicates that with 89092fadd5SThomas Gleixner * IRQCHIP_MASK_ON_SUSPEND. 90092fadd5SThomas Gleixner */ 91092fadd5SThomas Gleixner if (irq_desc_get_chip(desc)->flags & IRQCHIP_MASK_ON_SUSPEND) 92092fadd5SThomas Gleixner mask_irq(desc); 93c4df606cSThomas Gleixner return true; 948df2e02cSThomas Gleixner } 958df2e02cSThomas Gleixner 960a0c5168SRafael J. Wysocki /** 970a0c5168SRafael J. Wysocki * suspend_device_irqs - disable all currently enabled interrupt lines 980a0c5168SRafael J. Wysocki * 998df2e02cSThomas Gleixner * During system-wide suspend or hibernation device drivers need to be 1008df2e02cSThomas Gleixner * prevented from receiving interrupts and this function is provided 1018df2e02cSThomas Gleixner * for this purpose. 1028df2e02cSThomas Gleixner * 1038df2e02cSThomas Gleixner * So we disable all interrupts and mark them IRQS_SUSPENDED except 1049ce7a258SThomas Gleixner * for those which are unused, those which are marked as not 1058df2e02cSThomas Gleixner * suspendable via an interrupt request with the flag IRQF_NO_SUSPEND 1069ce7a258SThomas Gleixner * set and those which are marked as active wakeup sources. 1079ce7a258SThomas Gleixner * 1089ce7a258SThomas Gleixner * The active wakeup sources are handled by the flow handler entry 1099ce7a258SThomas Gleixner * code which checks for the IRQD_WAKEUP_ARMED flag, suspends the 1109ce7a258SThomas Gleixner * interrupt and notifies the pm core about the wakeup. 1110a0c5168SRafael J. Wysocki */ 1120a0c5168SRafael J. Wysocki void suspend_device_irqs(void) 1130a0c5168SRafael J. Wysocki { 1140a0c5168SRafael J. Wysocki struct irq_desc *desc; 1150a0c5168SRafael J. Wysocki int irq; 1160a0c5168SRafael J. Wysocki 1170a0c5168SRafael J. Wysocki for_each_irq_desc(irq, desc) { 1180a0c5168SRafael J. Wysocki unsigned long flags; 119c4df606cSThomas Gleixner bool sync; 1200a0c5168SRafael J. Wysocki 121239007b8SThomas Gleixner raw_spin_lock_irqsave(&desc->lock, flags); 122c4df606cSThomas Gleixner sync = suspend_device_irq(desc, irq); 123239007b8SThomas Gleixner raw_spin_unlock_irqrestore(&desc->lock, flags); 1240a0c5168SRafael J. Wysocki 125c4df606cSThomas Gleixner if (sync) 1260a0c5168SRafael J. Wysocki synchronize_irq(irq); 1270a0c5168SRafael J. Wysocki } 128c4df606cSThomas Gleixner } 1290a0c5168SRafael J. Wysocki EXPORT_SYMBOL_GPL(suspend_device_irqs); 1300a0c5168SRafael J. Wysocki 1318df2e02cSThomas Gleixner static void resume_irq(struct irq_desc *desc, int irq) 1328df2e02cSThomas Gleixner { 133b76f1674SThomas Gleixner irqd_clear(&desc->irq_data, IRQD_WAKEUP_ARMED); 134b76f1674SThomas Gleixner 1358df2e02cSThomas Gleixner if (desc->istate & IRQS_SUSPENDED) 1368df2e02cSThomas Gleixner goto resume; 1378df2e02cSThomas Gleixner 1385417de22SThomas Gleixner /* Force resume the interrupt? */ 1395417de22SThomas Gleixner if (!desc->force_resume_depth) 1408df2e02cSThomas Gleixner return; 1418df2e02cSThomas Gleixner 1428df2e02cSThomas Gleixner /* Pretend that it got disabled ! */ 1438df2e02cSThomas Gleixner desc->depth++; 1448df2e02cSThomas Gleixner resume: 1458df2e02cSThomas Gleixner desc->istate &= ~IRQS_SUSPENDED; 1468df2e02cSThomas Gleixner __enable_irq(desc, irq); 1478df2e02cSThomas Gleixner } 1488df2e02cSThomas Gleixner 1499bab0b7fSIan Campbell static void resume_irqs(bool want_early) 1500a0c5168SRafael J. Wysocki { 1510a0c5168SRafael J. Wysocki struct irq_desc *desc; 1520a0c5168SRafael J. Wysocki int irq; 1530a0c5168SRafael J. Wysocki 1540a0c5168SRafael J. Wysocki for_each_irq_desc(irq, desc) { 1550a0c5168SRafael J. Wysocki unsigned long flags; 1569bab0b7fSIan Campbell bool is_early = desc->action && 1579bab0b7fSIan Campbell desc->action->flags & IRQF_EARLY_RESUME; 1589bab0b7fSIan Campbell 159ac01810cSLaxman Dewangan if (!is_early && want_early) 1609bab0b7fSIan Campbell continue; 1610a0c5168SRafael J. Wysocki 162239007b8SThomas Gleixner raw_spin_lock_irqsave(&desc->lock, flags); 1638df2e02cSThomas Gleixner resume_irq(desc, irq); 164239007b8SThomas Gleixner raw_spin_unlock_irqrestore(&desc->lock, flags); 1650a0c5168SRafael J. Wysocki } 1660a0c5168SRafael J. Wysocki } 1679bab0b7fSIan Campbell 1689bab0b7fSIan Campbell /** 1699bab0b7fSIan Campbell * irq_pm_syscore_ops - enable interrupt lines early 1709bab0b7fSIan Campbell * 1719bab0b7fSIan Campbell * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 1729bab0b7fSIan Campbell */ 1739bab0b7fSIan Campbell static void irq_pm_syscore_resume(void) 1749bab0b7fSIan Campbell { 1759bab0b7fSIan Campbell resume_irqs(true); 1769bab0b7fSIan Campbell } 1779bab0b7fSIan Campbell 1789bab0b7fSIan Campbell static struct syscore_ops irq_pm_syscore_ops = { 1799bab0b7fSIan Campbell .resume = irq_pm_syscore_resume, 1809bab0b7fSIan Campbell }; 1819bab0b7fSIan Campbell 1829bab0b7fSIan Campbell static int __init irq_pm_init_ops(void) 1839bab0b7fSIan Campbell { 1849bab0b7fSIan Campbell register_syscore_ops(&irq_pm_syscore_ops); 1859bab0b7fSIan Campbell return 0; 1869bab0b7fSIan Campbell } 1879bab0b7fSIan Campbell 1889bab0b7fSIan Campbell device_initcall(irq_pm_init_ops); 1899bab0b7fSIan Campbell 1909bab0b7fSIan Campbell /** 1919bab0b7fSIan Campbell * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 1929bab0b7fSIan Campbell * 1939bab0b7fSIan Campbell * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 1949bab0b7fSIan Campbell * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 1959bab0b7fSIan Campbell * set as well as those with %IRQF_FORCE_RESUME. 1969bab0b7fSIan Campbell */ 1979bab0b7fSIan Campbell void resume_device_irqs(void) 1989bab0b7fSIan Campbell { 1999bab0b7fSIan Campbell resume_irqs(false); 2009bab0b7fSIan Campbell } 2010a0c5168SRafael J. Wysocki EXPORT_SYMBOL_GPL(resume_device_irqs); 202