| ← PREV_LEC | NEXT_LEC → |
Every device in a Device Tree needs an address so the kernel knows exactly which piece of hardware a node is describing. The device tree reg property is how that address gets written down, and it is one of the most misunderstood properties for anyone starting out with embedded Linux. In this lecture of our free embedded Linux course, we break the device tree reg property down into something you can actually reason about, then prove it with an original driver running on a current 6.x kernel.
address-cells size-cells
i2c device tree addressing
spi device tree addressing
free linux kernel development course
What You Will Learn
- Why the device tree reg property exists and what problem it solves
- How
#address-cellsand#size-cellscontrol howregis interpreted - Why a device’s addressing rules come from its parent node, not itself
- How I2C devices are addressed on the bus using
reg - How SPI devices use
regto select a chip-select line, not a memory address - How to write and test an original I2C driver that reads its own
reg-assigned address on a modern kernel
Prerequisites
- Basic Device Tree concepts (nodes, properties, compatible strings) — covered in Part 1 of this chapter
- Node naming and labels — covered in Part 2
- Phandles and aliases — covered in Part 3
- A Linux 6.x kernel build environment (native or cross-compiled) with a board or QEMU target that supports Device Tree overlays
Why Devices Need an Addressing Scheme
A Device Tree can describe hundreds of devices sitting on many different buses — I2C, SPI, memory-mapped platform buses, and more. Each of those buses has a completely different idea of what an “address” even means. An I2C device has a 7-bit slave address. An SPI device does not have an address at all — it has a chip-select line. A memory-mapped device has a physical base address and a size. The Device Tree needs one generic mechanism flexible enough to describe all three, and that mechanism is the reg property, controlled by two companion properties: #address-cells and #size-cells.
| Parent bus node defines #address-cells defines #size-cells |
→ | Child device node reg = <address size> read using parent’s rules |
Notice the wording above: the rules live on the parent. A node’s own #address-cells and #size-cells say nothing about how to read that node’s own reg — they only affect how the kernel should read the reg of that node’s children. This single detail trips up more beginners than any other part of Device Tree, so read it twice before moving on.
Understanding #address-cells and #size-cells
Both properties are counted in 32-bit “cells”. A cell is just a 4-byte chunk of the reg value.
| Property | Meaning |
| #address-cells | Number of 32-bit cells used for the address part of a child’s reg entry |
| #size-cells | Number of 32-bit cells used for the size part of a child’s reg entry. Set to 0 when size has no meaning, such as an I2C slave address |
A general reg entry looks like reg = <address size>, and a node can list several of these tuples if it occupies more than one address range. The exact number of cells used for each half comes entirely from the parent’s #address-cells and #size-cells.
I2C and SPI Addressing: Non-Memory-Mapped Devices
I2C and SPI devices are not directly visible to the CPU. The CPU cannot read or write them with a load/store instruction — instead, the bus controller driver performs the transaction on the CPU’s behalf. Because there is no real memory region involved, these buses set #size-cells = 0, and every child’s reg becomes a single-cell value.
I2C: reg Is the Slave Address
For an I2C device, the single cell in reg is the 7-bit (or 10-bit) slave address on the bus. Here is an original example describing a humidity sensor and a real-time clock on the same I2C controller:
&i2c2 {
status = "okay";
ep_humidity: humidity-sensor@40 {
compatible = "ep,ep-hum100";
reg = <0x40>;
};
ep_rtc: rtc@51 {
compatible = "ep,ep-rtc51";
reg = <0x51>;
};
};
Because the parent I2C controller node sets #address-cells = 1 and #size-cells = 0, both children get exactly one cell in reg, and that cell is read as the device address on the bus — 0x40 and 0x51 respectively.
SPI: reg Is a Chip-Select Index, Not an Address
SPI has no addressing at the protocol level at all. What looks like an “address” in an SPI child node is actually the index into the controller’s chip-select list. Here is an original example for a dual-channel ADC on an SPI bus with two chip-select lines:
&spi1 {
status = "okay";
cs-gpios = <&gpio3 12 0>, <&gpio3 13 0>;
ep_adc0: adc@0 {
compatible = "ep,ep-adc12";
reg = <0>;
spi-max-frequency = <2000000>;
};
ep_adc1: adc@1 {
compatible = "ep,ep-adc12";
reg = <1>;
spi-max-frequency = <2000000>;
};
};
ep_adc1 has reg = <1>, which selects index 1 of cs-gpios, meaning GPIO line 13 on gpio3 is toggled as its chip-select. The number in reg never becomes a memory address for SPI devices — it only ever selects a chip-select line.
| Bus | #size-cells | reg means |
| I2C | 0 | 7/10-bit slave address |
| SPI | 0 | Index into cs-gpios / native chip-select |
Hands-On: Reading the reg-Assigned Address in a Driver
Let’s write an original I2C client driver, ep_i2c_addr_demo, that binds to ep_humidity above and simply prints the address that Device Tree assigned it. This confirms in practice what the device tree reg property is actually doing under the hood.
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/of.h>
static int ep_i2c_addr_probe(struct i2c_client *client)
{
dev_info(&client->dev,
"ep_i2c_addr_demo: bound to device at reg address 0x%02x\n",
client->addr);
return 0;
}
static void ep_i2c_addr_remove(struct i2c_client *client)
{
dev_info(&client->dev, "ep_i2c_addr_demo: removed\n");
}
static const struct of_device_id ep_i2c_addr_of_match[] = {
{ .compatible = "ep,ep-hum100" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_i2c_addr_of_match);
static struct i2c_driver ep_i2c_addr_driver = {
.driver = {
.name = "ep_i2c_addr_demo",
.of_match_table = ep_i2c_addr_of_match,
},
.probe = ep_i2c_addr_probe,
.remove = ep_i2c_addr_remove,
};
module_i2c_driver(ep_i2c_addr_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala reg property demo driver");
Note that on kernel 6.3 and later, the I2C probe callback signature was cleaned up to take a single struct i2c_client * argument (the older const struct i2c_device_id *id parameter was dropped from the primary probe path). The example above already uses the current signature so it builds cleanly on the latest kernels.
Build and Load
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_i2c_addr_demo.ko
$ dmesg | tail -n 3
Expected Output
[ 1523.881204] ep_i2c_addr_demo: bound to device at reg address 0x40
The value 0x40 printed by the driver is exactly the value written in reg = <0x40>; in the Device Tree source. The kernel’s I2C core parsed the node using the parent bus’s addressing rules and populated client->addr for us before probe() ever ran.
Common Mistakes and Troubleshooting
- Forgetting #size-cells = 0 on I2C/SPI buses: if left at the default, the kernel expects a size field in every child reg entry and node parsing fails.
- Assuming reg on an SPI node is a memory address: it is a chip-select index. Confusing the two leads to wiring the wrong GPIO to the wrong device.
- Looking at the wrong node for #address-cells/#size-cells: always check the immediate parent, never the node’s own values.
- Duplicate node addresses: two children of the same parent with identical unit-addresses will fail the dtc compile with a duplicate node name error.
Best Practices
- Always match the node’s unit-address (the part after @) to the first cell of its own reg property, for readability and dtc validation.
- Keep #address-cells and #size-cells declarations close to the bus node so they are easy to find when auditing a .dts file.
- Use
dtc -I fs /sys/firmware/devicetree/base -O dtsto dump the live tree on your target and confirm your reg values actually reached the running kernel.
Key Takeaways
- The device tree reg property is a generic addressing mechanism whose meaning depends entirely on the bus a device sits on
- #address-cells and #size-cells on a bus node govern how that bus’s children are addressed, not the bus node itself
- I2C reg is a slave address; SPI reg is a chip-select index — neither is a memory address
- #size-cells = 0 is the signal that a bus has no real memory region to describe
Conclusion
The device tree reg property looks intimidating the first time you see a raw .dts file, but it boils down to one rule: ask the parent how many cells to read, then read them. Once that clicks, I2C, SPI, and memory-mapped addressing all become variations on the same idea. In the next part of this free Linux device drivers course, we extend this to platform devices sitting on a simple memory-mapped bus, where reg finally does describe a real physical address and size.
Frequently Asked Questions
What is the device tree reg property used for?
It describes the address (and, where relevant, size) of a device on whichever bus it sits on — I2C slave address, SPI chip-select index, or a memory-mapped base address and length, depending on the parent bus.
Why is #size-cells set to 0 for I2C and SPI buses?
Because I2C and SPI devices do not occupy a memory region that the CPU can read directly, there is no “size” to describe, so the size field is omitted entirely.
Does a node’s own #address-cells affect its own reg property?
No. A node’s #address-cells and #size-cells only control how its children’s reg properties are interpreted, never its own.
Is the reg value in an SPI node a memory address?
No. It is an index into the controller’s chip-select list, matched against properties like cs-gpios on the parent SPI controller node.
Can a device have more than one reg tuple?
Yes. A node can list multiple address/size tuples in reg when a single device occupies more than one address range.
How do I confirm the reg value the kernel actually parsed?
Use client->addr in an I2C driver’s probe function, or dump the live tree with dtc -I fs /sys/firmware/devicetree/base -O dts on the target board.
Where can I practice device tree reg property concepts for free?
This lecture is part of EmbeddedPathashala’s free Linux kernel development course, which includes original driver examples for every concept covered.
Continue the Free Linux Kernel Development Course
Next up: platform device addressing under simple-bus, and an introduction to handling device resources.
| ← PREV_LEC | NEXT_LEC → |
