Platform Data vs Device Tree in Linux Drivers
Free Linux Kernel Development Course — Device Tree Chapter, Part 15 (Closing Lecture)
Every platform driver eventually has to answer one question before it can safely initialize hardware: was this device described the old way, through platform data in a board file, or the modern way, through a device tree node? Getting the platform data vs device tree check right in your Linux kernel driver is what lets the exact same binary run correctly on both legacy and device-tree boards. This lecture closes out the Device Tree chapter of our free Linux kernel development course by covering that check, a useful hybrid resource pattern, and a full recap of everything we have built.
What You Will Learn
- How to detect whether your driver was instantiated from platform data or a device tree node
- The hybrid resource method used for DMA, IRQ, and memory resources
- How to convert a legacy, hand-written platform_device into an equivalent device tree node
- A full recap of the Device Tree chapter, and what comes next in this course
Prerequisites
- All previous lectures in the Device Tree chapter (lddch6_1 through lddch6_14)
- Comfortable writing and matching an of_device_id table
- A working kernel 6.x build environment
Checking platform_data vs of_node in Your Driver
Inside probe(), the rule is simple: check dev->platform_data first. If it is non-NULL, your device was registered the legacy way, through a board configuration file, and device tree plays no role at all. If it is NULL, your device was almost certainly instantiated from a device tree node, and dev->of_node will point at the corresponding DT entry, ready for the of_property_* family of functions to parse.
| Field | Legacy Board File | Device Tree |
|---|---|---|
| dev->platform_data | Non-NULL, points to a C struct | NULL |
| dev->of_node | NULL | Non-NULL, points to the DT node |
| Resource source | struct resource array in board file | reg / interrupts properties in .dts |
| Application data | Custom struct passed via platform_data | Custom properties parsed via of_property_read_* |
The Hybrid Resource Method for DMA, IRQ, and Memory
There is a special case worth knowing about. Some drivers only need generic resources — memory ranges, interrupt lines, DMA channels — and no device-specific application data at all. For that narrow case, the kernel allows a hybrid approach: platform data declared in C can still be associated with a device tree node purely to supply those generic resources, while everything else about the device comes from the DT node itself. This is uncommon and should only be used for that specific resource-only scenario; if your driver needs custom application data, keep it entirely in the device tree using custom properties instead, as covered earlier in this chapter.
| probe(pdev) called | → | dev->platform_data != NULL ? |
| ↓ | ||
| Yes: legacy board file init | No: use dev->of_node + of_property_read_* | |
Converting a Legacy Controller to a Device Tree Node
To make this concrete, here is an original example: a small SPI-style controller that used to be registered entirely from C using a resource array, converted to its device tree equivalent. This is a fresh example written for this course, not reproduced from any book.
Legacy C registration:
// legacy board file fragment
#define EP_SPI0_BASE 0xd0090000
#define EP_SPI0_SIZE 0x1000
#define IRQ_EP_SPI0 58
static struct resource ep_spi0_resources[] = {
{
.start = EP_SPI0_BASE,
.end = EP_SPI0_BASE + EP_SPI0_SIZE - 1,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_EP_SPI0,
.end = IRQ_EP_SPI0,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device ep_spi0_device = {
.name = "ep-spi-ctrl",
.id = 0,
.resource = ep_spi0_resources,
.num_resources = ARRAY_SIZE(ep_spi0_resources),
};
Equivalent device tree node:
// ep-spi0 device tree node
spi0: spi@d0090000 {
compatible = "ep,spi-ctrl";
reg = <0xd0090000 0x1000>;
interrupt-parent = <&intc>;
interrupts = <0 58 4>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
};
Hands-On: An Original Driver That Handles Both Paths
The ep_hybrid_demo driver below checks dev->platform_data first and only falls through to device tree parsing when it is absent, printing which path it took so you can verify the behaviour directly from dmesg.
// ep_hybrid_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
struct ep_legacy_pdata {
u32 clock_hz;
};
static int ep_hybrid_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct ep_legacy_pdata *pdata = dev_get_platdata(dev);
u32 clock_hz = 0;
if (pdata) {
clock_hz = pdata->clock_hz;
dev_info(dev, "ep_hybrid_demo: using legacy platform_data, clock=%u Hz\n",
clock_hz);
} else if (dev->of_node) {
of_property_read_u32(dev->of_node, "clock-frequency", &clock_hz);
dev_info(dev, "ep_hybrid_demo: using device tree, clock=%u Hz\n",
clock_hz);
} else {
dev_err(dev, "ep_hybrid_demo: no platform_data or of_node found\n");
return -EINVAL;
}
return 0;
}
static void ep_hybrid_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "ep_hybrid_demo: removed\n");
}
static const struct of_device_id ep_hybrid_of_match[] = {
{ .compatible = "ep,spi-ctrl" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_hybrid_of_match);
static struct platform_driver ep_hybrid_driver = {
.probe = ep_hybrid_probe,
.remove = ep_hybrid_remove,
.driver = {
.name = "ep_hybrid_demo",
.of_match_table = ep_hybrid_of_match,
},
};
module_platform_driver(ep_hybrid_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demonstrates platform_data vs device tree init path selection");
Add clock-frequency = <48000000>; inside the spi0 node above, then build and test:
make -C /lib/modules/$(uname -r)/build M=$PWD modules
dtc -@ -I dts -O dtb -o ep-spi0-overlay.dtbo ep-spi0-overlay.dts
sudo dtoverlay ep-spi0-overlay.dtbo
sudo insmod ep_hybrid_demo.ko
dmesg | tail -3
Expected output on a device tree boot:
ep_hybrid_demo: using device tree, clock=48000000 Hz
Common Mistakes and Troubleshooting
| Mistake | Why It Breaks | Fix |
|---|---|---|
| Checking of_node before platform_data | Mixed hybrid setups can have both fields non-NULL | Check platform_data first, treat it as authoritative |
| Assuming platform_data is always NULL on modern boards | Some SoC vendor trees still register a few legacy devices | Never assume — always check at runtime |
| Duplicating application data in both platform_data and DT | Creates two sources of truth that can drift apart | Pick one source per device and stay consistent |
Best Practices
- Treat the hybrid platform_data-for-resources pattern as an exception, not a default design choice
- For new hardware, always describe both resources and application data entirely in the device tree
- Keep of_device_id and platform_device_id tables in sync so the same driver binds correctly regardless of which path is active
Security Considerations
Device tree overlays loaded at runtime should only come from a trusted, verified source — a malicious overlay could redefine reg or interrupts properties and cause a driver to map memory it should not have access to. Legacy platform_data structs, by contrast, are compiled directly into the kernel image and share its trust boundary.
Device Tree Chapter Recap
This closes the Device Tree chapter of the free Linux kernel development course. Across fifteen lectures we covered:
- Device tree origins, data types, and the CONFIG_OF build option
- Node naming, labels, phandles, and aliases
- The reg property and #address-cells / #size-cells rules for I2C, SPI, and memory-mapped devices
- Named resources, interrupt handling, and custom application-specific properties
- Device tree sub-nodes and of_device_id / platform_device_id matching, including mixed matching styles
- How platform_get_resource() and platform_get_irq() transparently read from device tree
- What happens inside platform_get_irq() and how of_platform_device_create_pdata() builds platform devices at boot
- Platform data versus device tree, and the resource-only hybrid pattern
free linux device drivers course
free embedded systems course
platform data vs device tree
Frequently Asked Questions
Should I check platform_data or of_node first in probe?
Always check platform_data first. Some hybrid setups populate both, and platform_data is meant to take priority when present.
Can a single driver support both platform_data and device tree boards?
Yes, that is exactly the pattern shown in this lecture — branch on dev->platform_data at the top of probe() and fall back to dev->of_node parsing otherwise.
When should I use the hybrid platform_data-for-resources method?
Only when your driver needs generic resources like DMA, IRQ, or memory and has no custom application data to parse — otherwise, describe everything in the device tree.
Is platform_data deprecated in modern Linux kernels?
It still works and some vendor code still uses it, but new drivers targeting device-tree or ACPI platforms should avoid it in favor of DT properties or ACPI device properties.
What is next after the Device Tree chapter?
The course moves on to I2C driver development, applying everything learned about device tree node matching and resource extraction to real I2C client devices.
Do I need dtc installed to test the examples in this lecture?
Yes, the device tree compiler (dtc) is required to compile the .dts overlay fragments into a loadable .dtbo binary before using dtoverlay.
Start the Next Chapter: I2C Driver Development
Continue this free Linux kernel development course with I2C client driver enumeration and configuration.
