[PATCH v24 07/12] mfd: core: Add firmware-node support to MFD cells
Shivendra Pratap <[email protected]> Mon, 03 Aug 2026 15:13:38 +0530
| Newsgroups | org.kernel.vger.linux-pm,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 |
|---|---|
| Message-ID | <20260803-arm-psci-system_reset2-vendor-reboots-v24-7-889281373870@oss.qualcomm.com> |
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 | 151 +++++++++++++++++++++++++++++++++++++++++------ include/linux/mfd/core.h | 10 ++++ 2 files changed, 142 insertions(+), 19 deletions(-) diff --git a/drivers/mfd/mfd-core.c b/drivers/mfd/mfd-core.c index 7aa32b90cf1e..79b400535138 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,48 @@ 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; + return mfd_claim_of_node_to_dev(pdev, np); +} - 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); +static int mfd_attach_named_fwnode(struct device *parent, + struct platform_device *pdev, + const struct mfd_cell *cell) +{ + struct fwnode_handle *fwnode; + struct device_node *named_np; + int ret = 0; - device_set_node(&pdev->dev, of_fwnode_handle(np)); - return 0; + /* named_fwnode is a fallback only when no OF/ACPI match and no swnode */ + if (dev_fwnode(&pdev->dev) || cell->swnode || !cell->named_fwnode) + return 0; + + fwnode = device_get_named_child_node(parent, cell->named_fwnode); + if (!fwnode) + return 0; + + if (!fwnode_device_is_available(fwnode)) + goto out_put; + + named_np = to_of_node(fwnode); + if (named_np) + ret = mfd_claim_of_node_to_dev(pdev, named_np); + else + ret = mfd_claim_named_fwnode(pdev, fwnode); + + if (ret == -EAGAIN) { + ret = 0; + goto out_put; + } + + if (ret) + goto out_put; + + if (!named_np) + device_set_node(&pdev->dev, fwnode); + +out_put: + fwnode_handle_put(fwnode); + return ret; } static int mfd_add_device(struct device *parent, int id, @@ -224,6 +329,10 @@ static int mfd_add_device(struct device *parent, int id, mfd_acpi_add_device(cell, pdev); + ret = mfd_attach_named_fwnode(parent, pdev, cell); + if (ret) + goto fail_alias; + if (cell->pdata_size) { ret = platform_device_add_data(pdev, cell->platform_data, cell->pdata_size); @@ -295,6 +404,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 +492,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. + */ + const char *named_fwnode; /* * Device Tree compatible string -- 2.34.1