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