Linux Runtime PM Driver Example-Free Linux Device Drivers Training Online

Linux Runtime PM Driver Example

Linux Runtime PM Driver Example

A complete, original I2C driver — ep_prox01 — implementing the .runtime_suspend, .runtime_resume, and .runtime_idle callbacks discussed in the previous lecture, with a build/insmod walkthrough and real dmesg output. Part of our free Linux device drivers course.

1 Driver
ep_prox01.c
3 Callbacks
Wired Through DEFINE_RUNTIME_DEV_PM_OPS
Lecture 5 of 11
Kernel Power Management Series

This lecture puts the Linux runtime power management callback contract from the previous page into a complete, working example. ep_prox01 is an original teaching driver for a fictional I2C proximity sensor — it does not correspond to any real shipping part, and it is not a copy of any driver from any book. It implements the full probe()-time enable/disable lifecycle, wires its three runtime PM callbacks up through DEFINE_RUNTIME_DEV_PM_OPS(), and exposes a single sysfs attribute that lets you watch the device transition between RPM_ACTIVE and RPM_SUSPENDED in real time.

What You Will Learn

A complete probe()/remove() runtime PM lifecycle in real code Implementing runtime_suspend/runtime_resume/runtime_idle for real hardware registers Wiring callbacks with DEFINE_RUNTIME_DEV_PM_OPS and pm_ptr() Using pm_runtime_resume_and_get() from a sysfs attribute Building and loading the module, and reading dmesg for PM transitions

Prerequisites

  • Read the previous lecture in this pair, “Implementing Linux Runtime PM”, which explains the callback contract this driver implements.
  • Basic C and I2C client driver structure (probe, remove, i2c_device_id, of_device_id).
  • A Linux target (real board or QEMU) with kernel headers matching the running kernel, for building the out-of-tree module.

Meet The Example Driver: ep_prox01

About ep_prox01

ep_prox01 models a small I2C-attached proximity sensor with three registers: a read-only chip ID register, a power control register (0x00 = off, 0x01 = on), and a 16-bit data register holding the latest proximity reading. The driver’s job is to keep the sensor powered off whenever nothing is reading from it, and to power it back on transparently the moment a sysfs attribute is read — exactly the pattern real ambient-light, proximity, and gesture sensor drivers use in the mainline kernel, just with an original register map and an original name invented for this course.

Full Driver Source: ep_prox01.c

The three runtime PM callbacks and the probe()/remove() lifecycle calls are highlighted in the walkthrough below the listing. The system-sleep behavior comes for free from DEFINE_RUNTIME_DEV_PM_OPS(), which reuses these same runtime_suspend/runtime_resume functions via pm_runtime_force_suspend()/pm_runtime_force_resume() whenever the whole system goes to sleep.

// SPDX-License-Identifier: GPL-2.0
/*
 * ep_prox01.c - Example I2C proximity sensor driver demonstrating
 * Linux runtime power management for EmbeddedPathashala's free
 * Linux kernel development course.
 *
 * 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

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 ---------------------------------- */

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: powering down sensor\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;

	/* datasheet (fictional): sensor needs ~2ms to settle after power-up */
	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, requesting suspend\n");
	return 0; /* let the PM core proceed straight to runtime_suspend() */
}

static DEFINE_RUNTIME_DEV_PM_OPS(ep_prox01_pm_ops,
				  ep_prox01_runtime_suspend,
				  ep_prox01_runtime_resume,
				  ep_prox01_runtime_idle);

/* ---- sysfs attribute: triggers resume -> read -> idle -> suspend */

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);

	pm_runtime_put(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(): the runtime PM enable/disable lifecycle */

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 up by the bootloader at this
	 * point, so tell the runtime PM core the true initial state
	 * before enabling it -- otherwise the core assumes 'suspended'
	 * and the very first idle check would skip a power-down that
	 * never actually happened.
	 */
	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);

	/*
	 * Hold an extra reference while we finish probing so an
	 * asynchronous idle-triggered suspend 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_put(dev);

	dev_info(dev, "runtime PM enabled, initial status = active\n");
	return 0;
}

static void ep_prox01_remove(struct i2c_client *client)
{
	struct device *dev = &client->dev;

	dev_info(dev, "removing driver, disabling runtime PM\n");
	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("Example I2C proximity sensor driver demonstrating Linux runtime power management");
MODULE_LICENSE("GPL");

What Happens When You Read proximity_raw

user: cat proximity_raw→pm_runtime_resume_and_get(dev)
status is RPM_SUSPENDED→runtime_resume() runs→sensor powered on
i2c_smbus_read_word_data()→reads EP_PROX01_REG_DATA
pm_runtime_put(dev)→usage_count back to 0→idle check queued
runtime_idle() returns 0→runtime_suspend() runs→sensor powered off again

Build and Insmod Walkthrough

Save the listing above as ep_prox01.c alongside the following Makefile in an empty directory:

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

Build it against your running kernel’s headers:

$ make
  CC [M]  ep_prox01.o
  MODPOST ep_prox01.mod.c
  CC [M]  ep_prox01.mod.o
  LD [M]  ep_prox01.ko

Load the module. On a target where the sensor is described in the device tree with compatible = "ep,prox01", the driver binds automatically as soon as the module is loaded. On a bench setup without a device tree entry, you can instantiate the device 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

Expected dmesg output right after probe completes. Note that the driver core automatically requests an idle check once probe() returns, so the sensor drops into runtime suspend on its own within milliseconds — with no user-space action needed:

$ dmesg | tail -n 10
[  123.456701] ep_prox01 1-0044: EP Prox01 proximity sensor found, chip id 0x7a
[  123.456715] ep_prox01 1-0044: runtime PM enabled, initial status = active
[  123.501233] ep_prox01 1-0044: runtime idle check triggered
[  123.501240] ep_prox01 1-0044: runtime_idle: no active users, requesting suspend
[  123.501299] ep_prox01 1-0044: runtime_suspend: powering down sensor
[  123.501311] ep_prox01 1-0044: runtime PM status now suspended

Confirm the status from sysfs directly:

$ cat /sys/bus/i2c/devices/1-0044/power/runtime_status
suspended

Now read the proximity value. This forces a resume, an I2C register read, and — once the reference is dropped — an automatic idle-triggered suspend, all visible in dmesg:

$ cat /sys/bus/i2c/devices/1-0044/proximity_raw
812

$ dmesg | tail -n 6
[  145.220011] ep_prox01 1-0044: runtime_resume: powering up sensor
[  145.222344] ep_prox01 1-0044: runtime PM status now active
[  145.222401] ep_prox01 1-0044: proximity_raw read = 812
[  145.222450] ep_prox01 1-0044: runtime idle check triggered
[  145.222457] ep_prox01 1-0044: runtime_idle: no active users, requesting suspend
[  145.222510] ep_prox01 1-0044: runtime_suspend: powering down sensor

Unload the module and confirm clean teardown:

$ echo 0x44 | sudo tee /sys/bus/i2c/devices/i2c-1/delete_device
$ sudo rmmod ep_prox01

$ dmesg | tail -n 1
[  210.004112] ep_prox01 1-0044: removing driver, disabling runtime PM

Common Mistakes and Troubleshooting

  • proximity_raw always returns a stale or negative value. Check that pm_runtime_resume_and_get() actually returned 0 before touching the bus; a nonzero return from the sensor’s own .runtime_resume() will propagate here as a fatal PM error.
  • The sensor never seems to power off. Confirm the sysfs handler always calls pm_runtime_put() on every return path, including the error path after a failed i2c_smbus_read_word_data() — an unbalanced get() leaves usage_count stuck above zero forever.
  • power/runtime_status stays “unsupported”. This means pm_runtime_enable() was never reached in probe(), usually because an earlier return (for example, a chip ID mismatch) short-circuited before the PM lifecycle calls.
  • rmmod hangs or logs warnings. Make sure remove() calls pm_runtime_disable() and does not also try to force a manual runtime suspend — the driver core already brackets driver removal with its own pm_runtime_get_sync()/pm_runtime_put_sync() pair.

Best Practices

  • Keep register-level power on/off logic in small, dedicated helper functions (ep_prox01_power_on/off here) so .runtime_suspend() and .runtime_resume() stay easy to read and audit.
  • Log dev_info() (or dev_dbg() in production) from inside each runtime PM callback during bring-up — it is the fastest way to confirm the Linux runtime power management transitions are actually happening the way you expect.
  • Guard shared data structures (here, the I2C transaction sequence in proximity_raw_show()) with your own mutex; the PM core’s guarantees about not overlapping runtime_suspend/runtime_resume do not extend to your driver’s own I/O paths.
  • Use pm_runtime_resume_and_get() rather than the older pm_runtime_get_sync() when you plan to check the return value, since it does not leave the usage counter incremented on failure.

Summary and Key Takeaways

ep_prox01 demonstrates every piece of the Linux runtime power management contract from the previous lecture in a complete, buildable driver: pm_runtime_set_active() and pm_runtime_enable() establish the truthful initial state in probe(), pm_runtime_get_noresume() and pm_runtime_put() bracket the remaining hardware setup, and DEFINE_RUNTIME_DEV_PM_OPS() wires the three callbacks into struct dev_pm_ops with system-sleep support included for free. The dmesg walkthrough shows the full cycle end to end — a device that suspends itself automatically once probe() finishes, resumes transparently the moment user space touches it through sysfs, and idles back down again the instant that access completes. The next lecture in this free Linux kernel development course builds directly on this driver’s get/put calls to add autosuspend delays, so the sensor does not bounce power on and off for every single read.

Frequently Asked Questions

Why does ep_prox01 use I2C instead of the platform bus?

Sensors like this are almost always attached over I2C in real hardware, and the pattern shown here — probe(), runtime PM setup, and a sysfs attribute that triggers resume/suspend — transfers directly to a platform_driver with only the bus-specific boilerplate changed.

Why DEFINE_RUNTIME_DEV_PM_OPS() instead of SET_RUNTIME_PM_OPS() in this driver?

DEFINE_RUNTIME_DEV_PM_OPS() builds the entire dev_pm_ops struct in one line and automatically reuses ep_prox01_runtime_suspend()/ep_prox01_runtime_resume() for system-sleep transitions via pm_runtime_force_suspend()/pm_runtime_force_resume(). SET_RUNTIME_PM_OPS() still works, but you would need to also fill in .suspend/.resume yourself to get the same system-sleep behavior.

What happens if I read proximity_raw twice in quick succession?

The second pm_runtime_resume_and_get() call simply finds the device still RPM_ACTIVE and increments usage_count without re-running .runtime_resume(); the PM core only invokes the callback on an actual RPM_SUSPENDED to RPM_ACTIVE transition.

How do I check the driver’s current runtime PM state from user space?

Read /sys/bus/i2c/devices/<bus-addr>/power/runtime_status. It reports “active”, “suspended”, or “unsupported” if runtime PM was never enabled for the device.

Why does the sensor suspend immediately after probe() finishes, with no user action?

The driver core itself calls pm_request_idle() right after a successful probe() returns. Since ep_prox01’s .runtime_idle() returns 0 and usage_count is already back to zero after the pm_runtime_put() at the end of probe(), the PM core proceeds straight into .runtime_suspend() automatically.

What comes after this lecture?

The next lecture in this series adds autosuspend to this exact driver — pm_runtime_use_autosuspend(), pm_runtime_set_autosuspend_delay(), and the *_autosuspend() helper variants — so the sensor stays powered on for a short grace period instead of suspending immediately after every single read.

Ready For Autosuspend?

You now have a driver that correctly enables Linux runtime power management, quiesces and restores real hardware state, and reacts to user-space access through sysfs. The next lecture in this free Linux kernel development course takes ep_prox01 further with autosuspend delays.

Continue To Autosuspend Lecture Back To Course Index

Leave a Reply

Your email address will not be published. Required fields are marked *