free linux kernel development course
free linux device drivers course
free embedded systems course
mfd_cell struct
The Linux MFD subsystem is the kernel framework used to describe a single physical chip that exposes several unrelated functions — for example a PMIC that bundles a regulator, an RTC, and a set of GPIOs on one I2C address. If you are following this free Linux kernel development course, this lecture is where character-device and platform-device knowledge from earlier chapters comes together to explain how one hardware IC can register itself with several completely different kernel subsystems at once. By the end of this lecture you will be able to read, and write, real MFD core drivers on a modern 6.x kernel.
What You Will Learn
- What a multi-function device (MFD) is and why it needs its own subsystem
- The role of
struct mfd_celland its most important fields - How
mfd_add_devices()anddevm_mfd_add_devices()register sub-devices - How to write, build, and load an original MFD core driver with two cells
- Common mistakes and best practices when designing an MFD driver
Prerequisites
- Comfortable with C and pointers to structures
- Completed the platform device driver lectures of this free linux device drivers course
- A Linux kernel 6.x source tree, or a Raspberry Pi / QEMU virtual board to test loadable modules
What Is a Multi-Function Device?
A multi-function device is a single piece of silicon that physically looks like one chip, but logically behaves like several independent devices. A power-management IC is the textbook example: it might contain a battery charger, a set of voltage regulators, a real-time clock, and a handful of GPIO lines, all reachable through one shared I2C or SPI bus, and often through one shared register map.
Without a dedicated subsystem, every driver author would end up re-inventing the same plumbing: who talks to the bus first, how IRQs from the chip are demultiplexed to the right sub-function, and how each sub-function gets its own platform_device so that the existing regulator, rtc, or gpio subsystems can bind their normal drivers to it. The MFD subsystem, implemented in drivers/mfd/mfd-core.c, solves exactly this problem.
The mfd_cell Structure
Each sub-function of an MFD chip is described to the kernel using one struct mfd_cell entry, defined in include/linux/mfd/core.h. The core driver builds an array of these cells — one per sub-function — and hands the whole array to the MFD core in a single call.
struct mfd_cell {
const char *name;
int id;
const char *devname;
int (*enable)(struct platform_device *dev);
int (*disable)(struct platform_device *dev);
int (*suspend)(struct platform_device *dev);
int (*resume)(struct platform_device *dev);
void *platform_data;
size_t pdata_size;
const struct software_node *swnode;
const char *of_compatible;
u64 of_reg;
bool use_of_reg;
int num_resources;
const struct resource *resources;
bool ignore_resource_conflicts;
bool pm_runtime_no_callbacks;
};
| Field | Purpose |
|---|---|
name |
Driver name used to match the sub-device’s platform driver |
id |
Platform device id, useful when the same cell type repeats |
of_compatible |
Device tree compatible string used to bind the cell to a DT child node |
resources / num_resources |
Memory or IRQ resources that belong only to this sub-device |
platform_data / pdata_size |
Private configuration data passed straight to the sub-device driver |
Registering Cells: mfd_add_devices() and devm_mfd_add_devices()
Once the cell array is ready, the core driver registers every cell in one shot. The managed variant is preferred in modern drivers because the sub-devices are automatically unregistered when the parent device is removed.
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);
The plain, non-managed version has the same signature but is called mfd_add_devices(), and it must be paired with an explicit mfd_remove_devices() call in the driver’s remove path.
Original Coding Example: ep_mfd_demo
The following driver is an original teaching example built for this course. It models a tiny fictional PMIC that exposes two sub-functions: an LED cell and a misc-status cell, matched purely on the of_compatible string so no device tree changes are required for this lecture.
// ep_mfd_demo.c — original EmbeddedPathashala MFD core driver example
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
static const struct mfd_cell ep_mfd_cells[] = {
{
.name = "ep-mfd-led",
.of_compatible = "ep,mfd-led",
},
{
.name = "ep-mfd-status",
.of_compatible = "ep,mfd-status",
},
};
static int ep_mfd_probe(struct platform_device *pdev)
{
int ret;
dev_info(&pdev->dev, "ep_mfd_demo: registering %zu cells\n",
ARRAY_SIZE(ep_mfd_cells));
ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_AUTO,
ep_mfd_cells, ARRAY_SIZE(ep_mfd_cells),
NULL, 0, NULL);
if (ret) {
dev_err(&pdev->dev, "failed to add mfd cells: %d\n", ret);
return ret;
}
dev_info(&pdev->dev, "ep_mfd_demo: probe complete\n");
return 0;
}
static const struct of_device_id ep_mfd_of_match[] = {
{ .compatible = "ep,mfd-demo" },
{ }
};
MODULE_DEVICE_TABLE(of, ep_mfd_of_match);
static struct platform_driver ep_mfd_driver = {
.driver = {
.name = "ep-mfd-demo",
.of_match_table = ep_mfd_of_match,
},
.probe = ep_mfd_probe,
};
module_platform_driver(ep_mfd_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala MFD subsystem teaching driver");
Build and Run Steps
# Kbuild file for an out-of-tree module
obj-m += ep_mfd_demo.o
# Build against the running kernel headers
make -C /lib/modules/$(uname -r)/build M=$PWD modules
# Load the module (probe is triggered via a matching platform device)
sudo insmod ep_mfd_demo.ko
# Watch the kernel log
dmesg | tail -5
# Unload
sudo rmmod ep_mfd_demo
Expected dmesg Output
[ 102.441823] ep_mfd_demo: registering 2 cells
[ 102.441917] ep_mfd_demo: probe complete
Because both cells use of_compatible matching only, no separate driver is bound in this lecture — you will see the platform devices appear under /sys/bus/platform/devices/ once a matching device tree node with two child nodes is present, which we build in the next lecture on device tree binding.
Common Mistakes
- Forgetting
MODULE_DEVICE_TABLE(of, ...), which stops module auto-loading from device tree - Mixing
mfd_add_devices()with a hand-written unwind path instead of just using the manageddevm_variant - Sharing one
mfd_cellresource array across cells that need different IRQ or memory ranges - Assuming cell order in the array defines probe order — it does not; probing is asynchronous per sub-device driver availability
Best Practices
- Prefer
devm_mfd_add_devices()so removal is handled automatically - Keep the cell array
static const— the core copies what it needs at registration time - Use
of_compatiblefor device-tree-based platforms instead of relying on cell index/id matching - Keep shared register access behind a single regmap so cells never race on the same bus transaction
Real-World Use Cases
MFD drivers are everywhere in embedded Linux: PMIC drivers such as da9055, tps65086, and axp20x use exactly this pattern to expose regulator, RTC, and GPIO cells from one chip. Audio codecs that bundle a mixer and a jack-detection block, and SoC “top-level” companion chips on set-top boxes, follow the same design.
Summary / Key Takeaways
- The Linux MFD subsystem lets one physical chip register sub-devices across multiple kernel subsystems
struct mfd_celldescribes each sub-function; an array of cells is handed to the MFD coredevm_mfd_add_devices()is the modern, managed way to register cells- Sub-devices become ordinary
platform_deviceinstances that regulator/rtc/gpio/led drivers bind to normally
Conclusion
The MFD subsystem is a small but elegant piece of the Linux driver model: rather than inventing a new binding mechanism for every multi-function chip, it reuses the existing platform device infrastructure and simply automates the bookkeeping of splitting one device into many. In the next lecture of this free linux kernel development course, we describe these same cells in the device tree so the kernel can match them automatically on boot.
Frequently Asked Questions
What is the difference between an MFD driver and a normal platform driver?
A normal platform driver manages one device. An MFD core driver manages one physical chip but registers several child platform devices — one per function — so several independent subsystem drivers can bind to the same chip.
Do I need device tree to use the MFD subsystem?
No. Cells can be matched by name/id without device tree, though DT matching via of_compatible is the standard approach on modern embedded platforms.
What happens if two cells need the same register?
The MFD driver should own the shared register access itself, typically through a regmap, and expose safe helper functions or a syscon-style regmap handle rather than letting each cell touch the hardware directly.
Is mfd_add_devices() deprecated?
No, but the managed devm_mfd_add_devices() variant is preferred in new drivers since it removes the need for manual cleanup in the remove path.
Can an MFD cell have its own interrupt?
Yes, cells can list IRQ resources via num_resources/resources, and an irq_domain can be passed to mfd_add_devices() to map chip-local interrupts to virtual IRQ numbers.
Where is the MFD subsystem implemented in the kernel source?
The core logic lives in drivers/mfd/mfd-core.c, with the public API declared in include/linux/mfd/core.h.
What is a good first MFD driver to read in the kernel source tree?
drivers/mfd/da9055-core.c is a compact, well-structured example that pairs well with this lecture’s concepts.
Continue the Free Linux Kernel Development Course
Next up: Device Tree Binding for MFD Devices
