Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0d85fd42 authored by Rafael J. Wysocki's avatar Rafael J. Wysocki
Browse files

Merge branch 'pm-wakeirq'

* pm-wakeirq:
  PM / wakeirq: Fix typo in prototype for dev_pm_set_dedicated_wake_irq
  PM / Wakeirq: Add automated device wake IRQ handling
parents ab232ba5 db874c7e
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
obj-$(CONFIG_PM)	+= sysfs.o generic_ops.o common.o qos.o runtime.o
obj-$(CONFIG_PM)	+= sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
obj-$(CONFIG_PM_SLEEP)	+= main.o wakeup.o
obj-$(CONFIG_PM_SLEEP)	+= main.o wakeup.o
obj-$(CONFIG_PM_TRACE_RTC)	+= trace.o
obj-$(CONFIG_PM_TRACE_RTC)	+= trace.o
obj-$(CONFIG_PM_OPP)	+= opp.o
obj-$(CONFIG_PM_OPP)	+= opp.o
+3 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@
#include <linux/pm.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/pm_runtime.h>
#include <linux/pm-trace.h>
#include <linux/pm-trace.h>
#include <linux/pm_wakeirq.h>
#include <linux/interrupt.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/sched.h>
#include <linux/async.h>
#include <linux/async.h>
@@ -587,6 +588,7 @@ void dpm_resume_noirq(pm_message_t state)
	async_synchronize_full();
	async_synchronize_full();
	dpm_show_time(starttime, state, "noirq");
	dpm_show_time(starttime, state, "noirq");
	resume_device_irqs();
	resume_device_irqs();
	device_wakeup_disarm_wake_irqs();
	cpuidle_resume();
	cpuidle_resume();
	trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
	trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, false);
}
}
@@ -1104,6 +1106,7 @@ int dpm_suspend_noirq(pm_message_t state)


	trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
	trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true);
	cpuidle_pause();
	cpuidle_pause();
	device_wakeup_arm_wake_irqs();
	suspend_device_irqs();
	suspend_device_irqs();
	mutex_lock(&dpm_list_mtx);
	mutex_lock(&dpm_list_mtx);
	pm_transition = state;
	pm_transition = state;
+48 −0
Original line number Original line Diff line number Diff line
@@ -20,6 +20,46 @@ static inline void pm_runtime_early_init(struct device *dev)
extern void pm_runtime_init(struct device *dev);
extern void pm_runtime_init(struct device *dev);
extern void pm_runtime_remove(struct device *dev);
extern void pm_runtime_remove(struct device *dev);


struct wake_irq {
	struct device *dev;
	int irq;
	bool dedicated_irq:1;
};

extern void dev_pm_arm_wake_irq(struct wake_irq *wirq);
extern void dev_pm_disarm_wake_irq(struct wake_irq *wirq);

#ifdef CONFIG_PM_SLEEP

extern int device_wakeup_attach_irq(struct device *dev,
				    struct wake_irq *wakeirq);
extern void device_wakeup_detach_irq(struct device *dev);
extern void device_wakeup_arm_wake_irqs(void);
extern void device_wakeup_disarm_wake_irqs(void);

#else

static inline int
device_wakeup_attach_irq(struct device *dev,
			 struct wake_irq *wakeirq)
{
	return 0;
}

static inline void device_wakeup_detach_irq(struct device *dev)
{
}

static inline void device_wakeup_arm_wake_irqs(void)
{
}

static inline void device_wakeup_disarm_wake_irqs(void)
{
}

#endif /* CONFIG_PM_SLEEP */

/*
/*
 * sysfs.c
 * sysfs.c
 */
 */
@@ -52,6 +92,14 @@ static inline void wakeup_sysfs_remove(struct device *dev) {}
static inline int pm_qos_sysfs_add(struct device *dev) { return 0; }
static inline int pm_qos_sysfs_add(struct device *dev) { return 0; }
static inline void pm_qos_sysfs_remove(struct device *dev) {}
static inline void pm_qos_sysfs_remove(struct device *dev) {}


static inline void dev_pm_arm_wake_irq(struct wake_irq *wirq)
{
}

static inline void dev_pm_disarm_wake_irq(struct wake_irq *wirq)
{
}

#endif
#endif


#ifdef CONFIG_PM_SLEEP
#ifdef CONFIG_PM_SLEEP
+5 −0
Original line number Original line Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/sched.h>
#include <linux/sched.h>
#include <linux/export.h>
#include <linux/export.h>
#include <linux/pm_runtime.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeirq.h>
#include <trace/events/rpm.h>
#include <trace/events/rpm.h>
#include "power.h"
#include "power.h"


@@ -514,6 +515,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)


	callback = RPM_GET_CALLBACK(dev, runtime_suspend);
	callback = RPM_GET_CALLBACK(dev, runtime_suspend);


	dev_pm_enable_wake_irq(dev);
	retval = rpm_callback(callback, dev);
	retval = rpm_callback(callback, dev);
	if (retval)
	if (retval)
		goto fail;
		goto fail;
@@ -552,6 +554,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
	return retval;
	return retval;


 fail:
 fail:
	dev_pm_disable_wake_irq(dev);
	__update_runtime_status(dev, RPM_ACTIVE);
	__update_runtime_status(dev, RPM_ACTIVE);
	dev->power.deferred_resume = false;
	dev->power.deferred_resume = false;
	wake_up_all(&dev->power.wait_queue);
	wake_up_all(&dev->power.wait_queue);
@@ -734,10 +737,12 @@ static int rpm_resume(struct device *dev, int rpmflags)


	callback = RPM_GET_CALLBACK(dev, runtime_resume);
	callback = RPM_GET_CALLBACK(dev, runtime_resume);


	dev_pm_disable_wake_irq(dev);
	retval = rpm_callback(callback, dev);
	retval = rpm_callback(callback, dev);
	if (retval) {
	if (retval) {
		__update_runtime_status(dev, RPM_SUSPENDED);
		__update_runtime_status(dev, RPM_SUSPENDED);
		pm_runtime_cancel_pending(dev);
		pm_runtime_cancel_pending(dev);
		dev_pm_enable_wake_irq(dev);
	} else {
	} else {
 no_callback:
 no_callback:
		__update_runtime_status(dev, RPM_ACTIVE);
		__update_runtime_status(dev, RPM_ACTIVE);
+273 −0
Original line number Original line Diff line number Diff line
/*
 * wakeirq.c - Device wakeirq helper functions
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed "as is" WITHOUT ANY WARRANTY of any
 * kind, whether express or implied; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeirq.h>

#include "power.h"

/**
 * dev_pm_attach_wake_irq - Attach device interrupt as a wake IRQ
 * @dev: Device entry
 * @irq: Device wake-up capable interrupt
 * @wirq: Wake irq specific data
 *
 * Internal function to attach either a device IO interrupt or a
 * dedicated wake-up interrupt as a wake IRQ.
 */
static int dev_pm_attach_wake_irq(struct device *dev, int irq,
				  struct wake_irq *wirq)
{
	unsigned long flags;
	int err;

	if (!dev || !wirq)
		return -EINVAL;

	spin_lock_irqsave(&dev->power.lock, flags);
	if (dev_WARN_ONCE(dev, dev->power.wakeirq,
			  "wake irq already initialized\n")) {
		spin_unlock_irqrestore(&dev->power.lock, flags);
		return -EEXIST;
	}

	dev->power.wakeirq = wirq;
	spin_unlock_irqrestore(&dev->power.lock, flags);

	err = device_wakeup_attach_irq(dev, wirq);
	if (err)
		return err;

	return 0;
}

/**
 * dev_pm_set_wake_irq - Attach device IO interrupt as wake IRQ
 * @dev: Device entry
 * @irq: Device IO interrupt
 *
 * Attach a device IO interrupt as a wake IRQ. The wake IRQ gets
 * automatically configured for wake-up from suspend  based
 * on the device specific sysfs wakeup entry. Typically called
 * during driver probe after calling device_init_wakeup().
 */
int dev_pm_set_wake_irq(struct device *dev, int irq)
{
	struct wake_irq *wirq;
	int err;

	wirq = kzalloc(sizeof(*wirq), GFP_KERNEL);
	if (!wirq)
		return -ENOMEM;

	wirq->dev = dev;
	wirq->irq = irq;

	err = dev_pm_attach_wake_irq(dev, irq, wirq);
	if (err)
		kfree(wirq);

	return err;
}
EXPORT_SYMBOL_GPL(dev_pm_set_wake_irq);

/**
 * dev_pm_clear_wake_irq - Detach a device IO interrupt wake IRQ
 * @dev: Device entry
 *
 * Detach a device wake IRQ and free resources.
 *
 * Note that it's OK for drivers to call this without calling
 * dev_pm_set_wake_irq() as all the driver instances may not have
 * a wake IRQ configured. This avoid adding wake IRQ specific
 * checks into the drivers.
 */
void dev_pm_clear_wake_irq(struct device *dev)
{
	struct wake_irq *wirq = dev->power.wakeirq;
	unsigned long flags;

	if (!wirq)
		return;

	spin_lock_irqsave(&dev->power.lock, flags);
	dev->power.wakeirq = NULL;
	spin_unlock_irqrestore(&dev->power.lock, flags);

	device_wakeup_detach_irq(dev);
	if (wirq->dedicated_irq)
		free_irq(wirq->irq, wirq);
	kfree(wirq);
}
EXPORT_SYMBOL_GPL(dev_pm_clear_wake_irq);

/**
 * handle_threaded_wake_irq - Handler for dedicated wake-up interrupts
 * @irq: Device specific dedicated wake-up interrupt
 * @_wirq: Wake IRQ data
 *
 * Some devices have a separate wake-up interrupt in addition to the
 * device IO interrupt. The wake-up interrupt signals that a device
 * should be woken up from it's idle state. This handler uses device
 * specific pm_runtime functions to wake the device, and then it's
 * up to the device to do whatever it needs to. Note that as the
 * device may need to restore context and start up regulators, we
 * use a threaded IRQ.
 *
 * Also note that we are not resending the lost device interrupts.
 * We assume that the wake-up interrupt just needs to wake-up the
 * device, and then device's pm_runtime_resume() can deal with the
 * situation.
 */
static irqreturn_t handle_threaded_wake_irq(int irq, void *_wirq)
{
	struct wake_irq *wirq = _wirq;
	int res;

	/* We don't want RPM_ASYNC or RPM_NOWAIT here */
	res = pm_runtime_resume(wirq->dev);
	if (res < 0)
		dev_warn(wirq->dev,
			 "wake IRQ with no resume: %i\n", res);

	return IRQ_HANDLED;
}

/**
 * dev_pm_set_dedicated_wake_irq - Request a dedicated wake-up interrupt
 * @dev: Device entry
 * @irq: Device wake-up interrupt
 *
 * Unless your hardware has separate wake-up interrupts in addition
 * to the device IO interrupts, you don't need this.
 *
 * Sets up a threaded interrupt handler for a device that has
 * a dedicated wake-up interrupt in addition to the device IO
 * interrupt.
 *
 * The interrupt starts disabled, and needs to be managed for
 * the device by the bus code or the device driver using
 * dev_pm_enable_wake_irq() and dev_pm_disable_wake_irq()
 * functions.
 */
int dev_pm_set_dedicated_wake_irq(struct device *dev, int irq)
{
	struct wake_irq *wirq;
	int err;

	wirq = kzalloc(sizeof(*wirq), GFP_KERNEL);
	if (!wirq)
		return -ENOMEM;

	wirq->dev = dev;
	wirq->irq = irq;
	wirq->dedicated_irq = true;
	irq_set_status_flags(irq, IRQ_NOAUTOEN);

	/*
	 * Consumer device may need to power up and restore state
	 * so we use a threaded irq.
	 */
	err = request_threaded_irq(irq, NULL, handle_threaded_wake_irq,
				   IRQF_ONESHOT, dev_name(dev), wirq);
	if (err)
		goto err_free;

	err = dev_pm_attach_wake_irq(dev, irq, wirq);
	if (err)
		goto err_free_irq;

	return err;

err_free_irq:
	free_irq(irq, wirq);
err_free:
	kfree(wirq);

	return err;
}
EXPORT_SYMBOL_GPL(dev_pm_set_dedicated_wake_irq);

/**
 * dev_pm_enable_wake_irq - Enable device wake-up interrupt
 * @dev: Device
 *
 * Called from the bus code or the device driver for
 * runtime_suspend() to enable the wake-up interrupt while
 * the device is running.
 *
 * Note that for runtime_suspend()) the wake-up interrupts
 * should be unconditionally enabled unlike for suspend()
 * that is conditional.
 */
void dev_pm_enable_wake_irq(struct device *dev)
{
	struct wake_irq *wirq = dev->power.wakeirq;

	if (wirq && wirq->dedicated_irq)
		enable_irq(wirq->irq);
}
EXPORT_SYMBOL_GPL(dev_pm_enable_wake_irq);

/**
 * dev_pm_disable_wake_irq - Disable device wake-up interrupt
 * @dev: Device
 *
 * Called from the bus code or the device driver for
 * runtime_resume() to disable the wake-up interrupt while
 * the device is running.
 */
void dev_pm_disable_wake_irq(struct device *dev)
{
	struct wake_irq *wirq = dev->power.wakeirq;

	if (wirq && wirq->dedicated_irq)
		disable_irq_nosync(wirq->irq);
}
EXPORT_SYMBOL_GPL(dev_pm_disable_wake_irq);

/**
 * dev_pm_arm_wake_irq - Arm device wake-up
 * @wirq: Device wake-up interrupt
 *
 * Sets up the wake-up event conditionally based on the
 * device_may_wake().
 */
void dev_pm_arm_wake_irq(struct wake_irq *wirq)
{
	if (!wirq)
		return;

	if (device_may_wakeup(wirq->dev))
		enable_irq_wake(wirq->irq);
}

/**
 * dev_pm_disarm_wake_irq - Disarm device wake-up
 * @wirq: Device wake-up interrupt
 *
 * Clears up the wake-up event conditionally based on the
 * device_may_wake().
 */
void dev_pm_disarm_wake_irq(struct wake_irq *wirq)
{
	if (!wirq)
		return;

	if (device_may_wakeup(wirq->dev))
		disable_irq_wake(wirq->irq);
}
Loading