Runtime PM, system sleep states, dev_pm_ops callbacks, wakeup sources, power domains, and autosuspend for Linux device drivers.
Linux power management for device drivers operates in two distinct modes: runtime PM, which suspends idle devices while the system is fully operational, and system sleep, which powers down devices during system-wide sleep transitions such as suspend-to-RAM and hibernation. Both modes use the same struct dev_pm_ops callback table defined in include/linux/pm.h.
Devices enter low-power states as part of a system-wide transition (suspend-to-RAM, suspend-to-disk). The PM core coordinates ordering across the entire device hierarchy.
Runtime PM
Devices are individually suspended while the system is running and resumed on demand. Reduces power consumption without a full system sleep.
These models are not mutually exclusive. A driver that supports runtime PM must also handle the case where the device is already runtime-suspended when a system sleep transition begins.
All PM callbacks are collected in struct dev_pm_ops. Declare one and reference it from your driver struct:
#include <linux/pm.h>#include <linux/pm_runtime.h>static int mydrv_runtime_suspend(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); /* Quiesce hardware, gate clocks */ mydrv_hw_disable(priv); clk_disable_unprepare(priv->clk); return 0;}static int mydrv_runtime_resume(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); int ret; ret = clk_prepare_enable(priv->clk); if (ret) return ret; mydrv_hw_enable(priv); return 0;}static int mydrv_suspend(struct device *dev){ /* System sleep: quiesce device, save state if needed */ return pm_runtime_force_suspend(dev);}static int mydrv_resume(struct device *dev){ /* System resume: restore state */ return pm_runtime_force_resume(dev);}static const struct dev_pm_ops mydrv_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS(mydrv_suspend, mydrv_resume) SET_RUNTIME_PM_OPS(mydrv_runtime_suspend, mydrv_runtime_resume, NULL)};static struct platform_driver mydrv = { .driver = { .name = "mydevice", .pm = &mydrv_pm_ops, }, .probe = mydrv_probe, .remove = mydrv_remove,};
SET_SYSTEM_SLEEP_PM_OPS and SET_RUNTIME_PM_OPS are convenience macros that populate the correct fields and compile out to nothing in non-PM kernel configurations.
The system sleep callbacks are called in phases during suspend and resume:
Phase
Suspend callback
Resume callback
Notes
1
prepare
complete
Top-down; no new children may register after prepare
2
suspend
resume
Quiesce I/O; may enable wakeup
3
suspend_late
resume_early
After runtime PM disabled; save register state
4
suspend_noirq
resume_noirq
IRQs disabled; final hardware save
Runtime PM callbacks:
struct dev_pm_ops { /* System sleep */ int (*prepare)(struct device *dev); void (*complete)(struct device *dev); int (*suspend)(struct device *dev); int (*resume)(struct device *dev); int (*freeze)(struct device *dev); /* hibernation: before snapshot */ int (*thaw)(struct device *dev); /* hibernation: after snapshot */ int (*poweroff)(struct device *dev); /* hibernation: before power off */ int (*restore)(struct device *dev); /* hibernation: restore from disk */ int (*suspend_late)(struct device *dev); int (*resume_early)(struct device *dev); int (*suspend_noirq)(struct device *dev); int (*resume_noirq)(struct device *dev); /* ... freeze_late, thaw_early, poweroff_late, etc. */ /* Runtime PM */ int (*runtime_suspend)(struct device *dev); int (*runtime_resume)(struct device *dev); int (*runtime_idle)(struct device *dev);};
Runtime PM allows the driver to suspend an idle device and resume it automatically when it is accessed. The PM core tracks a usage counter and an active-children counter; when both reach zero the runtime_idle callback fires, which by default triggers runtime_suspend.
static int mydrv_probe(struct platform_device *pdev){ struct device *dev = &pdev->dev; /* ... hardware init ... */ /* * Mark the device as active and enable runtime PM. * The initial runtime PM state is RPM_SUSPENDED; set_active moves it * to RPM_ACTIVE so the first pm_runtime_put() starts the idle timer. */ pm_runtime_set_active(dev); pm_runtime_enable(dev); /* * Drop the reference held since set_active so the device * can suspend after autosuspend_delay with no users. */ pm_runtime_put_autosuspend(dev); return 0;}static int mydrv_remove(struct platform_device *pdev){ struct device *dev = &pdev->dev; pm_runtime_get_sync(dev); /* ensure device is resumed before shutdown */ pm_runtime_disable(dev); /* stop runtime PM; no more callbacks */ pm_runtime_put_noidle(dev); /* release the reference taken above */ /* ... hardware teardown ... */ return 0;}
#include <linux/pm_runtime.h>/* * pm_runtime_get_sync - increment usage counter and synchronously resume. * Returns 0 (or positive) on success, negative on error. * Caller must call pm_runtime_put*() when done. */int pm_runtime_get_sync(struct device *dev);/* * pm_runtime_get_if_in_use - get only if already active; returns 1 if * successful, 0 if device was suspended, negative on error. */int pm_runtime_get_if_in_use(struct device *dev);/* * pm_runtime_put_sync - decrement usage counter and synchronously suspend * if the counter reaches zero. */int pm_runtime_put_sync(struct device *dev);/* * pm_runtime_put_autosuspend - decrement counter; schedule autosuspend * if counter reaches zero (respects autosuspend_delay). */void pm_runtime_put_autosuspend(struct device *dev);/* Non-blocking put — schedules suspend via the PM workqueue */void pm_runtime_put(struct device *dev);/* Mark current time as last busy — resets autosuspend timer */void pm_runtime_mark_last_busy(struct device *dev);/* Force suspend or resume regardless of usage counter */int pm_runtime_force_suspend(struct device *dev);int pm_runtime_force_resume(struct device *dev);/* Enable / disable runtime PM (not ref-counted) */void pm_runtime_enable(struct device *dev);void pm_runtime_disable(struct device *dev);/* Set initial runtime PM state without triggering callbacks */void pm_runtime_set_active(struct device *dev);void pm_runtime_set_suspended(struct device *dev);
pm_runtime_get_sync() can sleep and must not be called from atomic context. Use pm_runtime_get_if_in_use() in interrupt handlers or use pm_runtime_irq_safe() to configure the device for atomic-context callbacks.
/* In any driver operation that accesses hardware */static int mydrv_do_transfer(struct mydrv_priv *priv, ...){ int ret; ret = pm_runtime_get_sync(priv->dev); if (ret < 0) return ret; /* Access hardware safely */ ret = mydrv_hw_xfer(priv, ...); pm_runtime_mark_last_busy(priv->dev); pm_runtime_put_autosuspend(priv->dev); return ret;}
Autosuspend delays the runtime suspend until a configurable timeout has elapsed since the last time pm_runtime_mark_last_busy() was called. This prevents excessive suspend/resume cycling for devices that are used in short bursts.
#include <linux/pm_runtime.h>/* Enable autosuspend for a device */void pm_runtime_use_autosuspend(struct device *dev);void pm_runtime_dont_use_autosuspend(struct device *dev);/* Set the autosuspend delay in milliseconds */void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
Devices quiesce I/O, save register state if necessary, optionally enable wakeup events, and enter the lowest power state the hardware supports while maintaining context.
static int mydrv_suspend(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); /* * If using runtime PM, pm_runtime_force_suspend() will call * runtime_suspend() if the device is currently active. */ return pm_runtime_force_suspend(dev);}static int mydrv_resume(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); return pm_runtime_force_resume(dev);}
Devices that can generate hardware wakeup events must be registered as wakeup sources:
#include <linux/pm_wakeup.h>/* * device_set_wakeup_capable - mark a device as capable of waking the system. * Creates the /sys/devices/.../power/wakeup sysfs file. */void device_set_wakeup_capable(struct device *dev, bool capable);/* * device_set_wakeup_enable - enable/disable wakeup signalling. * Must only be called for wakeup-capable devices. */int device_set_wakeup_enable(struct device *dev, bool enable);/* * device_may_wakeup - returns true if wakeup is capable AND enabled. * Use this in suspend callbacks to decide whether to arm wakeup hardware. */bool device_may_wakeup(struct device *dev);
Typical suspend callback with wakeup support:
static int mydrv_suspend(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); if (device_may_wakeup(dev)) { /* * Enable the hardware wakeup interrupt so the device can * bring the system back up while it is in a sleep state. */ enable_irq_wake(priv->irq); } else { disable_irq(priv->irq); } mydrv_hw_suspend(priv); return 0;}static int mydrv_resume(struct device *dev){ struct mydrv_priv *priv = dev_get_drvdata(dev); mydrv_hw_resume(priv); if (device_may_wakeup(dev)) disable_irq_wake(priv->irq); else enable_irq(priv->irq); return 0;}
In probe, declare wakeup capability:
static int mydrv_probe(struct platform_device *pdev){ /* ... */ device_set_wakeup_capable(&pdev->dev, true); /* Wakeup is disabled by default; user enables via sysfs power/wakeup */ return 0;}
Power domains group devices that share a power rail or clock gate. When the last device in a domain suspends, the domain itself can be powered off. The power domain framework provides callbacks via struct dev_pm_domain:
The kernel exposes runtime PM controls per device under /sys/devices/.../power/:
File
Values
Description
control
auto / on
auto allows runtime PM; on forces device active
runtime_status
active / suspended / suspending / resuming
Current runtime PM state
runtime_active_time
milliseconds
Total time device has been active
runtime_suspended_time
milliseconds
Total time device has been suspended
autosuspend_delay_ms
integer
Autosuspend delay; -1 to disable autosuspend
wakeup
enabled / disabled
System wakeup capability (wakeup-capable devices only)
# Check runtime PM status for a devicecat /sys/bus/platform/devices/mydevice.0/power/runtime_status# Prevent runtime suspend (force device on)echo on > /sys/bus/platform/devices/mydevice.0/power/control# Re-enable autosuspendecho auto > /sys/bus/platform/devices/mydevice.0/power/control