[2/5] PM / Wakeirq: Add automated device wake IRQ handling
diff mbox

Message ID 20150519181842.GR10274@atomide.com
State New
Headers show

Commit Message

Tony Lindgren May 19, 2015, 6:18 p.m. UTC
* Tony Lindgren <tony@atomide.com> [150519 08:21]:
> * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 07:02]:
> > On Tuesday, May 19, 2015 04:04:43 PM Rafael J. Wysocki wrote:
> > > On Monday, May 18, 2015 04:44:01 PM Tony Lindgren wrote:
> > > > 
> > > > We can replace it with just the following init and exit
> > > > time code:
> > > > 
> > > > 	...
> > > > 	device_init_wakeup(dev, true);
> > > > 	dev_pm_set_wake_irq(dev, irq);
> > > > 	...
> > > > 	dev_pm_clear_wake_irq(dev);
> > > > 	device_init_wakeup(dev, false);
> > > > 	...
> > > > 
> > > > And for hardware with dedicated wake-up interrupts:
> > > > 
> > > > 	...
> > > > 	device_init_wakeup(dev, true);
> > > > 	dev_pm_set_dedicated_wake_irq(dev, irq);
> > > > 	...
> > > > 	dev_pm_clear_wake_irq(dev);
> > > > 	device_init_wakeup(dev, false);
> > > > 	...
> > > > 
> > > > For now, let's only enable it for select PM_WAKEIRQ.
> > > 
> > > Why?  What would be wrong with doing that unconditionally?
> 
> No reason to make it conditional any longer. it's there from
> the earlier version that only handled the dedicated wake IRQS.
>  
> > I mean, what about making it depend on CONFIG_PM directly?
> 
> OK let's do that.

Here's the patch updated and now using just CONFIG_PM.

Regards,

Tony

8< ---------------------------
From: Tony Lindgren <tony@atomide.com>
Date: Mon, 18 May 2015 15:40:29 -0700
Subject: [PATCH] PM / Wakeirq: Add automated device wake IRQ handling

Turns out we can automate the handling for the device_may_wakeup()
quite a bit by using the kernel wakeup source list as suggested
by Rafael J. Wysocki <rjw@rjwysocki.net>.

And as some hardware has separate dedicated wake-up interrupt
in addition to the IO interrupt, we can automate the handling by
adding a generic threaded interrupt handler that just calls the
device PM runtime to wake up the device.

This allows dropping code from device drivers as we currently
are doing it in multiple ways, and often wrong.

For most drivers, we should be able to drop the following
boilerplate code from runtime_suspend and runtime_resume
functions:

	...
	device_init_wakeup(dev, true);
	...
	if (device_may_wakeup(dev))
		enable_irq_wake(irq);
	...
	if (device_may_wakeup(dev))
		disable_irq_wake(irq);
	...
	device_init_wakeup(dev, false);
	...

We can replace it with just the following init and exit
time code:

	...
	device_init_wakeup(dev, true);
	dev_pm_set_wake_irq(dev, irq);
	...
	dev_pm_clear_wake_irq(dev);
	device_init_wakeup(dev, false);
	...

And for hardware with dedicated wake-up interrupts:

	...
	device_init_wakeup(dev, true);
	dev_pm_set_dedicated_wake_irq(dev, irq);
	...
	dev_pm_clear_wake_irq(dev);
	device_init_wakeup(dev, false);
	...

Signed-off-by: Tony Lindgren <tony@atomide.com>

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Thomas Gleixner May 19, 2015, 10:41 p.m. UTC | #1
On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> This one looks really good. :-)
> 
> If it doesn't depend on anything, I can apply it right away, so please let me
> know.

Can you pretty please trim your replies? It's a PITA to scroll through
several pages of quoted patch just to find your signature as the extra
content.

Thanks,

	tglx
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki May 19, 2015, 11:01 p.m. UTC | #2
On Tuesday, May 19, 2015 11:18:42 AM Tony Lindgren wrote:
> * Tony Lindgren <tony@atomide.com> [150519 08:21]:
> > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 07:02]:
> > > On Tuesday, May 19, 2015 04:04:43 PM Rafael J. Wysocki wrote:
> > > > On Monday, May 18, 2015 04:44:01 PM Tony Lindgren wrote:
> > > > > 
> > > > > We can replace it with just the following init and exit
> > > > > time code:
> > > > > 
> > > > > 	...
> > > > > 	device_init_wakeup(dev, true);
> > > > > 	dev_pm_set_wake_irq(dev, irq);
> > > > > 	...
> > > > > 	dev_pm_clear_wake_irq(dev);
> > > > > 	device_init_wakeup(dev, false);
> > > > > 	...
> > > > > 
> > > > > And for hardware with dedicated wake-up interrupts:
> > > > > 
> > > > > 	...
> > > > > 	device_init_wakeup(dev, true);
> > > > > 	dev_pm_set_dedicated_wake_irq(dev, irq);
> > > > > 	...
> > > > > 	dev_pm_clear_wake_irq(dev);
> > > > > 	device_init_wakeup(dev, false);
> > > > > 	...
> > > > > 
> > > > > For now, let's only enable it for select PM_WAKEIRQ.
> > > > 
> > > > Why?  What would be wrong with doing that unconditionally?
> > 
> > No reason to make it conditional any longer. it's there from
> > the earlier version that only handled the dedicated wake IRQS.
> >  
> > > I mean, what about making it depend on CONFIG_PM directly?
> > 
> > OK let's do that.
> 
> Here's the patch updated and now using just CONFIG_PM.

This one looks really good. :-)

If it doesn't depend on anything, I can apply it right away, so please let me
know.


> 8< ---------------------------
> From: Tony Lindgren <tony@atomide.com>
> Date: Mon, 18 May 2015 15:40:29 -0700
> Subject: [PATCH] PM / Wakeirq: Add automated device wake IRQ handling
> 
> Turns out we can automate the handling for the device_may_wakeup()
> quite a bit by using the kernel wakeup source list as suggested
> by Rafael J. Wysocki <rjw@rjwysocki.net>.
> 
> And as some hardware has separate dedicated wake-up interrupt
> in addition to the IO interrupt, we can automate the handling by
> adding a generic threaded interrupt handler that just calls the
> device PM runtime to wake up the device.
> 
> This allows dropping code from device drivers as we currently
> are doing it in multiple ways, and often wrong.
> 
> For most drivers, we should be able to drop the following
> boilerplate code from runtime_suspend and runtime_resume
> functions:
> 
> 	...
> 	device_init_wakeup(dev, true);
> 	...
> 	if (device_may_wakeup(dev))
> 		enable_irq_wake(irq);
> 	...
> 	if (device_may_wakeup(dev))
> 		disable_irq_wake(irq);
> 	...
> 	device_init_wakeup(dev, false);
> 	...
> 
> We can replace it with just the following init and exit
> time code:
> 
> 	...
> 	device_init_wakeup(dev, true);
> 	dev_pm_set_wake_irq(dev, irq);
> 	...
> 	dev_pm_clear_wake_irq(dev);
> 	device_init_wakeup(dev, false);
> 	...
> 
> And for hardware with dedicated wake-up interrupts:
> 
> 	...
> 	device_init_wakeup(dev, true);
> 	dev_pm_set_dedicated_wake_irq(dev, irq);
> 	...
> 	dev_pm_clear_wake_irq(dev);
> 	device_init_wakeup(dev, false);
> 	...
> 
> Signed-off-by: Tony Lindgren <tony@atomide.com>
> 
> diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> index 1cb8544..f94a6cc 100644
> --- a/drivers/base/power/Makefile
> +++ b/drivers/base/power/Makefile
> @@ -1,4 +1,4 @@
> -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_TRACE_RTC)	+= trace.o
>  obj-$(CONFIG_PM_OPP)	+= opp.o
> diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
> index 3d874ec..6f2515c 100644
> --- a/drivers/base/power/main.c
> +++ b/drivers/base/power/main.c
> @@ -24,6 +24,7 @@
>  #include <linux/pm.h>
>  #include <linux/pm_runtime.h>
>  #include <linux/pm-trace.h>
> +#include <linux/pm_wakeirq.h>
>  #include <linux/interrupt.h>
>  #include <linux/sched.h>
>  #include <linux/async.h>
> @@ -587,6 +588,7 @@ void dpm_resume_noirq(pm_message_t state)
>  	async_synchronize_full();
>  	dpm_show_time(starttime, state, "noirq");
>  	resume_device_irqs();
> +	device_wakeup_disarm_wake_irqs();
>  	cpuidle_resume();
>  	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);
>  	cpuidle_pause();
> +	device_wakeup_arm_wake_irqs();
>  	suspend_device_irqs();
>  	mutex_lock(&dpm_list_mtx);
>  	pm_transition = state;
> diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
> index b6b8a27..f1a5d95 100644
> --- a/drivers/base/power/power.h
> +++ b/drivers/base/power/power.h
> @@ -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_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
>   */
> @@ -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 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
>  
>  #ifdef CONFIG_PM_SLEEP
> diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
> index 4ffe4a2..e1a10a0 100644
> --- a/drivers/base/power/runtime.c
> +++ b/drivers/base/power/runtime.c
> @@ -10,6 +10,7 @@
>  #include <linux/sched.h>
>  #include <linux/export.h>
>  #include <linux/pm_runtime.h>
> +#include <linux/pm_wakeirq.h>
>  #include <trace/events/rpm.h>
>  #include "power.h"
>  
> @@ -514,6 +515,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
>  
>  	callback = RPM_GET_CALLBACK(dev, runtime_suspend);
>  
> +	dev_pm_enable_wake_irq(dev);
>  	retval = rpm_callback(callback, dev);
>  	if (retval)
>  		goto fail;
> @@ -552,6 +554,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
>  	return retval;
>  
>   fail:
> +	dev_pm_disable_wake_irq(dev);
>  	__update_runtime_status(dev, RPM_ACTIVE);
>  	dev->power.deferred_resume = false;
>  	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);
>  
> +	dev_pm_disable_wake_irq(dev);
>  	retval = rpm_callback(callback, dev);
>  	if (retval) {
>  		__update_runtime_status(dev, RPM_SUSPENDED);
>  		pm_runtime_cancel_pending(dev);
> +		dev_pm_enable_wake_irq(dev);
>  	} else {
>   no_callback:
>  		__update_runtime_status(dev, RPM_ACTIVE);
> diff --git a/drivers/base/power/wakeirq.c b/drivers/base/power/wakeirq.c
> new file mode 100644
> index 0000000..7470004
> --- /dev/null
> +++ b/drivers/base/power/wakeirq.c
> @@ -0,0 +1,273 @@
> +/*
> + * 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);
> +}
> diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
> index 7726200..7332ebc 100644
> --- a/drivers/base/power/wakeup.c
> +++ b/drivers/base/power/wakeup.c
> @@ -14,6 +14,7 @@
>  #include <linux/suspend.h>
>  #include <linux/seq_file.h>
>  #include <linux/debugfs.h>
> +#include <linux/pm_wakeirq.h>
>  #include <trace/events/power.h>
>  
>  #include "power.h"
> @@ -239,6 +240,97 @@ int device_wakeup_enable(struct device *dev)
>  EXPORT_SYMBOL_GPL(device_wakeup_enable);
>  
>  /**
> + * device_wakeup_attach_irq - Attach a wakeirq to a wakeup source
> + * @dev: Device to handle
> + * @wakeirq: Device specific wakeirq entry
> + *
> + * Attach a device wakeirq to the wakeup source so the device
> + * wake IRQ can be configured automatically for suspend and
> + * resume.
> + */
> +int device_wakeup_attach_irq(struct device *dev,
> +			     struct wake_irq *wakeirq)
> +{
> +	struct wakeup_source *ws;
> +	int ret = 0;
> +
> +	spin_lock_irq(&dev->power.lock);
> +	ws = dev->power.wakeup;
> +	if (!ws) {
> +		dev_err(dev, "forgot to call call device_init_wakeup?\n");
> +		ret = -EINVAL;
> +		goto unlock;
> +	}
> +
> +	if (ws->wakeirq) {
> +		ret = -EEXIST;
> +		goto unlock;
> +	}
> +
> +	ws->wakeirq = wakeirq;
> +
> +unlock:
> +	spin_unlock_irq(&dev->power.lock);
> +
> +	return ret;
> +}
> +
> +/**
> + * device_wakeup_detach_irq - Detach a wakeirq from a wakeup source
> + * @dev: Device to handle
> + *
> + * Removes a device wakeirq from the wakeup source.
> + */
> +void device_wakeup_detach_irq(struct device *dev)
> +{
> +	struct wakeup_source *ws;
> +
> +	spin_lock_irq(&dev->power.lock);
> +	ws = dev->power.wakeup;
> +	if (!ws)
> +		goto unlock;
> +
> +	ws->wakeirq = NULL;
> +
> +unlock:
> +	spin_unlock_irq(&dev->power.lock);
> +}
> +
> +/**
> + * device_wakeup_arm_wake_irqs(void)
> + *
> + * Itereates over the list of device wakeirqs to arm them.
> + */
> +void device_wakeup_arm_wake_irqs(void)
> +{
> +	struct wakeup_source *ws;
> +
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
> +		if (ws->wakeirq)
> +			dev_pm_arm_wake_irq(ws->wakeirq);
> +	}
> +	rcu_read_unlock();
> +}
> +
> +/**
> + * device_wakeup_disarm_wake_irqs(void)
> + *
> + * Itereates over the list of device wakeirqs to disarm them.
> + */
> +void device_wakeup_disarm_wake_irqs(void)
> +{
> +	struct wakeup_source *ws;
> +
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
> +		if (ws->wakeirq)
> +			dev_pm_disarm_wake_irq(ws->wakeirq);
> +	}
> +	rcu_read_unlock();
> +}
> +
> +/**
>   * device_wakeup_detach - Detach a device's wakeup source object from it.
>   * @dev: Device to detach the wakeup source object from.
>   *
> diff --git a/include/linux/pm.h b/include/linux/pm.h
> index 2d29c64..1c4ed0c 100644
> --- a/include/linux/pm.h
> +++ b/include/linux/pm.h
> @@ -529,6 +529,7 @@ enum rpm_request {
>  };
>  
>  struct wakeup_source;
> +struct wake_irq;
>  struct pm_domain_data;
>  
>  struct pm_subsys_data {
> @@ -568,6 +569,7 @@ struct dev_pm_info {
>  	unsigned long		timer_expires;
>  	struct work_struct	work;
>  	wait_queue_head_t	wait_queue;
> +	struct wake_irq		*wakeirq;
>  	atomic_t		usage_count;
>  	atomic_t		child_count;
>  	unsigned int		disable_depth:3;
> diff --git a/include/linux/pm_wakeirq.h b/include/linux/pm_wakeirq.h
> new file mode 100644
> index 0000000..4046fa1
> --- /dev/null
> +++ b/include/linux/pm_wakeirq.h
> @@ -0,0 +1,52 @@
> +/*
> + * pm_wakeirq.h - 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.
> + */
> +
> +#ifndef _LINUX_PM_WAKEIRQ_H
> +#define _LINUX_PM_WAKEIRQ_H
> +
> +#ifdef CONFIG_PM
> +
> +extern int dev_pm_set_wake_irq(struct device *dev, int irq);
> +extern int dev_pm_set_dedicated_wake_irq(struct device *dev,
> +					 int irq);
> +extern void dev_pm_clear_wake_irq(struct device *dev);
> +extern void dev_pm_enable_wake_irq(struct device *dev);
> +extern void dev_pm_disable_wake_irq(struct device *dev);
> +
> +#else	/* !CONFIG_PM */
> +
> +static inline int dev_pm_set_wake_irq(struct device *dev, int irq)
> +{
> +	return 0;
> +}
> +
> +static inline int dev_pm_set_dedicated__wake_irq(struct device *dev,
> +						 int irq)
> +{
> +	return 0;
> +}
> +
> +static inline void dev_pm_clear_wake_irq(struct device *dev)
> +{
> +}
> +
> +static inline void dev_pm_enable_wake_irq(struct device *dev)
> +{
> +}
> +
> +static inline void dev_pm_disable_wake_irq(struct device *dev)
> +{
> +}
> +
> +#endif	/* CONFIG_PM */
> +#endif	/* _LINUX_PM_WAKEIRQ_H */
> diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h
> index a0f7080..a344793 100644
> --- a/include/linux/pm_wakeup.h
> +++ b/include/linux/pm_wakeup.h
> @@ -28,9 +28,17 @@
>  
>  #include <linux/types.h>
>  
> +struct wake_irq;
> +
>  /**
>   * struct wakeup_source - Representation of wakeup sources
>   *
> + * @name: Name of the wakeup source
> + * @entry: Wakeup source list entry
> + * @lock: Wakeup source lock
> + * @wakeirq: Optional device specific wakeirq
> + * @timer: Wakeup timer list
> + * @timer_expires: Wakeup timer expiration
>   * @total_time: Total time this wakeup source has been active.
>   * @max_time: Maximum time this wakeup source has been continuously active.
>   * @last_time: Monotonic clock when the wakeup source's was touched last time.
> @@ -47,6 +55,7 @@ struct wakeup_source {
>  	const char 		*name;
>  	struct list_head	entry;
>  	spinlock_t		lock;
> +	struct wake_irq		*wakeirq;
>  	struct timer_list	timer;
>  	unsigned long		timer_expires;
>  	ktime_t total_time;
Tony Lindgren May 19, 2015, 11:27 p.m. UTC | #3
* Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > This one looks really good. :-)
> > > 
> > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > know.

Sure works for me, it just has a dependency to patch #1 in this
series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").

> > Can you pretty please trim your replies? It's a PITA to scroll through
> > several pages of quoted patch just to find your signature as the extra
> > content.
> 
> Sorry about that.

Sounds like Thomas might be OK with the patch too then :)

Regards,

Tony 
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki May 19, 2015, 11:31 p.m. UTC | #4
On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > This one looks really good. :-)
> > 
> > If it doesn't depend on anything, I can apply it right away, so please let me
> > know.
> 
> Can you pretty please trim your replies? It's a PITA to scroll through
> several pages of quoted patch just to find your signature as the extra
> content.

Sorry about that.

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki May 20, 2015, 12:25 a.m. UTC | #5
On Tuesday, May 19, 2015 04:27:56 PM Tony Lindgren wrote:
> * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> > On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > > This one looks really good. :-)
> > > > 
> > > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > > know.
> 
> Sure works for me, it just has a dependency to patch #1 in this
> series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").

OK, I'll queue them both up for 4.2, then.

Please let me know if you need a special branch with them.

> > > Can you pretty please trim your replies? It's a PITA to scroll through
> > > several pages of quoted patch just to find your signature as the extra
> > > content.
> > 
> > Sorry about that.
> 
> Sounds like Thomas might be OK with the patch too then :)

Well, yeah.

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tony Lindgren May 20, 2015, 2:10 a.m. UTC | #6
* Rafael J. Wysocki <rjw@rjwysocki.net> [150519 17:01]:
> On Tuesday, May 19, 2015 04:27:56 PM Tony Lindgren wrote:
> > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> > > On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > > > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > > > This one looks really good. :-)
> > > > > 
> > > > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > > > know.
> > 
> > Sure works for me, it just has a dependency to patch #1 in this
> > series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").
> 
> OK, I'll queue them both up for 4.2, then.
> 
> Please let me know if you need a special branch with them.

A separate branch would be nice so I can merge that in too.

Thanks,

Tony 
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Tony Lindgren May 21, 2015, 12:35 a.m. UTC | #7
* Rafael J. Wysocki <rjw@rjwysocki.net> [150520 17:30]:
> On Tuesday, May 19, 2015 07:10:57 PM Tony Lindgren wrote:
> > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 17:01]:
> > > On Tuesday, May 19, 2015 04:27:56 PM Tony Lindgren wrote:
> > > > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> > > > > On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > > > > > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > > > > > This one looks really good. :-)
> > > > > > > 
> > > > > > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > > > > > know.
> > > > 
> > > > Sure works for me, it just has a dependency to patch #1 in this
> > > > series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").
> > > 
> > > OK, I'll queue them both up for 4.2, then.
> > > 
> > > Please let me know if you need a special branch with them.
> > 
> > A separate branch would be nice so I can merge that in too.
> 
> OK
> 
> I added a new pm-wakeirq branch to linux-pm.git with these two patches and
> one other commit related to runtime PM (all based on 4.1-rc4).  This branch
> will not be rebased going forward.

Great thanks!

Tony
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Rafael J. Wysocki May 21, 2015, 12:54 a.m. UTC | #8
On Tuesday, May 19, 2015 07:10:57 PM Tony Lindgren wrote:
> * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 17:01]:
> > On Tuesday, May 19, 2015 04:27:56 PM Tony Lindgren wrote:
> > > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> > > > On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > > > > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > > > > This one looks really good. :-)
> > > > > > 
> > > > > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > > > > know.
> > > 
> > > Sure works for me, it just has a dependency to patch #1 in this
> > > series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").
> > 
> > OK, I'll queue them both up for 4.2, then.
> > 
> > Please let me know if you need a special branch with them.
> 
> A separate branch would be nice so I can merge that in too.

OK

I added a new pm-wakeirq branch to linux-pm.git with these two patches and
one other commit related to runtime PM (all based on 4.1-rc4).  This branch
will not be rebased going forward.
Felipe Balbi May 21, 2015, 1:40 a.m. UTC | #9
On Thu, May 21, 2015 at 02:54:14AM +0200, Rafael J. Wysocki wrote:
> On Tuesday, May 19, 2015 07:10:57 PM Tony Lindgren wrote:
> > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 17:01]:
> > > On Tuesday, May 19, 2015 04:27:56 PM Tony Lindgren wrote:
> > > > * Rafael J. Wysocki <rjw@rjwysocki.net> [150519 16:07]:
> > > > > On Wednesday, May 20, 2015 12:41:06 AM Thomas Gleixner wrote:
> > > > > > On Wed, 20 May 2015, Rafael J. Wysocki wrote:
> > > > > > > This one looks really good. :-)
> > > > > > > 
> > > > > > > If it doesn't depend on anything, I can apply it right away, so please let me
> > > > > > > know.
> > > > 
> > > > Sure works for me, it just has a dependency to patch #1 in this
> > > > series ("[PATCH 1/5] PM / Runtime: Update last_busy in rpm_resume").
> > > 
> > > OK, I'll queue them both up for 4.2, then.
> > > 
> > > Please let me know if you need a special branch with them.
> > 
> > A separate branch would be nice so I can merge that in too.
> 
> OK
> 
> I added a new pm-wakeirq branch to linux-pm.git with these two patches and
> one other commit related to runtime PM (all based on 4.1-rc4).  This branch
> will not be rebased going forward.

Hopefully it's not too late to get my

Reviewed-by: Felipe Balbi <balbi@ti.com>

in the patch. If it is, no problem :-)

cheers

Patch
diff mbox

diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
index 1cb8544..f94a6cc 100644
--- a/drivers/base/power/Makefile
+++ b/drivers/base/power/Makefile
@@ -1,4 +1,4 @@ 
-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_TRACE_RTC)	+= trace.o
 obj-$(CONFIG_PM_OPP)	+= opp.o
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index 3d874ec..6f2515c 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -24,6 +24,7 @@ 
 #include <linux/pm.h>
 #include <linux/pm_runtime.h>
 #include <linux/pm-trace.h>
+#include <linux/pm_wakeirq.h>
 #include <linux/interrupt.h>
 #include <linux/sched.h>
 #include <linux/async.h>
@@ -587,6 +588,7 @@  void dpm_resume_noirq(pm_message_t state)
 	async_synchronize_full();
 	dpm_show_time(starttime, state, "noirq");
 	resume_device_irqs();
+	device_wakeup_disarm_wake_irqs();
 	cpuidle_resume();
 	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);
 	cpuidle_pause();
+	device_wakeup_arm_wake_irqs();
 	suspend_device_irqs();
 	mutex_lock(&dpm_list_mtx);
 	pm_transition = state;
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
index b6b8a27..f1a5d95 100644
--- a/drivers/base/power/power.h
+++ b/drivers/base/power/power.h
@@ -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_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
  */
@@ -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 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
 
 #ifdef CONFIG_PM_SLEEP
diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c
index 4ffe4a2..e1a10a0 100644
--- a/drivers/base/power/runtime.c
+++ b/drivers/base/power/runtime.c
@@ -10,6 +10,7 @@ 
 #include <linux/sched.h>
 #include <linux/export.h>
 #include <linux/pm_runtime.h>
+#include <linux/pm_wakeirq.h>
 #include <trace/events/rpm.h>
 #include "power.h"
 
@@ -514,6 +515,7 @@  static int rpm_suspend(struct device *dev, int rpmflags)
 
 	callback = RPM_GET_CALLBACK(dev, runtime_suspend);
 
+	dev_pm_enable_wake_irq(dev);
 	retval = rpm_callback(callback, dev);
 	if (retval)
 		goto fail;
@@ -552,6 +554,7 @@  static int rpm_suspend(struct device *dev, int rpmflags)
 	return retval;
 
  fail:
+	dev_pm_disable_wake_irq(dev);
 	__update_runtime_status(dev, RPM_ACTIVE);
 	dev->power.deferred_resume = false;
 	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);
 
+	dev_pm_disable_wake_irq(dev);
 	retval = rpm_callback(callback, dev);
 	if (retval) {
 		__update_runtime_status(dev, RPM_SUSPENDED);
 		pm_runtime_cancel_pending(dev);
+		dev_pm_enable_wake_irq(dev);
 	} else {
  no_callback:
 		__update_runtime_status(dev, RPM_ACTIVE);
diff --git a/drivers/base/power/wakeirq.c b/drivers/base/power/wakeirq.c
new file mode 100644
index 0000000..7470004
--- /dev/null
+++ b/drivers/base/power/wakeirq.c
@@ -0,0 +1,273 @@ 
+/*
+ * 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);
+}
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index 7726200..7332ebc 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -14,6 +14,7 @@ 
 #include <linux/suspend.h>
 #include <linux/seq_file.h>
 #include <linux/debugfs.h>
+#include <linux/pm_wakeirq.h>
 #include <trace/events/power.h>
 
 #include "power.h"
@@ -239,6 +240,97 @@  int device_wakeup_enable(struct device *dev)
 EXPORT_SYMBOL_GPL(device_wakeup_enable);
 
 /**
+ * device_wakeup_attach_irq - Attach a wakeirq to a wakeup source
+ * @dev: Device to handle
+ * @wakeirq: Device specific wakeirq entry
+ *
+ * Attach a device wakeirq to the wakeup source so the device
+ * wake IRQ can be configured automatically for suspend and
+ * resume.
+ */
+int device_wakeup_attach_irq(struct device *dev,
+			     struct wake_irq *wakeirq)
+{
+	struct wakeup_source *ws;
+	int ret = 0;
+
+	spin_lock_irq(&dev->power.lock);
+	ws = dev->power.wakeup;
+	if (!ws) {
+		dev_err(dev, "forgot to call call device_init_wakeup?\n");
+		ret = -EINVAL;
+		goto unlock;
+	}
+
+	if (ws->wakeirq) {
+		ret = -EEXIST;
+		goto unlock;
+	}
+
+	ws->wakeirq = wakeirq;
+
+unlock:
+	spin_unlock_irq(&dev->power.lock);
+
+	return ret;
+}
+
+/**
+ * device_wakeup_detach_irq - Detach a wakeirq from a wakeup source
+ * @dev: Device to handle
+ *
+ * Removes a device wakeirq from the wakeup source.
+ */
+void device_wakeup_detach_irq(struct device *dev)
+{
+	struct wakeup_source *ws;
+
+	spin_lock_irq(&dev->power.lock);
+	ws = dev->power.wakeup;
+	if (!ws)
+		goto unlock;
+
+	ws->wakeirq = NULL;
+
+unlock:
+	spin_unlock_irq(&dev->power.lock);
+}
+
+/**
+ * device_wakeup_arm_wake_irqs(void)
+ *
+ * Itereates over the list of device wakeirqs to arm them.
+ */
+void device_wakeup_arm_wake_irqs(void)
+{
+	struct wakeup_source *ws;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
+		if (ws->wakeirq)
+			dev_pm_arm_wake_irq(ws->wakeirq);
+	}
+	rcu_read_unlock();
+}
+
+/**
+ * device_wakeup_disarm_wake_irqs(void)
+ *
+ * Itereates over the list of device wakeirqs to disarm them.
+ */
+void device_wakeup_disarm_wake_irqs(void)
+{
+	struct wakeup_source *ws;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
+		if (ws->wakeirq)
+			dev_pm_disarm_wake_irq(ws->wakeirq);
+	}
+	rcu_read_unlock();
+}
+
+/**
  * device_wakeup_detach - Detach a device's wakeup source object from it.
  * @dev: Device to detach the wakeup source object from.
  *
diff --git a/include/linux/pm.h b/include/linux/pm.h
index 2d29c64..1c4ed0c 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -529,6 +529,7 @@  enum rpm_request {
 };
 
 struct wakeup_source;
+struct wake_irq;
 struct pm_domain_data;
 
 struct pm_subsys_data {
@@ -568,6 +569,7 @@  struct dev_pm_info {
 	unsigned long		timer_expires;
 	struct work_struct	work;
 	wait_queue_head_t	wait_queue;
+	struct wake_irq		*wakeirq;
 	atomic_t		usage_count;
 	atomic_t		child_count;
 	unsigned int		disable_depth:3;
diff --git a/include/linux/pm_wakeirq.h b/include/linux/pm_wakeirq.h
new file mode 100644
index 0000000..4046fa1
--- /dev/null
+++ b/include/linux/pm_wakeirq.h
@@ -0,0 +1,52 @@ 
+/*
+ * pm_wakeirq.h - 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.
+ */
+
+#ifndef _LINUX_PM_WAKEIRQ_H
+#define _LINUX_PM_WAKEIRQ_H
+
+#ifdef CONFIG_PM
+
+extern int dev_pm_set_wake_irq(struct device *dev, int irq);
+extern int dev_pm_set_dedicated_wake_irq(struct device *dev,
+					 int irq);
+extern void dev_pm_clear_wake_irq(struct device *dev);
+extern void dev_pm_enable_wake_irq(struct device *dev);
+extern void dev_pm_disable_wake_irq(struct device *dev);
+
+#else	/* !CONFIG_PM */
+
+static inline int dev_pm_set_wake_irq(struct device *dev, int irq)
+{
+	return 0;
+}
+
+static inline int dev_pm_set_dedicated__wake_irq(struct device *dev,
+						 int irq)
+{
+	return 0;
+}
+
+static inline void dev_pm_clear_wake_irq(struct device *dev)
+{
+}
+
+static inline void dev_pm_enable_wake_irq(struct device *dev)
+{
+}
+
+static inline void dev_pm_disable_wake_irq(struct device *dev)
+{
+}
+
+#endif	/* CONFIG_PM */
+#endif	/* _LINUX_PM_WAKEIRQ_H */
diff --git a/include/linux/pm_wakeup.h b/include/linux/pm_wakeup.h
index a0f7080..a344793 100644
--- a/include/linux/pm_wakeup.h
+++ b/include/linux/pm_wakeup.h
@@ -28,9 +28,17 @@ 
 
 #include <linux/types.h>
 
+struct wake_irq;
+
 /**
  * struct wakeup_source - Representation of wakeup sources
  *
+ * @name: Name of the wakeup source
+ * @entry: Wakeup source list entry
+ * @lock: Wakeup source lock
+ * @wakeirq: Optional device specific wakeirq
+ * @timer: Wakeup timer list
+ * @timer_expires: Wakeup timer expiration
  * @total_time: Total time this wakeup source has been active.
  * @max_time: Maximum time this wakeup source has been continuously active.
  * @last_time: Monotonic clock when the wakeup source's was touched last time.
@@ -47,6 +55,7 @@  struct wakeup_source {
 	const char 		*name;
 	struct list_head	entry;
 	spinlock_t		lock;
+	struct wake_irq		*wakeirq;
 	struct timer_list	timer;
 	unsigned long		timer_expires;
 	ktime_t total_time;