Re: [PATCH v23 08/13] mfd: core: Add firmware-node support to MFD cells
Bartosz Golaszewski <[email protected]> Tue, 21 Jul 2026 09:29:10 +0000
| Newsgroups | dev.linux.lists.mfd,org.infradead.lists.linux-arm-kernel,org.kernel.vger.linux-arm-msm,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <CAMRc=McxoC6711rmXmPb9t21bBsTeBMJ3ieZ1XhyTqMLaMS-4Q@mail.gmail.com> |
On Tue, 14 Jul 2026 19:16:36 +0200, Shivendra Pratap <[email protected]> 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 <[email protected]> > Signed-off-by: Shivendra Pratap <[email protected]> > --- > 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. > + 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? > + } > + > 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