I2C Device Tree Linux Driver
Free Linux Device Drivers Course — I2C Client Drivers, Part 5
free linux device drivers course
free linux kernel development course
free embedded systems course
free embedded linux course
Every I2C driver we have written so far in this free linux device drivers course needs one more thing before it can find its device on the bus: a way to tell the kernel which chip physically exists, at which address. This lecture covers i2c device tree driver registration — the modern, standard way of describing I2C devices on kernel 6.x, and briefly looks at the old board-file method it replaced.
What You Will Learn
- The legacy board-info method for declaring I2C devices, and why it is deprecated
- How I2C devices are represented as child nodes in the device tree
- The role of the
compatibleandregproperties for I2C nodes - How
of_device_idconnects a device tree node to your driver - How to write and register an original I2C driver that matches purely through device tree
Prerequisites
- Parts 1–4 of this I2C Client Drivers series
- Basic device tree concepts from the Device Tree chapter of this free embedded linux course (compatible strings, nodes, overlays)
- A board with a device tree enabled kernel (Raspberry Pi, BeagleBone, or QEMU virt board)
Two Ways to Tell the Kernel a Device Exists
An I2C controller can talk to many chips at different addresses, but nothing on the bus itself announces what is actually connected — unlike USB or PCI, I2C has no built-in device discovery. The kernel must be told explicitly. There have historically been two ways to do this:
| Method | Status on kernel 6.x | Typical use |
|---|---|---|
Board info file (i2c_register_board_info()) |
Deprecated, legacy ARM board files only | Older non-device-tree platforms |
| Device tree node | Standard, actively used | Virtually all modern embedded Linux boards |
The Legacy Board-Info Method (For Historical Context Only)
Older kernels let a board support file fill an array of struct i2c_board_info describing each device’s driver name, address, and optional platform data, then hand that array to i2c_register_board_info() during boot. You will still see this pattern in old textbooks and very old vendor trees, but it has been superseded almost everywhere by device tree, and new drivers should not rely on it. We mention it here only so that you recognise it if you come across legacy code — every board you work with today should use device tree instead.
Declaring an I2C Device in the Device Tree
I2C devices sit as child nodes of the I2C bus (controller) node. Since I2C is an addressable bus, each child node needs only a single address cell in its reg property — there is no length or memory range involved, unlike memory-mapped platform devices.
Here is an original device tree snippet declaring one custom sensor node used by the driver we build below:
&i2c1 {
status = "okay";
ep_temp: temp-sensor@48 {
compatible = "ep,ep-tempsensor";
reg = <0x48>;
};
};
The node name temp-sensor@48 follows the usual device tree naming rule of <generic-name>@<address>, and the @48 address suffix must match the value inside reg.
Matching the Node to a Driver With of_device_id
On the driver side, you list every compatible string your driver understands inside a struct of_device_id table, then point the driver’s of_match_table at it. This is exactly the same mechanism used for platform drivers, applied here to I2C.
static const struct of_device_id ep_temp_of_match[] = {
{ .compatible = "ep,ep-tempsensor" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_temp_of_match);
The MODULE_DEVICE_TABLE(of, ...) line makes sure this compatible string is exported into the module’s metadata, so user-space tools such as modinfo and udev can trigger automatic module loading when a matching node is found.
Hands-On: An Original Device-Tree-Matched I2C Driver
Let us write ep_temp_of_demo, a small driver that only matches through device tree and reads one register using the SMBus calls from the previous lecture.
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
#include <linux/of_device.h>
#define EP_TEMP_REG 0x00
static int ep_temp_of_probe(struct i2c_client *client)
{
s32 raw;
dev_info(&client->dev, "matched via device tree node %pOF\n",
client->dev.of_node);
raw = i2c_smbus_read_word_data(client, EP_TEMP_REG);
if (raw < 0) {
dev_err(&client->dev, "read failed: %d\n", raw);
return raw;
}
dev_info(&client->dev, "temperature raw word = 0x%04x\n", raw);
return 0;
}
static const struct of_device_id ep_temp_of_match[] = {
{ .compatible = "ep,ep-tempsensor" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_temp_of_match);
static struct i2c_driver ep_temp_of_driver = {
.driver = {
.name = "ep_temp_of_demo",
.of_match_table = ep_temp_of_match,
},
.probe = ep_temp_of_probe,
};
module_i2c_driver(ep_temp_of_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Original device-tree-matched I2C driver for the free linux kernel development course");
Notice there is no i2c_device_id table here at all — this driver relies purely on the device tree match. The %pOF format specifier in dev_info() is a kernel printk extension that prints the full device tree node path, which is handy for confirming exactly which node triggered your probe.
Building, Applying the Overlay, and Testing
Compile the driver the same way as in the previous lecture, then compile the device tree snippet into an overlay:
$ dtc -@ -I dts -O dtb -o ep-tempsensor.dtbo ep-tempsensor-overlay.dts
$ sudo dtoverlay ep-tempsensor.dtbo
$ sudo insmod ep_temp_of_demo.ko
$ dmesg | tail -5
Expected kernel log:
[ 455.201122] ep_temp_of_demo 1-0048: matched via device tree node /i2c@7e804000/temp-sensor@48
[ 455.201390] ep_temp_of_demo 1-0048: temperature raw word = 0x016c
You can also confirm the match happened correctly from user space:
$ cat /sys/bus/i2c/devices/1-0048/of_node/compatible
ep,ep-tempsensor
Recap: probe() Signature and Matching Order on Kernel 6.x
As covered earlier in this series, the modern single-argument probe(struct i2c_client *client) signature is standard since kernel 6.3, and it works identically whether the device was matched through device tree, ACPI, or an i2c_device_id table. On kernel 6.x, the I2C core tries these in order when deciding whether your driver matches a device:
of_match_table)i2c_device_id table, matched by nameOur driver in this lecture only fills in step 1, which is enough for any board where the device is declared purely in device tree — the common case on virtually all modern embedded Linux hardware.
Common Mistakes to Avoid
- Forgetting
MODULE_DEVICE_TABLE(of, ...)— without it, automatic module loading from user space will not work. - Mismatching the
@48node-name suffix against the actual value inreg. - Writing a
compatiblestring without a vendor prefix (always use something like"ep,ep-tempsensor", not just"ep-tempsensor"). - Assuming an
i2c_device_idtable is required — it is optional when you only need device tree matching.
Debugging Device Tree Matching
When a driver fails to bind to its device tree node, the fastest checks are entirely from user space, without touching the driver code at all:
$ ls /sys/bus/i2c/devices/
$ cat /sys/bus/i2c/devices/1-0048/of_node/compatible
$ cat /sys/bus/i2c/devices/1-0048/modalias
$ dmesg | grep -i i2c
The modalias file shows the exact string udev uses to match a kernel module, in the form of:N<node>T<type>C<compatible>. If this file exists but your module never loads, compare its compatible value character by character against the string in your driver’s of_device_id table — a single typo, such as a missing comma after the vendor prefix, is the most common cause of a silent match failure.
Suggested Images for This Post
- A diagram-style photo of a Raspberry Pi with an overlay-loaded I2C sensor connected
- A terminal screenshot of
dtccompiling an overlay anddtoverlayapplying it - A screenshot of
/sys/bus/i2c/devices/<addr>/of_node/compatibleoutput
Frequently Asked Questions
Do I still need the old board-info method on a modern board?
No. Nearly every current embedded Linux board boots with device tree, so you should declare I2C devices as device tree nodes rather than using the deprecated board-info API.
What does the reg property contain for an I2C device node?
It contains the single-cell I2C slave address of the device, unlike memory-mapped platform devices where reg holds a base address and length pair.
Can I combine device tree matching with an i2c_device_id table?
Yes, many real drivers provide both so the same driver also works on non-device-tree systems, but device tree matching is checked first on kernel 6.x.
Why does my compatible string need a vendor prefix?
The prefix, such as ep,, avoids name collisions with other vendors’ compatible strings in the wider device tree ecosystem.
Is a device tree overlay required for every I2C device?
Not always — on many boards the device node is already baked into the base device tree, and an overlay is only needed when adding devices at runtime, such as on a Raspberry Pi.
Continue the Free Linux Device Drivers Course
Next, this free embedded systems course moves further into I2C client driver internals, building on the device tree matching you just learned.
