This lecture in our free Linux kernel development course covers two closely related survival skills for driver authors: writing device tree code that still compiles cleanly when of_match_ptr and CONFIG_OF are involved, and using per-device configuration data to drive several hardware variants from a single probe function. Both patterns show up constantly in real mainline drivers, making them essential for anyone following a free embedded Linux course toward professional driver work.
free embedded systems course
of_match_ptr macro
CONFIG_OF
per device specific data
What You Will Learn
of_match_ptr() vs #ifdef CONFIG_OF
Per-device data structs
One probe function, many variants
device_get_match_data() on modern kernels
Prerequisites
This lecture builds directly on lecture 6.10 (of_device_id matching, MODULE_DEVICE_TABLE, of_match_device). Read that first if you have not already, since the driver example below extends the same pattern.
Why Device Tree Support Might Be Disabled
Not every kernel build enables device tree support. Some x86 systems, and some embedded builds targeting ACPI or legacy board files, compile with CONFIG_OF unset. A driver that unconditionally references an of_match_table still compiles fine in that case — the struct type is always defined — but referencing device-tree-only helper functions without guarding them can pull in dead weight or, in older kernels, fail to link.
The Old Way: #ifdef CONFIG_OF
Older driver code wrapped the entire match table in a preprocessor conditional:
#ifdef CONFIG_OF
static const struct of_device_id ep_gpio_dt_ids[] = {
{ .compatible = "ep,gpio-ctrl", },
{ }
};
#endif
This works, but it means every place that references ep_gpio_dt_ids also needs its own #ifdef, which clutters the driver and is easy to get wrong.
The Modern Way: of_match_ptr()
of_match_ptr() is a small macro defined in include/linux/of.h that removes the need for scattered #ifdef blocks:
#define of_match_ptr(_ptr) (_ptr) /* CONFIG_OF enabled */
#define of_match_ptr(_ptr) NULL /* CONFIG_OF disabled */
You keep the of_device_id array unconditional and only wrap the single place it’s assigned:
static const struct of_device_id ep_gpio_dt_ids[] = {
{ .compatible = "ep,gpio-ctrl", },
{ }
};
MODULE_DEVICE_TABLE(of, ep_gpio_dt_ids);
static struct platform_driver ep_gpio_driver = {
.probe = ep_gpio_probe,
.remove = ep_gpio_remove,
.driver = {
.name = "ep-gpio",
.of_match_table = of_match_ptr(ep_gpio_dt_ids),
},
};
module_platform_driver(ep_gpio_driver);
When CONFIG_OF is disabled, of_match_table simply becomes NULL and the platform core skips device tree matching for this driver, while id_table or name-based matching keeps working normally.
Supporting Multiple Hardware Variants With One Driver
A very common real-world situation: your team ships three related chips that differ only in a handful of register offsets or feature bits, but you want one kernel module and one probe function. The pattern is to attach a small const struct to each of_device_id entry’s data field.
Start by defining the per-variant configuration struct and an enum to identify each type:
enum ep_temp_variant {
EP_TEMP_BASIC,
EP_TEMP_PLUS,
EP_TEMP_PRO,
};
struct ep_temp_config {
enum ep_temp_variant variant;
u32 reg_offset;
bool has_alarm;
};
static const struct ep_temp_config ep_temp_basic_cfg = {
.variant = EP_TEMP_BASIC,
.reg_offset = 0x00,
.has_alarm = false,
};
static const struct ep_temp_config ep_temp_plus_cfg = {
.variant = EP_TEMP_PLUS,
.reg_offset = 0x10,
.has_alarm = true,
};
static const struct ep_temp_config ep_temp_pro_cfg = {
.variant = EP_TEMP_PRO,
.reg_offset = 0x20,
.has_alarm = true,
};
Now attach each config to its matching compatible string:
static const struct of_device_id ep_temp_dt_ids[] = {
{ .compatible = "ep,temp-basic", .data = &ep_temp_basic_cfg },
{ .compatible = "ep,temp-plus", .data = &ep_temp_plus_cfg },
{ .compatible = "ep,temp-pro", .data = &ep_temp_pro_cfg },
{ }
};
MODULE_DEVICE_TABLE(of, ep_temp_dt_ids);
Retrieving the Config Inside probe()
Two approaches work on modern kernels. The explicit one uses of_match_device(), exactly as in the previous lecture:
static int ep_temp_probe(struct platform_device *pdev)
{
const struct of_device_id *match;
const struct ep_temp_config *cfg;
match = of_match_device(ep_temp_dt_ids, &pdev->dev);
if (!match)
return -ENODEV;
cfg = match->data;
dev_info(&pdev->dev, "variant=%d reg_offset=0x%x alarm=%d\n",
cfg->variant, cfg->reg_offset, cfg->has_alarm);
return 0;
}
The shorter, kernel 6.x-friendly form uses device_get_match_data(), which works across both device tree and ACPI matching without you having to pick the right helper function yourself:
#include <linux/property.h>
static int ep_temp_probe(struct platform_device *pdev)
{
const struct ep_temp_config *cfg;
cfg = device_get_match_data(&pdev->dev);
if (!cfg)
return -ENODEV;
dev_info(&pdev->dev, "variant=%d reg_offset=0x%x alarm=%d\n",
cfg->variant, cfg->reg_offset, cfg->has_alarm);
return 0;
}
static struct platform_driver ep_temp_driver = {
.probe = ep_temp_probe,
.driver = {
.name = "ep-temp",
.of_match_table = of_match_ptr(ep_temp_dt_ids),
},
};
module_platform_driver(ep_temp_driver);
| Compatible String | Config Struct | reg_offset | has_alarm |
| ep,temp-basic | ep_temp_basic_cfg | 0x00 | false |
| ep,temp-plus | ep_temp_plus_cfg | 0x10 | true |
| ep,temp-pro | ep_temp_pro_cfg | 0x20 | true |
Trying It: Build, Load, Check dmesg
$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_temp.ko
$ dmesg | tail -3
[ 120.512] ep-temp ep_temp0: variant=2 reg_offset=0x20 alarm=1
$ sudo rmmod ep_temp
The expected output confirms the probe read back the correct config struct for whichever compatible string matched in your device tree overlay.
Common Mistakes
| Mistake | Fix |
| Wrapping the whole of_device_id array in #ifdef instead of using of_match_ptr() | Only wrap the of_match_table assignment with of_match_ptr() |
| Forgetting device_get_match_data() returns NULL if nothing matched | Always check the returned pointer before dereferencing |
| Storing config structs as non-const | Mark per-variant config structs const so they land in read-only memory |
Best Practices
- Prefer device_get_match_data() over of_match_device() in new code targeting kernel 6.x, since it also covers ACPI-matched devices.
- Keep of_match_ptr() at the single assignment site, not scattered across the file.
- Group per-variant config structs together near the top of the driver for easy comparison.
Real-World Use Case
Touchscreen controller drivers and PMIC drivers commonly ship one kernel module supporting five or more silicon revisions this way — a single probe function, a handful of const config structs, and a compatible string per revision in the of_device_id table.
Summary / Key Takeaways
- of_match_ptr() replaces scattered #ifdef CONFIG_OF blocks with one macro at the assignment site.
- When CONFIG_OF is disabled, of_match_ptr() resolves to NULL, safely skipping device tree matching.
- Per-device configuration data lets one probe function serve several hardware variants.
- device_get_match_data() is the modern, matching-method-agnostic way to retrieve that data.
Frequently Asked Questions
Do I still need MODULE_DEVICE_TABLE if I use of_match_ptr()?
Yes, MODULE_DEVICE_TABLE is independent of of_match_ptr() and is still required for module auto-loading based on the compatible strings.
Does of_match_ptr() have any runtime cost?
No, it is a compile-time macro that either passes the pointer through or substitutes NULL, with no runtime branching involved.
Can device_get_match_data() be used with id_table matching too?
No, device_get_match_data() covers device tree and ACPI matching; id_table-based platform_device_id matching is retrieved separately with platform_get_device_id().
What happens if I forget to add the sentinel entry when using per-device data?
The kernel’s match loop can read past the end of the array, which is undefined behavior; always terminate the array with an empty {} entry.
Should config structs referenced by of_device_id.data be static const?
Yes, marking them static const keeps them in read-only memory and signals they must not be modified after boot.
Continue the Free Linux Kernel Development Course
Explore more free embedded Linux, kernel driver, and device tree lectures in this ongoing series.
