Of_Match_ptr and Multi-Variant Drivers- Free Linux Device Drivers Course

of_match_ptr and Multi-Variant Drivers
Free Linux Device Drivers Course — Lecture 6.11
Kernel 6.x Ready
Hands-On Driver Demo
100% Free

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 linux device drivers course
free embedded systems course
of_match_ptr macro
CONFIG_OF
per device specific data

What You Will Learn

Why CONFIG_OF can be disabled
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);
One Driver, Three Variants
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.

 

Leave a Reply

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