MFD Resources and IRQ Handling- Free Linux Device Drivers Tutorial

MFD Resources and IRQ Handling

Learn how Linux MFD subdevices receive their own memory and IRQ resources, and how to retrieve them safely in kernel 6.x drivers.

Reading Time
14 min
Difficulty
Intermediate
Kernel Version
6.x

If you have been following this free Linux kernel development course, you already know that an MFD (Multi-Function Device) driver splits one physical chip into several logical subdevices. What we have not covered yet is how each of those subdevices gets its own private slice of hardware — a specific IRQ line, a register range, or a named resource — without touching the parent’s memory map directly. That is exactly what Linux MFD cell resources are for, and understanding them is essential before you write any real multi-function chip driver. In this lecture of our free embedded Linux course, we take the .resources field of struct mfd_cell apart, show how a subdevice driver pulls its resource back out with platform_get_resource_byname() and platform_get_irq_byname(), and walk through the full signature of devm_mfd_add_devices() parameter by parameter.

Keywords covered in this lecture

free linux kernel development course
free linux device drivers course
free embedded systems course
free embedded linux course
linux mfd cell resources
platform_get_irq_byname
devm_mfd_add_devices

What You Will Learn

  • Why MFD subdevices need their own .resources array instead of sharing the parent’s memory map
  • How of_compatible forces a subdevice node to be a child of the MFD node in Device Tree
  • How to retrieve a named resource with platform_get_resource_byname() and platform_get_irq_byname()
  • Every parameter of the devm_mfd_add_devices() function signature
  • How to build an original ep_iox_demo driver that passes a named IRQ resource to a subdevice

Prerequisites

  • MFD subsystem basics — mfd_cell struct and devm_mfd_add_devices() (covered earlier in this MFD chapter)
  • MFD Device Tree binding — compatible-string and reg-based cell matching (covered earlier in this MFD chapter)
  • Comfort with platform_driver basics from our free Linux device drivers course
  • A Linux kernel 6.x build environment (native or QEMU) with kernel headers installed

Recap: Where Resources Sit in mfd_cell

Every subdevice the MFD core creates is a real platform_device. Two fields of struct mfd_cell decide what hardware that platform device is told about:

struct mfd_cell {
    const char *name;
    int id;
    const char *of_compatible;

    int num_resources;
    const struct resource *resources;
    /* ... other fields ... */
};

resources points to an array of struct resource entries (IRQ lines, memory ranges, or IO ports) that belong only to that one subdevice. num_resources tells the core how many entries are in the array. Nothing here is copied from platform_data — resources are a completely separate channel, and the kernel expects you to give each one a .name so the subdevice driver can ask for it by name instead of by array index.

MFD Cell to Subdevice Resource Flow
Parent MFD Driver (chip probe)
↓ builds mfd_cell[] with .resources
devm_mfd_add_devices()
↓ creates one platform_device per cell
Subdevice A
(no resources)
Subdevice B
resources = [ BTN_IRQ ]
platform_get_irq_byname(pdev, “BTN_IRQ”)

The of_compatible Requirement

When a cell sets .of_compatible, the MFD core expects a matching Device Tree node for that subdevice to exist as a child node of the parent MFD device. This is a hard rule in kernel 6.x — if the child node is a sibling instead of a child, of_compatible matching silently fails and the subdevice falls back to matching by .name only. We covered the full DT layout in the previous lecture of this MFD chapter; the short version is: keep every subdevice node nested inside the parent chip’s node, and give each one its own compatible string that matches the cell.

Retrieving Resources in the Subdevice Driver

Once devm_mfd_add_devices() has created the child platform_device, the subdevice driver’s own probe() reads its resources back out using the standard platform resource API — never by reaching into mfd_cell directly.

/* Memory-mapped register range, looked up by name */
struct resource *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "REGS");

/* IRQ, looked up by name */
int irq = platform_get_irq_byname(pdev, "BTN_IRQ");
if (irq < 0)
    return irq;

/* IRQ, looked up by array index instead of name */
int irq0 = platform_get_irq(pdev, 0);

Prefer the _byname() variants whenever a cell defines more than one resource of the same type. Index-based lookup breaks silently the moment someone reorders the resources array in a later driver revision; name-based lookup does not.

Resource Lookup Function Comparison

Function Looks up by Typical resource type
platform_get_resource() type + index MEM / IO / IRQ
platform_get_resource_byname() type + name MEM / IO / IRQ
platform_get_irq() index only IRQ
platform_get_irq_byname() name only IRQ

The devm_mfd_add_devices() Signature

int devm_mfd_add_devices(struct device *dev,
                          int id,
                          const struct mfd_cell *cells,
                          int n_devs,
                          struct resource *mem_base,
                          int irq_base,
                          struct irq_domain *domain);
Parameter Meaning
dev Parent device; devres ties subdevice cleanup to its lifetime
id Instance ID for the whole cell set, usually PLATFORM_DEVID_AUTO
cells Pointer to your mfd_cell array
n_devs Number of cells, normally ARRAY_SIZE(cells)
mem_base Optional base resource added to every cell’s own resources; NULL if unused
irq_base Optional base IRQ number added to every cell’s IRQ resources; 0 if unused
domain Optional irq_domain for translating cell IRQs through an irqchip; NULL if the chip has no on-chip irqchip

For most modern drivers that already resolve their IRQs through Device Tree or an irq_domain elsewhere, mem_base, irq_base, and domain are simply NULL/0/NULL, exactly as in the example below.

Hands-On Example: ep_iox_demo Driver

The following original demo models a small IO-expander chip with two logical subdevices: a buzzer subdevice that needs no resources, and a button subdevice that needs one named IRQ resource.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/interrupt.h>

#define EP_IOX_DEMO_IRQ 60   /* pick a free IRQ on your test system */

static struct resource ep_button_res[] = {
    {
        .name  = "BTN_IRQ",
        .start = EP_IOX_DEMO_IRQ,
        .end   = EP_IOX_DEMO_IRQ,
        .flags = IORESOURCE_IRQ,
    },
};

static const struct mfd_cell ep_iox_cells[] = {
    {
        .name          = "ep-iox-buzzer",
        .of_compatible = "ep,iox-buzzer",
    },
    {
        .name          = "ep-iox-button",
        .of_compatible = "ep,iox-button",
        .resources     = ep_button_res,
        .num_resources = ARRAY_SIZE(ep_button_res),
    },
};

static int ep_iox_probe(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "ep_iox: parent probed, adding %zu cells\n",
             ARRAY_SIZE(ep_iox_cells));

    return devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
                                 ep_iox_cells, ARRAY_SIZE(ep_iox_cells),
                                 NULL, 0, NULL);
}

static struct platform_driver ep_iox_driver = {
    .driver = { .name = "ep-iox" },
    .probe  = ep_iox_probe,
};

static struct platform_device *ep_iox_pdev;

static int __init ep_iox_init(void)
{
    ep_iox_pdev = platform_device_register_simple("ep-iox", -1, NULL, 0);
    if (IS_ERR(ep_iox_pdev))
        return PTR_ERR(ep_iox_pdev);

    return platform_driver_register(&ep_iox_driver);
}

static void __exit ep_iox_exit(void)
{
    platform_driver_unregister(&ep_iox_driver);
    platform_device_unregister(ep_iox_pdev);
}

module_init(ep_iox_init);
module_exit(ep_iox_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala MFD resource demo");

The button subdevice driver retrieves its IRQ by name and requests it:

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>

static irqreturn_t ep_iox_button_irq(int irq, void *data)
{
    dev_info((struct device *)data, "ep-iox-button: irq %d fired\n", irq);
    return IRQ_HANDLED;
}

static int ep_iox_button_probe(struct platform_device *pdev)
{
    int irq, ret;

    irq = platform_get_irq_byname(pdev, "BTN_IRQ");
    if (irq < 0) { dev_err(&pdev->dev, "BTN_IRQ resource missing: %d\n", irq);
        return irq;
    }

    ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
                                     ep_iox_button_irq,
                                     IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                                     "ep-iox-button", &pdev->dev);
    if (ret) {
        dev_err(&pdev->dev, "irq request failed: %d\n", ret);
        return ret;
    }

    dev_info(&pdev->dev, "ep-iox-button: probed, irq=%d\n", irq);
    return 0;
}

static struct platform_driver ep_iox_button_driver = {
    .driver = { .name = "ep-iox-button" },
    .probe  = ep_iox_button_probe,
};
module_platform_driver(ep_iox_button_driver);
MODULE_LICENSE("GPL");

Build and Run

obj-m += ep_iox.o ep_iox_button.o
make -C /lib/modules/$(uname -r)/build M=$PWD modules

sudo insmod ep_iox.ko
sudo insmod ep_iox_button.ko
dmesg | tail -n 10

Expected dmesg Output

ep_iox: parent probed, adding 2 cells
ep-iox-buzzer: probed
ep-iox-button: probed, irq=60

Common Mistakes to Avoid

  • Forgetting .name on a resource entry — name-based lookup then always fails
  • Reading resources straight from mfd_cell in the subdevice driver instead of using platform_get_resource_byname()
  • Placing an of_compatible subdevice node as a sibling of the parent instead of a child in the Device Tree
  • Mixing up platform_get_irq() index order after adding a new resource to the array

Best Practices

  • Always name IRQ and memory resources, even if you currently have only one
  • Keep resource arrays static const next to the cell array that references them
  • Let devres (the devm_ variant) manage cleanup instead of manual free/unregister paths
  • Validate every returned IRQ/resource before using it — a negative return means the resource was not found

Frequently Asked Questions

Why does a subdevice need its own resources instead of using the parent’s?

Each subdevice is treated as an independent platform device by the driver model. It should only see the hardware it owns, so the MFD core scopes resources per cell rather than exposing the parent’s full resource set.

Can a single mfd_cell have both memory and IRQ resources?

Yes. The resources array can mix IORESOURCE_MEM and IORESOURCE_IRQ entries; each is retrieved with the matching type in platform_get_resource_byname().

What happens if num_resources does not match the array size?

The core reads exactly num_resources entries, so a mismatch either drops real resources or reads past the array. Always use ARRAY_SIZE() instead of a hardcoded number.

Is mem_base in devm_mfd_add_devices() still commonly used?

Rarely in modern drivers. It was more common on older SoCs without Device Tree, where a base address had to be added to every cell’s offsets manually. Most kernel 6.x drivers pass NULL.

Do I need irq_domain if my chip has no internal interrupt controller?

No. Pass NULL for domain. irq_domain is only needed when the chip itself demultiplexes several interrupt sources behind one physical IRQ line.

What is the difference between platform_get_irq() and platform_get_irq_byname()?

platform_get_irq() looks up by array index, so it breaks if resource order changes later. platform_get_irq_byname() looks up by the resource’s .name field and is unaffected by ordering.

 

Continue the Free Linux Kernel Development Course

Next in this MFD chapter, we connect subdevice resources to a real syscon-backed chip.

 

Leave a Reply

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