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.
14 min
Intermediate
6.x
.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 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
.resourcesarray instead of sharing the parent’s memory map - How
of_compatibleforces 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()andplatform_get_irq_byname() - Every parameter of the
devm_mfd_add_devices()function signature - How to build an original
ep_iox_demodriver that passes a named IRQ resource to a subdevice
Prerequisites
- MFD subsystem basics —
mfd_cellstruct anddevm_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_driverbasics 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.
(no resources)
resources = [ 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
.nameon a resource entry — name-based lookup then always fails - Reading resources straight from
mfd_cellin the subdevice driver instead of usingplatform_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 constnext 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.
