Linux Autosuspend Driver Code Example
Lecture 7 of the Linux Kernel Power Management series, part of Ravi’s free Linux kernel development course — a complete, original autosuspend driver example built on top of the ep_prox01 I2C driver, with a build/insmod walkthrough and real dmesg output showing the autosuspend timer in action.
ep_prox01.c (Autosuspend Revision)
use_autosuspend / set_delay / mark_last_busy / put_autosuspend
Kernel Power Management Series
This lecture turns the previous lecture’s theory into a complete, buildable autosuspend driver example, so you can watch the autosuspend timer arm, get pushed back, and finally expire in real dmesg output instead of just reading about it. We take the exact same ep_prox01 I2C proximity sensor driver from the runtime PM example lecture and extend it: three new calls go into probe(), one new pairing replaces the plain put() in the sysfs read path, and remove() gets a small but important change so no stray autosuspend timer can fire mid-teardown. Nothing about the register map, the chip, or the runtime_suspend/runtime_resume/runtime_idle callback trio changes — only the timing policy around when a suspend actually gets requested changes, which is exactly what an autosuspend driver example should demonstrate.
Key Terms In This Lecture
What You Will Learn
By the end of this autosuspend driver example you will be able to:
Prerequisites
- The previous lecture, Linux Runtime PM Autosuspend Guide, which explains power.request, the PM workqueue, and the autosuspend mechanism this driver implements — read it first if pm_runtime_use_autosuspend() and power.last_busy are new terms.
- The Linux Runtime PM Driver Example lecture, which introduced the ep_prox01 driver this autosuspend driver example continues to build on; you should recognize its register map and its three runtime PM callbacks before proceeding.
- A Linux target (real board or QEMU) with kernel headers matching the running kernel, for building the out-of-tree module.
- Root access, to load the module and instantiate an I2C device manually if you are not using a device tree overlay.
What Changes In This Autosuspend Driver Example
Compared to the plain runtime PM version of ep_prox01, exactly four things change, and nothing else. First, probe() calls pm_runtime_set_autosuspend_delay() and pm_runtime_use_autosuspend() once, right after pm_runtime_enable(), before the device is exposed to anything else. Second, every put() that follows a real hardware access — inside probe() itself and inside the proximity_raw sysfs handler — is replaced by the pair pm_runtime_mark_last_busy() followed by pm_runtime_put_autosuspend(), instead of a bare pm_runtime_put(). Third, remove() resumes the device synchronously and drops the reference with pm_runtime_put_noidle() before calling pm_runtime_disable(), so a timer armed moments earlier cannot fire against a device that is already being torn down. Fourth, and this one requires no code at all: a new sysfs attribute, power/autosuspend_delay_ms, appears automatically the moment pm_runtime_use_autosuspend() is called, letting you retune the grace period from user space with no rebuild.
The table below summarizes the delta against the previous lecture’s driver, which is worth keeping in mind while reading the full listing further down.
| Aspect | Plain Runtime PM (Previous Lecture) | This Autosuspend Driver Example |
|---|---|---|
| Suspend timing after last access | Immediate — runtime_idle() fires as soon as usage_count hits 0 | Delayed by autosuspend_delay_ms via power.suspend_timer |
| Put call after a read | pm_runtime_put(dev) | pm_runtime_mark_last_busy(dev); pm_runtime_put_autosuspend(dev) |
| Probe-time setup | pm_runtime_set_active() + pm_runtime_enable() | Same, plus pm_runtime_set_autosuspend_delay() + pm_runtime_use_autosuspend() |
| Repeated quick reads | Resume/suspend cycle on every single read | Device stays active across reads inside the delay window |
| New sysfs attribute | None beyond power/runtime_status | power/autosuspend_delay_ms, tunable live |
Full Autosuspend Driver Example: ep_prox01.c
The four changes described above are commented inline in the listing. Everything else — the register definitions, the power_on()/power_off() helpers, the three runtime PM callbacks, and DEFINE_RUNTIME_DEV_PM_OPS() — is identical to the previous lecture’s driver, because the autosuspend mechanism only changes when a suspend gets requested, never what a suspend or resume actually does to the hardware.
// SPDX-License-Identifier: GPL-2.0
/*
* ep_prox01.c - I2C proximity sensor driver with runtime PM autosuspend,
* for EmbeddedPathashala's free Linux kernel development course.
*
* This autosuspend driver example continues the ep_prox01 driver from
* the previous lecture by wiring pm_runtime_use_autosuspend(),
* pm_runtime_set_autosuspend_delay(), pm_runtime_mark_last_busy(), and
* pm_runtime_put_autosuspend() into its probe(), remove(), and sysfs
* read paths.
*
* This is an original teaching example. ep_prox01 is not a real
* shipping part number and does not correspond to any vendor chip.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#define EP_PROX01_REG_CHIP_ID 0x00
#define EP_PROX01_REG_POWER 0x01
#define EP_PROX01_REG_DATA 0x02
#define EP_PROX01_POWER_ON 0x01
#define EP_PROX01_POWER_OFF 0x00
#define EP_PROX01_CHIP_ID_VAL 0x7a
/*
* The fictional datasheet says the sensor settles ~2ms after power-up.
* A typical caller polls proximity_raw roughly once a second, so
* suspending immediately after every single read would spend more
* time resuming than actually sensing. 3 seconds is a deliberately
* chosen grace period, comfortably longer than one polling interval,
* used purely to make the timer behavior easy to observe in dmesg.
*/
#define EP_PROX01_AUTOSUSPEND_DELAY_MS 3000
struct ep_prox01_data {
struct i2c_client *client;
struct mutex lock;
};
static int ep_prox01_power_on(struct ep_prox01_data *data)
{
return i2c_smbus_write_byte_data(data->client, EP_PROX01_REG_POWER,
EP_PROX01_POWER_ON);
}
static int ep_prox01_power_off(struct ep_prox01_data *data)
{
return i2c_smbus_write_byte_data(data->client, EP_PROX01_REG_POWER,
EP_PROX01_POWER_OFF);
}
/* ---- Runtime PM callback trio (unchanged from the previous lecture) --- */
static int ep_prox01_runtime_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ep_prox01_data *data = i2c_get_clientdata(client);
int ret;
dev_info(dev, "runtime_suspend: autosuspend timer expired, powering down\n");
ret = ep_prox01_power_off(data);
if (ret)
return ret;
dev_info(dev, "runtime PM status now suspended\n");
return 0;
}
static int ep_prox01_runtime_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct ep_prox01_data *data = i2c_get_clientdata(client);
int ret;
dev_info(dev, "runtime_resume: powering up sensor\n");
ret = ep_prox01_power_on(data);
if (ret)
return ret;
usleep_range(2000, 3000);
dev_info(dev, "runtime PM status now active\n");
return 0;
}
static int ep_prox01_runtime_idle(struct device *dev)
{
dev_info(dev, "runtime idle check triggered\n");
dev_info(dev, "runtime_idle: no active users, arming autosuspend timer\n");
return 0; /* autosuspend is enabled, so the core arms the timer here
* instead of suspending immediately */
}
static DEFINE_RUNTIME_DEV_PM_OPS(ep_prox01_pm_ops,
ep_prox01_runtime_suspend,
ep_prox01_runtime_resume,
ep_prox01_runtime_idle);
/* ---- sysfs attribute: resume -> read -> mark_last_busy -> autosuspend */
static ssize_t proximity_raw_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct ep_prox01_data *data = dev_get_drvdata(dev);
int val, ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
mutex_lock(&data->lock);
val = i2c_smbus_read_word_data(data->client, EP_PROX01_REG_DATA);
mutex_unlock(&data->lock);
dev_info(dev, "proximity_raw read = %d\n", val);
/*
* CHANGE 1: refresh power.last_busy before requesting a suspend,
* then request an autosuspend instead of an immediate one. If
* another read comes in before the delay expires, this timer
* simply gets pushed back and the device never suspends for
* that idle window.
*/
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
if (val < 0)
return val;
return sysfs_emit(buf, "%d\n", val);
}
static DEVICE_ATTR_RO(proximity_raw);
static struct attribute *ep_prox01_attrs[] = {
&dev_attr_proximity_raw.attr,
NULL,
};
ATTRIBUTE_GROUPS(ep_prox01);
/* ---- probe() / remove() ---- */
static int ep_prox01_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct ep_prox01_data *data;
int chip_id;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->client = client;
mutex_init(&data->lock);
i2c_set_clientdata(client, data);
dev_set_drvdata(dev, data);
chip_id = i2c_smbus_read_byte_data(client, EP_PROX01_REG_CHIP_ID);
if (chip_id < 0)
return chip_id;
if (chip_id != EP_PROX01_CHIP_ID_VAL) {
dev_err(dev, "unexpected chip id 0x%02x\n", chip_id);
return -ENODEV;
}
dev_info(dev, "EP Prox01 proximity sensor found, chip id 0x%02x\n",
chip_id);
/* The sensor is already powered by the bootloader at this point. */
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
/*
* CHANGE 2: enable autosuspend and set its delay here, before the
* device is touched by anything else. From this point on, every
* put() anywhere in this driver must use the _autosuspend()
* variant, or this setup is silently defeated.
*/
pm_runtime_set_autosuspend_delay(dev, EP_PROX01_AUTOSUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
/*
* Hold an extra reference while we finish probing so an
* asynchronous idle-triggered autosuspend can't race with any
* remaining setup that still needs the bus powered.
*/
pm_runtime_get_noresume(dev);
/* ... any additional one-time register setup would go here ... */
pm_runtime_mark_last_busy(dev);
pm_runtime_put_autosuspend(dev);
dev_info(dev, "runtime PM autosuspend enabled, delay = %d ms\n",
EP_PROX01_AUTOSUSPEND_DELAY_MS);
return 0;
}
static void ep_prox01_remove(struct i2c_client *client)
{
struct device *dev = &client->dev;
int ret;
dev_info(dev, "removing driver, disabling runtime PM\n");
/*
* CHANGE 3: bring the device back to a known-active state
* synchronously and drop the reference with put_noidle(), which
* runs no callback at all, instead of a plain put(). This
* guarantees no autosuspend timer armed moments earlier can fire
* against a device that pm_runtime_disable() is about to disable.
*/
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
dev_warn(dev, "resume during remove failed (%d), disabling anyway\n", ret);
else
pm_runtime_put_noidle(dev);
pm_runtime_disable(dev);
}
static const struct i2c_device_id ep_prox01_id[] = {
{ "ep_prox01" },
{ }
};
MODULE_DEVICE_TABLE(i2c, ep_prox01_id);
static const struct of_device_id ep_prox01_of_match[] = {
{ .compatible = "ep,prox01" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_prox01_of_match);
static struct i2c_driver ep_prox01_driver = {
.driver = {
.name = "ep_prox01",
.of_match_table = ep_prox01_of_match,
.pm = pm_ptr(&ep_prox01_pm_ops),
.dev_groups = ep_prox01_groups,
},
.probe = ep_prox01_probe,
.remove = ep_prox01_remove,
.id_table = ep_prox01_id,
};
module_i2c_driver(ep_prox01_driver);
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Autosuspend driver example built on the ep_prox01 I2C proximity sensor driver");
MODULE_LICENSE("GPL");
Two Quick Reads Inside One Autosuspend Window
Building And Testing The Autosuspend Driver Example
Save the listing above as ep_prox01.c alongside this Makefile in an empty directory. If you already have the previous lecture’s version checked out, simply overwrite it in place — the module name and driver name are unchanged, so the two are drop-in replacements for each other.
obj-m += ep_prox01.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
$ make
CC [M] ep_prox01.o
MODPOST ep_prox01.mod.c
CC [M] ep_prox01.mod.o
LD [M] ep_prox01.ko
Load it and, on a bench setup without a device tree entry, instantiate the sensor manually against an existing I2C adapter:
$ sudo insmod ep_prox01.ko
$ echo ep_prox01 0x44 | sudo tee /sys/bus/i2c/devices/i2c-1/new_device
Right after probe() completes, the driver core requests an idle check. Because autosuspend is now enabled, the sensor does not suspend instantly the way it did in the previous lecture’s driver — instead the timer arms, and only after the configured delay elapses does the suspend actually happen:
$ dmesg | tail -n 8
[ 200.100011] ep_prox01 1-0044: EP Prox01 proximity sensor found, chip id 0x7a
[ 200.100025] ep_prox01 1-0044: runtime PM autosuspend enabled, delay = 3000 ms
[ 200.100301] ep_prox01 1-0044: runtime idle check triggered
[ 200.100309] ep_prox01 1-0044: runtime_idle: no active users, arming autosuspend timer
[ 203.100480] ep_prox01 1-0044: runtime_suspend: autosuspend timer expired, powering down
[ 203.100501] ep_prox01 1-0044: runtime PM status now suspended
Notice the roughly three-second gap between the idle check at 200.100s and the suspend at 203.100s — that gap is the autosuspend_delay_ms grace period doing exactly what it is supposed to do. Now issue two quick reads about a second apart, well inside the delay window, and watch the device stay active between them instead of cycling through resume/suspend twice:
$ cat /sys/bus/i2c/devices/1-0044/proximity_raw
812
$ sleep 1
$ cat /sys/bus/i2c/devices/1-0044/proximity_raw
809
$ dmesg | tail -n 6
[ 260.004112] ep_prox01 1-0044: runtime_resume: powering up sensor
[ 260.006430] ep_prox01 1-0044: runtime PM status now active
[ 260.006471] ep_prox01 1-0044: proximity_raw read = 812
[ 261.009903] ep_prox01 1-0044: proximity_raw read = 809
[ 264.010102] ep_prox01 1-0044: runtime_idle: no active users, arming autosuspend timer
[ 264.010311] ep_prox01 1-0044: runtime_suspend: autosuspend timer expired, powering down
There is only one runtime_resume between the two reads, not two, because the second read at 261.0s found the device already RPM_ACTIVE and simply refreshed power.last_busy — exactly the behavior the mark_last_busy()/put_autosuspend() pairing exists to produce. The suspend at 264.0s lands roughly three seconds after the second read, confirming the timer restarted rather than continuing to count down from the first access.
Reading And Tuning autosuspend_delay_ms From Sysfs
Because pm_runtime_use_autosuspend() was called in probe(), the power/autosuspend_delay_ms attribute exists automatically, no extra code required. Confirm the compiled-in default, then try a much shorter delay without touching the driver source or rebuilding anything:
$ cat /sys/bus/i2c/devices/1-0044/power/autosuspend_delay_ms
3000
$ echo 300 | sudo tee /sys/bus/i2c/devices/1-0044/power/autosuspend_delay_ms
300
$ cat /sys/bus/i2c/devices/1-0044/proximity_raw
805
$ dmesg | tail -n 4
[ 310.500221] ep_prox01 1-0044: runtime_resume: powering up sensor
[ 310.502560] ep_prox01 1-0044: runtime PM status now active
[ 310.502601] ep_prox01 1-0044: proximity_raw read = 805
[ 310.803011] ep_prox01 1-0044: runtime_suspend: autosuspend timer expired, powering down
With the delay lowered to 300 ms, the suspend now lands roughly 300 ms after the read instead of 3000 ms — no module reload needed, because power.autosuspend_delay is just a field the sysfs attribute writes into directly. Setting a negative value disables autosuspend entirely for the device, reverting to the immediate-suspend behavior from the previous lecture’s driver:
$ echo -1 | sudo tee /sys/bus/i2c/devices/1-0044/power/autosuspend_delay_ms
-1
Finally, unload the module and confirm clean teardown — note that CHANGE 3 in remove() means you will see a resume/idle pair before the disable message, not a stray autosuspend timer firing after the driver is already gone:
$ echo 0x44 | sudo tee /sys/bus/i2c/devices/i2c-1/delete_device
$ sudo rmmod ep_prox01
$ dmesg | tail -n 1
[ 410.004112] ep_prox01 1-0044: removing driver, disabling runtime PM
Autosuspend Driver Example: Common Mistakes And Troubleshooting
- The device suspends instantly, as if autosuspend were never enabled. Check that every put() following a hardware access in this autosuspend driver example uses pm_runtime_put_autosuspend(), not pm_runtime_put(). A single leftover plain put() call anywhere on the access path bypasses power.autosuspend_delay entirely for that path.
- The timer always seems to fire based on stale timing. Confirm pm_runtime_mark_last_busy() runs immediately before pm_runtime_put_autosuspend(), not after it and not several lines earlier — the timer’s expiration is computed from whatever power.last_busy held at the moment put_autosuspend() actually ran.
- autosuspend_delay_ms does not exist under power/. This attribute is only created once pm_runtime_use_autosuspend() has actually been called; if probe() returned early (for example on the chip ID mismatch path) before reaching that call, the attribute is simply absent.
- rmmod occasionally logs a resume happening after the “removing driver” message. This means CHANGE 3 was skipped and remove() went straight to pm_runtime_disable() while a timer from the last access was still counting down; always resume-and-drop the reference in remove() before disabling.
- Two back-to-back reads still show two separate runtime_resume() calls in dmesg. This usually means the autosuspend delay is set too low relative to the gap between reads, so the timer genuinely expired between them — increase autosuspend_delay_ms or check the gap in your test script.
Best Practices For This Autosuspend Driver Example
- Set up pm_runtime_use_autosuspend() and pm_runtime_set_autosuspend_delay() together, right after pm_runtime_enable(), so there is never a window where the device is enabled for runtime PM but not yet configured for autosuspend.
- Audit every pm_runtime_put() call whenever you retrofit autosuspend onto an existing driver, the way this lecture retrofitted it onto ep_prox01 — a single missed conversion silently defeats the whole mechanism for that one code path.
- Choose the delay from the hardware’s real power-up cost plus a margin, exactly as EP_PROX01_AUTOSUSPEND_DELAY_MS does here, and document the reasoning in a comment next to the #define.
- Test with dmesg open in a second terminal while issuing reads at different intervals, some inside the delay window and some outside it, to directly observe the timer restart behavior shown in the diagram above.
- Always resume synchronously and use pm_runtime_put_noidle() in remove(), never a plain pm_runtime_disable() on its own, when autosuspend is enabled for the device.
Summary: Autosuspend Driver Example Recap
This autosuspend driver example took the working ep_prox01 runtime PM driver from the previous lecture and changed exactly four things: probe() now calls pm_runtime_set_autosuspend_delay() and pm_runtime_use_autosuspend() once, every put() after a hardware access became a mark_last_busy()/put_autosuspend() pair, remove() resumes and drops its reference with put_noidle() before disabling, and a new autosuspend_delay_ms sysfs attribute appeared for free. The dmesg walkthrough showed the timer arming instead of suspending immediately, restarting when a second read landed inside the grace period, and finally expiring once accesses stopped — and the sysfs section showed that same delay being retuned live, with no rebuild, down to 300 ms and then disabled entirely. With the theory from the previous lecture and this hands-on autosuspend driver example both in place, you have the complete toolkit for keeping a real device from thrashing between power states under normal, bursty access patterns.
Frequently Asked Questions
Do I need to change runtime_suspend(), runtime_resume(), or runtime_idle() to add autosuspend to a driver?
No. As this autosuspend driver example shows, all three callbacks stay exactly as they were in the plain runtime PM version. Autosuspend only changes when a suspend request gets queued, not what the suspend or resume callback actually does to the hardware.
Why does probe() still call pm_runtime_mark_last_busy() and pm_runtime_put_autosuspend() instead of just pm_runtime_put()?
Once pm_runtime_use_autosuspend() has been called anywhere in the driver’s lifetime for that device, every put() following a real access should consistently use the autosuspend-aware variant, including the one at the end of probe(), so the very first idle window also respects the configured delay instead of suspending immediately.
What happens if two reads land exactly at the edge of the autosuspend delay window?
Whichever event the PM core’s timer processes first wins. If the second read’s pm_runtime_resume_and_get() runs before the timer’s workqueue callback fires, the resume request cancels the pending suspend, exactly as described in the previous lecture; if the timer fires microseconds earlier, the device suspends and the next read simply triggers a fresh resume.
Can I set a different autosuspend delay for different instances of the same driver?
Yes, in two ways: pm_runtime_set_autosuspend_delay() can be called with a per-instance value computed in probe() (for example, based on a device tree property), and independently, each instance’s autosuspend_delay_ms sysfs attribute can be tuned separately from user space at runtime.
Why does remove() call pm_runtime_resume_and_get() before pm_runtime_disable() in this autosuspend driver example?
Because an autosuspend timer may already be counting down when remove() runs. Resuming synchronously and dropping the reference with pm_runtime_put_noidle() guarantees the device is in a known, active state with no timer pending before pm_runtime_disable() takes it out of runtime PM entirely.
Is it safe to set autosuspend_delay_ms to 0 from user space?
Yes, and it is a supported value — it means the device suspends essentially as soon as it goes idle, behaving very close to plain runtime PM without autosuspend, which is useful for temporarily disabling the grace period without fully disabling autosuspend with a negative value.
How would I verify autosuspend_delay_ms actually took effect without watching dmesg?
Watch /sys/bus/i2c/devices/1-0044/power/runtime_status alongside runtime_active_time and runtime_suspended_time in the same power/ directory; after changing the delay, the time spent in the active state between accesses should visibly track the new value.
You’ve Built A Complete Autosuspend Driver Example
You now have a full, working autosuspend driver example that arms a timer instead of suspending instantly, survives bursty access patterns without thrashing, and exposes its delay live through sysfs — all as part of this free Linux kernel development course.
Back To The Autosuspend Theory Lecture Next: Power Domains And Suspend Sequence