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++; 4617f48034SRafael J. Wysocki else if (action->flags & IRQF_COND_SUSPEND) 4717f48034SRafael J. Wysocki desc->cond_suspend_depth++; 48cab303beSThomas Gleixner 49cab303beSThomas Gleixner WARN_ON_ONCE(desc->no_suspend_depth && 5017f48034SRafael J. Wysocki (desc->no_suspend_depth + 5117f48034SRafael J. Wysocki desc->cond_suspend_depth) != desc->nr_actions); 52cab303beSThomas Gleixner } 53cab303beSThomas Gleixner 54cab303beSThomas Gleixner /* 55cab303beSThomas Gleixner * Called from __free_irq() with desc->lock held after @action has 56cab303beSThomas Gleixner * been removed from the action chain. 57cab303beSThomas Gleixner */ 58cab303beSThomas Gleixner void irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) 59cab303beSThomas Gleixner { 60cab303beSThomas Gleixner desc->nr_actions--; 61cab303beSThomas Gleixner 62cab303beSThomas Gleixner if (action->flags & IRQF_FORCE_RESUME) 63cab303beSThomas Gleixner desc->force_resume_depth--; 64cab303beSThomas Gleixner 65cab303beSThomas Gleixner if (action->flags & IRQF_NO_SUSPEND) 66cab303beSThomas Gleixner desc->no_suspend_depth--; 6717f48034SRafael J. Wysocki else if (action->flags & IRQF_COND_SUSPEND) 6817f48034SRafael J. Wysocki desc->cond_suspend_depth--; 69cab303beSThomas Gleixner } 70cab303beSThomas Gleixner 71c4df606cSThomas Gleixner static bool suspend_device_irq(struct irq_desc *desc, int irq) 728df2e02cSThomas Gleixner { 735417de22SThomas Gleixner if (!desc->action || 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; 888df2e02cSThomas Gleixner __disable_irq(desc, irq); 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); 129c4df606cSThomas Gleixner sync = suspend_device_irq(desc, irq); 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 1388df2e02cSThomas Gleixner static void resume_irq(struct irq_desc *desc, int irq) 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++; 1518df2e02cSThomas Gleixner resume: 1528df2e02cSThomas Gleixner desc->istate &= ~IRQS_SUSPENDED; 1538df2e02cSThomas Gleixner __enable_irq(desc, irq); 1548df2e02cSThomas Gleixner } 1558df2e02cSThomas Gleixner 1569bab0b7fSIan Campbell static void resume_irqs(bool want_early) 1570a0c5168SRafael J. Wysocki { 1580a0c5168SRafael J. Wysocki struct irq_desc *desc; 1590a0c5168SRafael J. Wysocki int irq; 1600a0c5168SRafael J. Wysocki 1610a0c5168SRafael J. Wysocki for_each_irq_desc(irq, desc) { 1620a0c5168SRafael J. Wysocki unsigned long flags; 1639bab0b7fSIan Campbell bool is_early = desc->action && 1649bab0b7fSIan Campbell desc->action->flags & IRQF_EARLY_RESUME; 1659bab0b7fSIan Campbell 166ac01810cSLaxman Dewangan if (!is_early && want_early) 1679bab0b7fSIan Campbell continue; 1683c646f2cSNeilBrown if (irq_settings_is_nested_thread(desc)) 1693c646f2cSNeilBrown continue; 1700a0c5168SRafael J. Wysocki 171239007b8SThomas Gleixner raw_spin_lock_irqsave(&desc->lock, flags); 1728df2e02cSThomas Gleixner resume_irq(desc, irq); 173239007b8SThomas Gleixner raw_spin_unlock_irqrestore(&desc->lock, flags); 1740a0c5168SRafael J. Wysocki } 1750a0c5168SRafael J. Wysocki } 1769bab0b7fSIan Campbell 1779bab0b7fSIan Campbell /** 1789bab0b7fSIan Campbell * irq_pm_syscore_ops - enable interrupt lines early 1799bab0b7fSIan Campbell * 1809bab0b7fSIan Campbell * Enable all interrupt lines with %IRQF_EARLY_RESUME set. 1819bab0b7fSIan Campbell */ 1829bab0b7fSIan Campbell static void irq_pm_syscore_resume(void) 1839bab0b7fSIan Campbell { 1849bab0b7fSIan Campbell resume_irqs(true); 1859bab0b7fSIan Campbell } 1869bab0b7fSIan Campbell 1879bab0b7fSIan Campbell static struct syscore_ops irq_pm_syscore_ops = { 1889bab0b7fSIan Campbell .resume = irq_pm_syscore_resume, 1899bab0b7fSIan Campbell }; 1909bab0b7fSIan Campbell 1919bab0b7fSIan Campbell static int __init irq_pm_init_ops(void) 1929bab0b7fSIan Campbell { 1939bab0b7fSIan Campbell register_syscore_ops(&irq_pm_syscore_ops); 1949bab0b7fSIan Campbell return 0; 1959bab0b7fSIan Campbell } 1969bab0b7fSIan Campbell 1979bab0b7fSIan Campbell device_initcall(irq_pm_init_ops); 1989bab0b7fSIan Campbell 1999bab0b7fSIan Campbell /** 2009bab0b7fSIan Campbell * resume_device_irqs - enable interrupt lines disabled by suspend_device_irqs() 2019bab0b7fSIan Campbell * 2029bab0b7fSIan Campbell * Enable all non-%IRQF_EARLY_RESUME interrupt lines previously 2039bab0b7fSIan Campbell * disabled by suspend_device_irqs() that have the IRQS_SUSPENDED flag 2049bab0b7fSIan Campbell * set as well as those with %IRQF_FORCE_RESUME. 2059bab0b7fSIan Campbell */ 2069bab0b7fSIan Campbell void resume_device_irqs(void) 2079bab0b7fSIan Campbell { 2089bab0b7fSIan Campbell resume_irqs(false); 2099bab0b7fSIan Campbell } 2100a0c5168SRafael J. Wysocki EXPORT_SYMBOL_GPL(resume_device_irqs); 211