[PATCH v6 2/3] mfd: syscon: Add managed registration for external regmaps

From: James Hilliard

Date: Tue Aug 11 2026 - 04:28:31 EST


of_syscon_register_regmap() publishes an externally owned regmap in the
global syscon list, but provides no way for a removable driver to
withdraw it. Registering a devm-managed regmap from such a driver would
therefore leave a stale pointer after unbind.

Factor external registration through an internal helper and add
devm_of_syscon_register_regmap(). The managed action removes the entry
under the syscon list lock before later devres actions release the
regmap. Hold a device-node reference for the lifetime of every
externally registered entry so its lookup key also remains valid.

Consumers of a removable provider remain responsible for ordering
teardown, for example with a managed device link.

Signed-off-by: James Hilliard <james.hilliard1@xxxxxxxxx>
---
drivers/mfd/syscon.c | 87 ++++++++++++++++++++++++++++++++++------------
include/linux/mfd/syscon.h | 12 +++++++
2 files changed, 77 insertions(+), 22 deletions(-)

diff --git a/drivers/mfd/syscon.c b/drivers/mfd/syscon.c
index 21a7fcdd2737..448422fe4f21 100644
--- a/drivers/mfd/syscon.c
+++ b/drivers/mfd/syscon.c
@@ -10,6 +10,7 @@

#include <linux/cleanup.h>
#include <linux/clk.h>
+#include <linux/device.h>
#include <linux/err.h>
#include <linux/hwspinlock.h>
#include <linux/list.h>
@@ -193,53 +194,95 @@ static struct regmap *device_node_get_regmap(struct device_node *np,
return syscon->regmap;
}

-/**
- * of_syscon_register_regmap() - Register regmap for specified device node
- * @np: Device tree node
- * @regmap: Pointer to regmap object
- *
- * Register an externally created regmap object with syscon for the specified
- * device tree node. This regmap will then be returned to client drivers using
- * the syscon_regmap_lookup_by_phandle() API.
- *
- * Return: 0 on success, negative error code on failure.
- */
-int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+static struct syscon *
+of_syscon_register_regmap_internal(struct device_node *np,
+ struct regmap *regmap)
{
struct syscon *entry, *syscon = NULL;
- int ret;

if (!np || !regmap)
- return -EINVAL;
+ return ERR_PTR(-EINVAL);

syscon = kzalloc_obj(*syscon);
if (!syscon)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);

/* check if syscon entry already exists */
mutex_lock(&syscon_list_lock);

list_for_each_entry(entry, &syscon_list, list)
if (entry->np == np) {
- ret = -EEXIST;
- goto err_unlock;
+ mutex_unlock(&syscon_list_lock);
+ kfree(syscon);
+ return ERR_PTR(-EEXIST);
}

syscon->regmap = regmap;
- syscon->np = np;
+ syscon->np = of_node_get(np);

/* register the regmap in syscon list */
list_add_tail(&syscon->list, &syscon_list);
mutex_unlock(&syscon_list_lock);

- return 0;
+ return syscon;
+}
+
+/**
+ * of_syscon_register_regmap() - Register regmap for specified device node
+ * @np: Device tree node
+ * @regmap: Pointer to regmap object
+ *
+ * Register an externally created regmap object with syscon for the specified
+ * device tree node. This regmap will then be returned to client drivers using
+ * the syscon_regmap_lookup_by_phandle() API.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int of_syscon_register_regmap(struct device_node *np, struct regmap *regmap)
+{
+ return PTR_ERR_OR_ZERO(of_syscon_register_regmap_internal(np, regmap));
+}
+EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+static void devm_of_syscon_register_regmap_release(void *data)
+{
+ struct syscon *syscon = data;

-err_unlock:
+ mutex_lock(&syscon_list_lock);
+ list_del(&syscon->list);
mutex_unlock(&syscon_list_lock);
+
+ of_node_put(syscon->np);
kfree(syscon);
- return ret;
}
-EXPORT_SYMBOL_GPL(of_syscon_register_regmap);
+
+/**
+ * devm_of_syscon_register_regmap() - Register a managed external syscon regmap
+ * @dev: Device that owns the regmap
+ * @np: Device tree node associated with the regmap
+ * @regmap: Pointer to the externally created regmap
+ *
+ * Register an externally created regmap object with syscon and remove it when
+ * @dev is unbound. Consumers must stop using the regmap before the provider is
+ * unbound, for example by establishing a managed device link to @dev.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap)
+{
+ struct syscon *syscon;
+
+ syscon = of_syscon_register_regmap_internal(np, regmap);
+ if (IS_ERR(syscon))
+ return PTR_ERR(syscon);
+
+ return devm_add_action_or_reset(dev,
+ devm_of_syscon_register_regmap_release,
+ syscon);
+}
+EXPORT_SYMBOL_GPL(devm_of_syscon_register_regmap);

/**
* device_node_to_regmap() - Get or create a regmap for specified device node
diff --git a/include/linux/mfd/syscon.h b/include/linux/mfd/syscon.h
index aad9c6b50463..3e804fc1b03e 100644
--- a/include/linux/mfd/syscon.h
+++ b/include/linux/mfd/syscon.h
@@ -15,6 +15,7 @@
#include <linux/errno.h>

struct device_node;
+struct device;

#ifdef CONFIG_MFD_SYSCON
struct regmap *device_node_to_regmap(struct device_node *np);
@@ -30,6 +31,9 @@ struct regmap *syscon_regmap_lookup_by_phandle_optional(struct device_node *np,
const char *property);
int of_syscon_register_regmap(struct device_node *np,
struct regmap *regmap);
+int devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap);
#else
static inline struct regmap *device_node_to_regmap(struct device_node *np)
{
@@ -75,6 +79,14 @@ static inline int of_syscon_register_regmap(struct device_node *np,
return -EOPNOTSUPP;
}

+static inline int
+devm_of_syscon_register_regmap(struct device *dev,
+ struct device_node *np,
+ struct regmap *regmap)
+{
+ return -EOPNOTSUPP;
+}
+
#endif

#endif /* __LINUX_MFD_SYSCON_H__ */

--
2.53.0