Device Tree Platform Addressing Guide- Free Linux Device Drivers Course

Device Tree Platform Addressing Guide
Free Linux Kernel Development Course — Chapter 6, Part 5
Kernel 6.x Ready
Original Driver Code
Free Linux Device Drivers Course
← PREV_LEC NEXT_LEC →

Device tree platform addressing covers the one case where the reg property finally means exactly what it looks like: a real, CPU-visible memory address and a size. In this lecture of our free embedded Linux course we cover how simple-bus nodes describe memory-mapped platform devices, write an original driver that reads that address, and open the door to the next topic — pulling structured resources like interrupts and clocks out of a device node.

Keywords covered in this lecture
device tree platform addressing
simple-bus device tree
memory mapped device reg property
device tree resources
free linux kernel development course

What You Will Learn

  • How device tree platform addressing differs from I2C/SPI addressing
  • What the simple-bus compatible string tells the kernel
  • How to read a memory-mapped reg tuple as base address and length
  • How to write an original platform driver that maps its Device Tree address with of_address_to_resource()
  • What a “resource” actually means for a device, as a preview of the next lecture

Prerequisites

  • Device tree reg property, #address-cells, #size-cells — covered in Part 4 of this chapter
  • Platform Device Drivers chapter (probe/remove, platform_driver_register) — covered earlier in this course
  • A Linux 6.x kernel build environment with Device Tree overlay support

Memory-Mapped Devices and the simple-bus Node

Unlike I2C or SPI peripherals, a memory-mapped platform device sits directly on the system’s address bus — the CPU can read and write its registers with ordinary load/store instructions, no bus controller driver required. Device Tree groups this kind of device under a parent node with compatible = "simple-bus". That single string tells the kernel: “walk my children and register each one as a plain platform device — no special bus driver needed.”

Memory-Mapped reg Tuple
reg = < base address
1st cell(s)
length
2nd cell(s)
>;

Here, both halves of the tuple carry real meaning: the first cells give the physical base address of the device’s register block, and the second cells give the number of bytes that block occupies. The number of cells used on each side still comes from the parent’s #address-cells and #size-cells, exactly as in the previous lecture — the difference is that #size-cells is no longer zero, because there really is a size to describe.

An Original simple-bus Example

Below is an original, simplified example modelling a memory-mapped watchdog-timer-like peripheral sitting under a simple-bus node:

soc {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "simple-bus";

    ep_periph_bus: peripherals@40000000 {
        compatible = "ep,periph-bus", "simple-bus";
        #address-cells = <1>;
        #size-cells = <1>;
        reg = <0x40000000 0x100000>;
        ranges;

        ep_wdt: watchdog@40010000 {
            compatible = "ep,ep-wdt1";
            reg = <0x40010000 0x1000>;
            status = "okay";
        };
    };
};

Reading this from the outside in: soc sets one cell each for address and size. Its child peripherals@40000000 inherits that rule, so its own reg is read as base 0x40000000, length 0x100000. Because that child is itself simple-bus-compatible and re-declares #address-cells/#size-cells, its child ep_wdt is addressed the same way: base 0x40010000, length 0x1000.

Hands-On: Reading a Memory-Mapped reg in a Driver

Let’s write an original platform driver, ep_simplebus_demo, that binds to ep_wdt and prints the base address and size the kernel resolved from Device Tree, using of_address_to_resource():

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/of_address.h>

static int ep_simplebus_probe(struct platform_device *pdev)
{
    struct resource res;
    int ret;

    ret = of_address_to_resource(pdev->dev.of_node, 0, &res);
    if (ret) {
        dev_err(&pdev->dev, "ep_simplebus_demo: failed to resolve reg\n");
        return ret;
    }

    dev_info(&pdev->dev,
             "ep_simplebus_demo: base=0x%llx size=0x%llx\n",
             (unsigned long long)res.start,
             (unsigned long long)resource_size(&res));
    return 0;
}

static void ep_simplebus_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_simplebus_demo: removed\n");
}

static const struct of_device_id ep_simplebus_of_match[] = {
    { .compatible = "ep,ep-wdt1" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_simplebus_of_match);

static struct platform_driver ep_simplebus_driver = {
    .probe = ep_simplebus_probe,
    .remove = ep_simplebus_remove,
    .driver = {
        .name = "ep_simplebus_demo",
        .of_match_table = ep_simplebus_of_match,
    },
};
module_platform_driver(ep_simplebus_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala simple-bus reg demo driver");

The remove() callback above already uses the modern void-returning signature that became standard from kernel 6.11 onward, so this driver needs no changes on the latest LTS trees.

Build and Load

$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_simplebus_demo.ko
$ dmesg | tail -n 3

Expected Output

[ 2011.442017] ep_simplebus_demo: base=0x40010000 size=0x1000

Compare this against the reg = <0x40010000 0x1000>; line in the Device Tree source — of_address_to_resource() did the cell-counting arithmetic for us and handed back a ready-to-use struct resource.

From reg to Resources: What Comes Next

A memory-mapped reg range is only one piece of what a device needs to function. A real peripheral node in Device Tree usually also lists an interrupt line, one or more clocks, and sometimes DMA channels — each described with its own property and each requiring its own kernel API to extract. Collectively, the kernel treats all of these — memory regions, IRQ lines, DMA channels — as device resources. The next lecture in this free Linux kernel development course walks through platform_get_resource() and friends, showing how a driver pulls a fully-populated node — reg, interrupts, clocks, and DMA — into working code.

What Counts as a Device Resource
Resource type Device Tree property
Memory region reg
Interrupt line interrupts / interrupts-extended
Clock clocks / clock-names
DMA channel dmas / dma-names

Common Mistakes and Troubleshooting

  • Forgetting the ranges property on a bridging simple-bus node: without it, address translation between parent and child address spaces can be interpreted incorrectly.
  • Overlapping reg regions between sibling devices: dtc will not always catch this; verify base+size math manually when adding new peripherals.
  • Using of_property_read_u32 instead of of_address_to_resource for memory-mapped devices: the former does not apply address translation through parent ranges, which can silently produce the wrong base address on multi-level buses.

Best Practices

  • Prefer of_address_to_resource() or the higher-level platform_get_resource() over manually parsing reg with of_property_read_u32_array.
  • Keep #address-cells/#size-cells consistent across sibling simple-bus nodes unless there is a real addressing reason not to.
  • Document the register block size in comments next to each reg entry in larger .dts files — it saves time during code review.

Key Takeaways

  • Device tree platform addressing is the one case where reg holds a real, CPU-visible base address and size
  • The compatible = “simple-bus” string tells the kernel to register children as ordinary platform devices
  • of_address_to_resource() correctly resolves a memory-mapped reg tuple into a struct resource
  • reg is just one of several device resources — interrupts, clocks, and DMA channels round out a complete peripheral description

Conclusion

With I2C, SPI, and now memory-mapped addressing covered, you have the full picture of how device tree platform addressing and the reg property work across every bus type the kernel supports. The next lecture builds directly on this by showing how a single, fully-described device node — carrying reg, interrupts, clocks, and DMA together — gets turned into a working driver using the kernel’s resource APIs.

Frequently Asked Questions

What does compatible = “simple-bus” mean in device tree?

It tells the kernel that the children of this node are ordinary memory-mapped platform devices with no dedicated bus controller driver needed to access them.

What does the reg property mean for a memory-mapped device?

It gives the physical base address and the size, in bytes, of that device’s register block — both are directly usable by the CPU.

Why use of_address_to_resource instead of reading reg manually?

It correctly applies address translation through any parent ranges properties, which manual parsing with of_property_read_u32_array does not do.

What is a device resource in the Linux kernel?

A generic term for anything a device needs to operate that Device Tree describes — memory regions, interrupt lines, clocks, and DMA channels are all resources.

Does every platform device need a ranges property?

Only bridging nodes that translate child addresses into a different parent address space need ranges; a plain pass-through simple-bus node can use an empty ranges property.

Where can I learn how to extract interrupts and clocks next?

The following lecture in this free Linux kernel development course covers platform_get_resource and related APIs for pulling interrupts, clocks, and DMA channels out of a device node.

 

Continue the Free Linux Kernel Development Course

Next up: handling device resources — interrupts, clocks, and DMA channels — with platform_get_resource().

← PREV_LEC NEXT_LEC →

Leave a Reply

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