[PATCH v23 08/13] mfd: core: Add firmware-node support to MFD cells
Shivendra Pratap
shivendra.pratap at oss.qualcomm.com
Tue Jul 21 05:48:46 PDT 2026
On 21-07-2026 14:59, Bartosz Golaszewski wrote:
> On Tue, 14 Jul 2026 19:16:36 +0200, Shivendra Pratap
> <shivendra.pratap at oss.qualcomm.com> said:
>> MFD core has no way to register a child device using an explicit firmware
>> node. This prevents drivers from registering child nodes when those nodes
>> do not define a compatible string. One such example is the PSCI
>> "reboot-mode" node, which omits a compatible string as it describes
>> boot-states provided by the underlying firmware.
>>
>> Extend struct mfd_cell with a named firmware-node field to identify a
>> child node under the MFD parent. The node is added to the MFD child
>> device during registration when none is assigned by device tree, ACPI,
>> or software matching.
>>
>> Suggested-by: Bartosz Golaszewski <bartosz.golaszewski at oss.qualcomm.com>
>> Signed-off-by: Shivendra Pratap <shivendra.pratap at oss.qualcomm.com>
>> ---
>> drivers/mfd/mfd-core.c | 142 ++++++++++++++++++++++++++++++++++++++++-------
>> include/linux/mfd/core.h | 10 ++++
>> 2 files changed, 131 insertions(+), 21 deletions(-)
>>
>> diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c
>> index 7aa32b90cf1e..ba9682ead2ca 100644
>> --- a/drivers/mfd/mfd-core.c
>> +++ b/drivers/mfd/mfd-core.c
>> @@ -10,6 +10,7 @@
>> #include <linux/kernel.h>
>> #include <linux/platform_device.h>
>> #include <linux/acpi.h>
>> +#include <linux/fwnode.h>
>> #include <linux/list.h>
>> #include <linux/property.h>
>> #include <linux/mfd/core.h>
>> @@ -22,6 +23,7 @@
>> #include <linux/regulator/consumer.h>
>>
>> static LIST_HEAD(mfd_of_node_list);
>> +static LIST_HEAD(mfd_named_fwnode_list);
>> static DEFINE_MUTEX(mfd_of_node_mutex);
>>
>> struct mfd_of_node_entry {
>> @@ -30,10 +32,91 @@ struct mfd_of_node_entry {
>> struct device_node *np;
>> };
>>
>> +struct mfd_named_fwnode_entry {
>> + struct list_head list;
>> + struct device *dev;
>> + struct fwnode_handle *fwnode;
>> +};
>> +
>> static const struct device_type mfd_dev_type = {
>> .name = "mfd_device",
>> };
>>
>> +static int mfd_claim_named_fwnode(struct platform_device *pdev,
>> + struct fwnode_handle *fwnode)
>> +{
>> + struct mfd_named_fwnode_entry *entry, *iter;
>> +
>> + entry = kzalloc_obj(*entry, GFP_KERNEL);
>> + if (!entry)
>> + return -ENOMEM;
>> +
>> + entry->dev = &pdev->dev;
>> + entry->fwnode = fwnode_handle_get(fwnode);
>> +
>> + scoped_guard(mutex, &mfd_of_node_mutex) {
>> + list_for_each_entry(iter, &mfd_named_fwnode_list, list)
>> + if (iter->fwnode == fwnode) {
>> + fwnode_handle_put(entry->fwnode);
>> + kfree(entry);
>> + return -EAGAIN;
>> + }
>> +
>> + list_add_tail(&entry->list, &mfd_named_fwnode_list);
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +/*
>> + * Temporary MFD-local cleanup for named non-OF child fwnodes.
>> + * Remove/rework this when platform core starts owning and dropping
>> + * dev->fwnode references for these devices.
>> + */
>> +static void mfd_release_named_fwnode(struct platform_device *pdev)
>> +{
>> + struct mfd_named_fwnode_entry *entry, *tmp;
>> +
>> + scoped_guard(mutex, &mfd_of_node_mutex) {
>> + list_for_each_entry_safe(entry, tmp, &mfd_named_fwnode_list, list)
>> + if (entry->dev == &pdev->dev) {
>> + if (dev_fwnode(&pdev->dev) == entry->fwnode)
>> + device_set_node(&pdev->dev, NULL);
>> + fwnode_handle_put(entry->fwnode);
>> + list_del(&entry->list);
>> + kfree(entry);
>> + }
>> + }
>> +}
>> +
>> +static int mfd_claim_of_node_to_dev(struct platform_device *pdev,
>> + struct device_node *np)
>> +{
>> + struct mfd_of_node_entry *of_entry, *iter;
>> +
>> + of_entry = kzalloc_obj(*of_entry, GFP_KERNEL);
>> + if (!of_entry)
>> + return -ENOMEM;
>> +
>> + of_entry->dev = &pdev->dev;
>> + of_entry->np = of_node_get(np);
>> +
>> + /* Skip if OF node has previously been allocated to a device */
>> + scoped_guard(mutex, &mfd_of_node_mutex) {
>> + list_for_each_entry(iter, &mfd_of_node_list, list)
>> + if (iter->np == np) {
>> + of_node_put(of_entry->np);
>> + kfree(of_entry);
>> + return -EAGAIN;
>> + }
>> +
>> + list_add_tail(&of_entry->list, &mfd_of_node_list);
>> + }
>> +
>> + device_set_node(&pdev->dev, of_fwnode_handle(np));
>> + return 0;
>> +}
>> +
>> #if IS_ENABLED(CONFIG_ACPI)
>> struct match_ids_walk_data {
>> struct acpi_device_id *ids;
>> @@ -111,19 +194,11 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev,
>> struct device_node *np,
>> const struct mfd_cell *cell)
>> {
>> - struct mfd_of_node_entry *of_entry;
>> u64 of_node_addr;
>>
>> - /* Skip if OF node has previously been allocated to a device */
>> - scoped_guard(mutex, &mfd_of_node_mutex) {
>> - list_for_each_entry(of_entry, &mfd_of_node_list, list)
>> - if (of_entry->np == np)
>> - return -EAGAIN;
>> - }
>> -
>> if (!cell->use_of_reg)
>> /* No of_reg defined - allocate first free compatible match */
>> - goto allocate_of_node;
>> + return mfd_claim_of_node_to_dev(pdev, np);
>>
>> /* We only care about each node's first defined address */
>> if (of_property_read_reg(np, 0, &of_node_addr, NULL))
>> @@ -134,18 +209,7 @@ static int mfd_match_of_node_to_dev(struct platform_device *pdev,
>> /* No match */
>> return -EAGAIN;
>>
>> -allocate_of_node:
>> - of_entry = kzalloc(sizeof(*of_entry), GFP_KERNEL);
>> - if (!of_entry)
>> - return -ENOMEM;
>> -
>> - of_entry->dev = &pdev->dev;
>> - of_entry->np = of_node_get(np);
>> - scoped_guard(mutex, &mfd_of_node_mutex)
>> - list_add_tail(&of_entry->list, &mfd_of_node_list);
>> -
>> - device_set_node(&pdev->dev, of_fwnode_handle(np));
>> - return 0;
>> + return mfd_claim_of_node_to_dev(pdev, np);
>> }
>>
>> static int mfd_add_device(struct device *parent, int id,
>> @@ -156,6 +220,7 @@ static int mfd_add_device(struct device *parent, int id,
>> struct resource *res;
>> struct platform_device *pdev;
>> struct mfd_of_node_entry *of_entry, *tmp;
>> + struct fwnode_handle *fwnode;
>> bool disabled = false;
>> int ret = -ENOMEM;
>> int platform_id;
>> @@ -224,6 +289,37 @@ static int mfd_add_device(struct device *parent, int id,
>>
>> mfd_acpi_add_device(cell, pdev);
>>
>> + /* named_fwnode is a fallback only when no OF/ACPI match and no swnode */
>> + if (!pdev->dev.fwnode && !cell->swnode && cell->named_fwnode) {
>
> Use dev_fwnode() instead of pdev->dev.fwnode.
sure will update. thanks.
>
>> + struct device_node *named_np;
>> +
>> + fwnode = device_get_named_child_node(parent, cell->named_fwnode);
>> + if (!fwnode) {
>> + ret = -ENODEV;
>> + goto fail_alias;
>> + }
>> +
>> + named_np = to_of_node(fwnode);
>> + if (named_np) {
>> + ret = mfd_claim_of_node_to_dev(pdev, named_np);
>> + fwnode_handle_put(fwnode);
>> + if (ret == -EAGAIN)
>> + ret = -EBUSY;
>> + if (ret)
>> + goto fail_alias;
>> + } else {
>> + ret = mfd_claim_named_fwnode(pdev, fwnode);
>> + if (ret) {
>> + fwnode_handle_put(fwnode);
>> + if (ret == -EAGAIN)
>> + ret = -EBUSY;
>> + goto fail_alias;
>> + }
>> + device_set_node(&pdev->dev, fwnode);
>> + fwnode_handle_put(fwnode);
>> + }
>
> What is the reason to have this split into OF and fwnode functions? I don't
> really see why you would need to do it twice, isn't fwnode sufficient for the
> OF node case too?
We do a duplicate check here before adding the named_fwnode and split it
for two reasons:
1. of_node list pre-exists. We add a new list of fwnode. of_node based
duplicate check go wrong, if its checked with the fwnode list.
2. of_node need to go to its respective list as fwnode list, release
path, is different for us.
>
>> + }
>> +
>> if (cell->pdata_size) {
>> ret = platform_device_add_data(pdev,
>> cell->platform_data, cell->pdata_size);
>> @@ -295,6 +391,7 @@ static int mfd_add_device(struct device *parent, int id,
>> if (cell->swnode)
>> device_remove_software_node(&pdev->dev);
>> fail_of_entry:
>> + mfd_release_named_fwnode(pdev);
>> scoped_guard(mutex, &mfd_of_node_mutex) {
>> list_for_each_entry_safe(of_entry, tmp, &mfd_of_node_list, list)
>> if (of_entry->dev == &pdev->dev) {
>> @@ -382,7 +479,10 @@ static int mfd_remove_devices_fn(struct device *dev, void *data)
>> regulator_bulk_unregister_supply_alias(dev, cell->parent_supplies,
>> cell->num_parent_supplies);
>>
>> + get_device(&pdev->dev);
>> platform_device_unregister(pdev);
>> + mfd_release_named_fwnode(pdev);
>> + put_device(&pdev->dev);
>> return 0;
>> }
>>
>> diff --git a/include/linux/mfd/core.h b/include/linux/mfd/core.h
>> index faeea7abd688..8daa83dd31ea 100644
>> --- a/include/linux/mfd/core.h
>> +++ b/include/linux/mfd/core.h
>> @@ -80,6 +80,16 @@ struct mfd_cell {
>>
>> /* Software node for the device. */
>> const struct software_node *swnode;
>> + /*
>> + * Name of a child firmware node under the MFD parent device.
>> + *
>> + * Used only as a fallback when no firmware node is assigned to MFD
>> + * child and no software node is provided.
>> + *
>> + * For Device Tree parents, lookup is by base node name only
>> + * (the part before '@'). Unit-addresses are not matched.
>> + */
>
> Side note: struct mfd_cell could really use a proper kernel doc but that's out
> of scope of this series.
>
>> + const char *named_fwnode;
>>
>> /*
>> * Device Tree compatible string
>>
>> --
>> 2.34.1
>>
>>
>
> Bartosz
thanks,
Shivendra
More information about the linux-arm-kernel
mailing list