mail archive of the barebox mailing list
 help / color / mirror / Atom feed
* [PATCH 00/15] NVMEM: Add support for layout drivers
@ 2025-08-04 14:36 Marco Felsch
  2025-08-04 14:36 ` [PATCH 01/15] of: sync of_*_phandle_with_args with Linux Marco Felsch
                   ` (15 more replies)
  0 siblings, 16 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Hi,

with this patchset the barebox nvmem-core is prepared for the Linux
nvmem-layout drivers. Layout drivers are used to describe the NVMEM
storage format. The patchset also adds the support to read nvmem-cells
via the devfs. This makes it possible to read the cells from the board
code or shell without the need of referencing the cells within the DT.

A long with the alignment and feature work I fixed a few issues like:
honor the DT alias for a nvmem provider correctly.

For the new features to work correctly the NVMEM core had to be partly
(re-)synced with the one from Linux. This involved porting different DT
helpers.

Unfortunately this patchset doesn't add a NVMEM layout driver therefore
a __dummy__.o is added within the Makefile.

I decided to start from a fresh v1 because of the rework I've done. But
for reference, the previous patchset can be found here:

 - https://lore.kernel.org/barebox/20240613131531.364894-1-m.felsch@pengutronix.de/

Regards,
  Marco

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
Marco Felsch (15):
      of: sync of_*_phandle_with_args with Linux
      of: base: add of_parse_phandle_with_optional_args()
      of: device: Export of_device_make_bus_id()
      nvmem: core: fix nvmem_register error path
      nvmem: core: sync with Linux
      nvmem: core: expose nvmem cells as cdev
      nvmem: core: allow single and dynamic device ids
      eeprom: at24: fix device name handling
      nvmem: core: create a header for internal sharing
      nvmem: core: add nvmem-layout support
      nvmem: core: add an index parameter to the cell
      nvmem: core: add per-cell post processing
      nvmem: core: add cell based fixup logic
      nvmem: core: provide own priv pointer in post process callback
      nvmem: core: drop global cell_post_process

 drivers/eeprom/at24.c          |  18 +-
 drivers/nvmem/Kconfig          |   7 +
 drivers/nvmem/Makefile         |   3 +
 drivers/nvmem/core.c           | 689 ++++++++++++++++++++++++++++++-----------
 drivers/nvmem/imx-ocotp-ele.c  |  12 +-
 drivers/nvmem/internals.h      |  53 ++++
 drivers/nvmem/layouts.c        | 173 +++++++++++
 drivers/nvmem/layouts/Kconfig  |  13 +
 drivers/nvmem/layouts/Makefile |   7 +
 drivers/nvmem/ocotp.c          |  12 +-
 drivers/nvmem/regmap.c         |   5 +-
 drivers/of/base.c              | 280 +++++++++++------
 drivers/of/device.c            |  37 +++
 drivers/of/platform.c          |  36 +--
 include/linux/nvmem-consumer.h |  17 +-
 include/linux/nvmem-provider.h | 148 ++++++++-
 include/of.h                   | 146 ++++++++-
 include/of_device.h            |   3 +
 18 files changed, 1293 insertions(+), 366 deletions(-)
---
base-commit: 89bf1fcc998fc5fea0ce613d9930dd9ee39c0fb2
change-id: 20250701-v2025-06-0-topic-nvmem-c747b64106e2

Best regards,
-- 
Marco Felsch <m.felsch@pengutronix.de>




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 01/15] of: sync of_*_phandle_with_args with Linux
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 02/15] of: base: add of_parse_phandle_with_optional_args() Marco Felsch
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

The current of_parse_phandle_with_args() logic was ported from Linux a
long time ago by commits:
 - 58f3457f4f03 ("of: add devicetree probing support") and
 - 511ba46157d8 ("OF: base: import parse phandle functions from Linux OF
   API")

Commit f114b0479a22 ("of: base: don't try to read cells_name property if
no cells_name set") adapted the logic to make it compatible with current
mainline device-trees.

This commits syncs the complete of_*_phandle_with_args() family with the
current Linux implementation instead of doing further adaptions to the
old Linux based logic. This ensures that the of_*_phandle_with_args()
family is compatible with the mainline provided device-trees.

Furthermore it prepares the code base to add new of_parse_phandle_* from
Linux more easily.

The main changes are:
 - __of_parse_phandle_with_args() was made public and the accessors like
   of_parse_phandle_with_args() are now static inline functions.
 - The open coded parsing logic was replaced by an iterator based logic
   (struct of_phandle_iterator and its accessors).
 - Some bugfixes

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/of/base.c | 280 +++++++++++++++++++++++++++++++++++-------------------
 include/of.h      | 121 ++++++++++++++++++++++-
 2 files changed, 300 insertions(+), 101 deletions(-)

diff --git a/drivers/of/base.c b/drivers/of/base.c
index 1439e55a0aac0f19aab822901a5331e11ea10d48..808533d5da4e94449b5eb48bfb33a540e504157e 100644
--- a/drivers/of/base.c
+++ b/drivers/of/base.c
@@ -1678,117 +1678,182 @@ struct device_node *of_parse_phandle(const struct device_node *np,
 }
 EXPORT_SYMBOL(of_parse_phandle);
 
-/**
- * of_parse_phandle_with_args() - Find a node pointed by phandle in a list
- * @np:		pointer to a device tree node containing a list
- * @list_name:	property name that contains a list
- * @cells_name:	property name that specifies phandles' arguments count
- * @index:	index of a phandle to parse out
- * @out_args:	optional pointer to output arguments structure (will be filled)
- *
- * This function is useful to parse lists of phandles and their arguments.
- * Returns 0 on success and fills out_args, on error returns appropriate
- * errno value.
- *
- * Example:
- *
- * phandle1: node1 {
- * 	#list-cells = <2>;
- * }
- *
- * phandle2: node2 {
- * 	#list-cells = <1>;
- * }
- *
- * node3 {
- * 	list = <&phandle1 1 2 &phandle2 3>;
- * }
- *
- * To get a device_node of the `node2' node you may call this:
- * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
- */
-static int __of_parse_phandle_with_args(const struct device_node *np,
-					const char *list_name,
-					const char *cells_name, int index,
-					struct of_phandle_args *out_args)
+int of_phandle_iterator_init(struct of_phandle_iterator *it,
+		const struct device_node *np,
+		const char *list_name,
+		const char *cells_name,
+		int cell_count)
 {
-	const __be32 *list, *list_end;
-	int rc = 0, size, cur_index = 0;
-	uint32_t count = 0;
-	struct device_node *node = NULL;
-	phandle phandle;
+	const __be32 *list;
+	int size;
+
+	memset(it, 0, sizeof(*it));
+
+	/*
+	 * one of cell_count or cells_name must be provided to determine the
+	 * argument length.
+	 */
+	if (cell_count < 0 && !cells_name)
+		return -EINVAL;
 
-	/* Retrieve the phandle list property */
 	list = of_get_property(np, list_name, &size);
 	if (!list)
 		return -ENOENT;
-	list_end = list + size / sizeof(*list);
 
-	/* Loop over the phandles until all the requested entry is found */
-	while (list < list_end) {
-		rc = -EINVAL;
-		count = 0;
+	it->cells_name = cells_name;
+	it->cell_count = cell_count;
+	it->parent = np;
+	it->list_end = list + size / sizeof(*list);
+	it->phandle_end = list;
+	it->cur = list;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(of_phandle_iterator_init);
+
+int of_phandle_iterator_next(struct of_phandle_iterator *it)
+{
+	uint32_t count = 0;
+
+	if (it->node) {
+		of_node_put(it->node);
+		it->node = NULL;
+	}
+
+	if (!it->cur || it->phandle_end >= it->list_end)
+		return -ENOENT;
+
+	it->cur = it->phandle_end;
+
+	/* If phandle is 0, then it is an empty entry with no arguments. */
+	it->phandle = be32_to_cpup(it->cur++);
+
+	if (it->phandle) {
 
 		/*
-		 * If phandle is 0, then it is an empty entry with no
-		 * arguments.  Skip forward to the next entry.
+		 * Find the provider node and parse the #*-cells property to
+		 * determine the argument length.
 		 */
-		phandle = be32_to_cpup(list++);
-		if (phandle) {
-			/*
-			 * Find the provider node and parse the #*-cells
-			 * property to determine the argument length
-			 */
-			node = of_find_node_by_phandle(phandle);
-			if (!node) {
-				pr_err("%pOF: could not find phandle\n", np);
-				goto err;
-			}
-			if (cells_name &&
-			    of_property_read_u32(node, cells_name, &count)) {
-				pr_err("%pOF: could not get %s for %pOF\n",
-					 np, cells_name, node);
+		it->node = of_find_node_by_phandle(it->phandle);
+
+		if (it->cells_name) {
+			if (!it->node) {
+				pr_err("%pOF: could not find phandle %d\n",
+				       it->parent, it->phandle);
 				goto err;
 			}
 
-			/*
-			 * Make sure that the arguments actually fit in the
-			 * remaining property data length
-			 */
-			if (list + count > list_end) {
-				pr_err("%pOF: arguments longer than property\n", np);
-				goto err;
+			if (of_property_read_u32(it->node, it->cells_name,
+						 &count)) {
+				/*
+				 * If both cell_count and cells_name is given,
+				 * fall back to cell_count in absence
+				 * of the cells_name property
+				 */
+				if (it->cell_count >= 0) {
+					count = it->cell_count;
+				} else {
+					pr_err("%pOF: could not get %s for %pOF\n",
+					       it->parent,
+					       it->cells_name,
+					       it->node);
+					goto err;
+				}
 			}
+		} else {
+			count = it->cell_count;
 		}
 
 		/*
-		 * All of the error cases above bail out of the loop, so at
+		 * Make sure that the arguments actually fit in the remaining
+		 * property data length
+		 */
+		if (it->cur + count > it->list_end) {
+			if (it->cells_name)
+				pr_err("%pOF: %s = %d found %td\n",
+					it->parent, it->cells_name,
+					count, it->list_end - it->cur);
+			else
+				pr_err("%pOF: phandle %s needs %d, found %td\n",
+					it->parent, of_node_full_name(it->node),
+					count, it->list_end - it->cur);
+			goto err;
+		}
+	}
+
+	it->phandle_end = it->cur + count;
+	it->cur_count = count;
+
+	return 0;
+
+err:
+	if (it->node) {
+		of_node_put(it->node);
+		it->node = NULL;
+	}
+
+	return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(of_phandle_iterator_next);
+
+int of_phandle_iterator_args(struct of_phandle_iterator *it,
+			     uint32_t *args,
+			     int size)
+{
+	int i, count;
+
+	count = it->cur_count;
+
+	if (WARN_ON(size < count))
+		count = size;
+
+	for (i = 0; i < count; i++)
+		args[i] = be32_to_cpup(it->cur++);
+
+	return count;
+}
+
+int __of_parse_phandle_with_args(const struct device_node *np,
+				 const char *list_name,
+				 const char *cells_name,
+				 int cell_count, int index,
+				 struct of_phandle_args *out_args)
+{
+	struct of_phandle_iterator it;
+	int rc, cur_index = 0;
+
+	if (index < 0)
+		return -EINVAL;
+
+	/* Loop over the phandles until all the requested entry is found */
+	of_for_each_phandle(&it, rc, np, list_name, cells_name, cell_count) {
+		/*
+		 * All of the error cases bail out of the loop, so at
 		 * this point, the parsing is successful. If the requested
 		 * index matches, then fill the out_args structure and return,
 		 * or return -ENOENT for an empty entry.
 		 */
 		rc = -ENOENT;
 		if (cur_index == index) {
-			if (!phandle)
+			if (!it.phandle)
 				goto err;
 
 			if (out_args) {
-				int i;
-				if (WARN_ON(count > MAX_PHANDLE_ARGS))
-					count = MAX_PHANDLE_ARGS;
-				out_args->np = node;
-				out_args->args_count = count;
-				for (i = 0; i < count; i++)
-					out_args->args[i] =
-						be32_to_cpup(list++);
+				int c;
+
+				c = of_phandle_iterator_args(&it,
+							     out_args->args,
+							     MAX_PHANDLE_ARGS);
+				out_args->np = it.node;
+				out_args->args_count = c;
+			} else {
+				of_node_put(it.node);
 			}
 
 			/* Found it! return success */
 			return 0;
 		}
 
-		node = NULL;
-		list += count;
 		cur_index++;
 	}
 
@@ -1796,23 +1861,13 @@ static int __of_parse_phandle_with_args(const struct device_node *np,
 	 * Unlock node before returning result; will be one of:
 	 * -ENOENT : index is for empty phandle
 	 * -EINVAL : parsing error on data
-	 * [1..n]  : Number of phandle (count mode; when index = -1)
 	 */
-	rc = index < 0 ? cur_index : -ENOENT;
+
  err:
+	of_node_put(it.node);
 	return rc;
 }
-
-int of_parse_phandle_with_args(const struct device_node *np,
-		const char *list_name, const char *cells_name, int index,
-		struct of_phandle_args *out_args)
-{
-	if (index < 0)
-		return -EINVAL;
-	return __of_parse_phandle_with_args(np, list_name, cells_name,
-					index, out_args);
-}
-EXPORT_SYMBOL(of_parse_phandle_with_args);
+EXPORT_SYMBOL(__of_parse_phandle_with_args);
 
 /**
  * of_count_phandle_with_args() - Find the number of phandles references in a property
@@ -1820,7 +1875,7 @@ EXPORT_SYMBOL(of_parse_phandle_with_args);
  * @list_name:	property name that contains a list
  * @cells_name:	property name that specifies phandles' arguments count
  *
- * Returns the number of phandle + argument tuples within a property. It
+ * Return: The number of phandle + argument tuples within a property. It
  * is a typical pattern to encode a list of phandle and variable
  * arguments into a single property. The number of arguments is encoded
  * by a property in the phandle-target node. For example, a gpios
@@ -1829,11 +1884,40 @@ EXPORT_SYMBOL(of_parse_phandle_with_args);
  * determined by the #gpio-cells property in the node pointed to by the
  * phandle.
  */
-int of_count_phandle_with_args(const struct device_node *np,
-			const char *list_name, const char *cells_name)
+int of_count_phandle_with_args(const struct device_node *np, const char *list_name,
+				const char *cells_name)
 {
-	return __of_parse_phandle_with_args(np, list_name, cells_name,
-					-1, NULL);
+	struct of_phandle_iterator it;
+	int rc, cur_index = 0;
+
+	/*
+	 * If cells_name is NULL we assume a cell count of 0. This makes
+	 * counting the phandles trivial as each 32bit word in the list is a
+	 * phandle and no arguments are to consider. So we don't iterate through
+	 * the list but just use the length to determine the phandle count.
+	 */
+	if (!cells_name) {
+		const __be32 *list;
+		int size;
+
+		list = of_get_property(np, list_name, &size);
+		if (!list)
+			return -ENOENT;
+
+		return size / sizeof(*list);
+	}
+
+	rc = of_phandle_iterator_init(&it, np, list_name, cells_name, -1);
+	if (rc)
+		return rc;
+
+	while ((rc = of_phandle_iterator_next(&it)) == 0)
+		cur_index += 1;
+
+	if (rc != -ENOENT)
+		return rc;
+
+	return cur_index;
 }
 EXPORT_SYMBOL(of_count_phandle_with_args);
 
diff --git a/include/of.h b/include/of.h
index 2258cd501b727797ac00fc4cce1a6fdcfc529d44..841ab0408b19f6341e67685891bc4da4e82771c8 100644
--- a/include/of.h
+++ b/include/of.h
@@ -52,6 +52,23 @@ struct of_phandle_args {
 	uint32_t args[MAX_PHANDLE_ARGS];
 };
 
+struct of_phandle_iterator {
+	/* Common iterator information */
+	const char *cells_name;
+	int cell_count;
+	const struct device_node *parent;
+
+	/* List size information */
+	const __be32 *list_end;
+	const __be32 *phandle_end;
+
+	/* Current position state */
+	const __be32 *cur;
+	uint32_t cur_count;
+	phandle phandle;
+	struct device_node *node;
+};
+
 #define OF_MAX_RESERVE_MAP	16
 struct of_reserve_map {
 	uint64_t start[OF_MAX_RESERVE_MAP];
@@ -134,6 +151,9 @@ extern int of_bus_n_addr_cells(struct device_node *np);
 extern int of_n_addr_cells(struct device_node *np);
 extern int of_bus_n_size_cells(struct device_node *np);
 extern int of_n_size_cells(struct device_node *np);
+extern int __of_parse_phandle_with_args(const struct device_node *np,
+	const char *list_name, const char *cells_name, int cell_count,
+	int index, struct of_phandle_args *out_args);
 extern bool of_node_name_eq(const struct device_node *np, const char *name);
 extern size_t of_node_has_prefix(const struct device_node *np, const char *prefix);
 
@@ -298,12 +318,69 @@ extern struct device_node *of_parse_phandle_from(const struct device_node *np,
 					    struct device_node *root,
 					    const char *phandle_name,
 					    int index);
-extern int of_parse_phandle_with_args(const struct device_node *np,
-	const char *list_name, const char *cells_name, int index,
-	struct of_phandle_args *out_args);
+/**
+ * of_parse_phandle_with_args() - Find a node pointed by phandle in a list
+ * @np:		pointer to a device tree node containing a list
+ * @list_name:	property name that contains a list
+ * @cells_name:	property name that specifies phandles' arguments count
+ * @index:	index of a phandle to parse out
+ * @out_args:	optional pointer to output arguments structure (will be filled)
+ *
+ * This function is useful to parse lists of phandles and their arguments.
+ * Returns 0 on success and fills out_args, on error returns appropriate
+ * errno value.
+ *
+ * Caller is responsible to call of_node_put() on the returned out_args->np
+ * pointer.
+ *
+ * Example::
+ *
+ *  phandle1: node1 {
+ *	#list-cells = <2>;
+ *  };
+ *
+ *  phandle2: node2 {
+ *	#list-cells = <1>;
+ *  };
+ *
+ *  node3 {
+ *	list = <&phandle1 1 2 &phandle2 3>;
+ *  };
+ *
+ * To get a device_node of the ``node2`` node you may call this:
+ * of_parse_phandle_with_args(node3, "list", "#list-cells", 1, &args);
+ */
+static inline int of_parse_phandle_with_args(const struct device_node *np,
+					     const char *list_name,
+					     const char *cells_name,
+					     int index,
+					     struct of_phandle_args *out_args)
+{
+	int cell_count = -1;
+
+	/* If cells_name is NULL we assume a cell count of 0 */
+	if (!cells_name)
+		cell_count = 0;
+
+	return __of_parse_phandle_with_args(np, list_name, cells_name,
+					    cell_count, index, out_args);
+}
+
 extern int of_count_phandle_with_args(const struct device_node *np,
 	const char *list_name, const char *cells_name);
 
+/* phandle iterator functions */
+extern int of_phandle_iterator_init(struct of_phandle_iterator *it,
+				    const struct device_node *np,
+				    const char *list_name,
+				    const char *cells_name,
+				    int cell_count);
+
+extern int of_phandle_iterator_next(struct of_phandle_iterator *it);
+extern int of_phandle_iterator_args(struct of_phandle_iterator *it,
+				    uint32_t *args,
+				    int size);
+
 extern void of_alias_scan(void);
 extern int of_alias_get_id(struct device_node *np, const char *stem);
 extern int of_alias_get_id_from(struct device_node *root, struct device_node *np,
@@ -751,6 +828,16 @@ static inline int of_property_read_string_helper(const struct device_node *np,
 	return -ENOSYS;
 }
 
+static inline int __of_parse_phandle_with_args(const struct device_node *np,
+					       const char *list_name,
+					       const char *cells_name,
+					       int cell_count,
+					       int index,
+					       struct of_phandle_args *out_args)
+{
+	return -ENOSYS;
+}
+
 static inline const __be32 *of_prop_next_u32(const struct property *prop,
 					const __be32 *cur, u32 *pu)
 {
@@ -825,6 +912,27 @@ static inline int of_count_phandle_with_args(const struct device_node *np,
 	return -ENOSYS;
 }
 
+static inline int of_phandle_iterator_init(struct of_phandle_iterator *it,
+					   const struct device_node *np,
+					   const char *list_name,
+					   const char *cells_name,
+					   int cell_count)
+{
+	return -ENOSYS;
+}
+
+static inline int of_phandle_iterator_next(struct of_phandle_iterator *it)
+{
+	return -ENOSYS;
+}
+
+static inline int of_phandle_iterator_args(struct of_phandle_iterator *it,
+					   uint32_t *args,
+					   int size)
+{
+	return 0;
+}
+
 static inline struct device_node *of_find_node_by_path_from(
 	struct device_node *from, const char *path)
 {
@@ -1229,6 +1337,13 @@ static inline int of_property_read_s32(const struct device_node *np,
 	return of_property_read_u32(np, propname, (u32*) out_value);
 }
 
+#define of_for_each_phandle(it, err, np, ln, cn, cc)			\
+	for (of_phandle_iterator_init((it), (np), (ln), (cn), (cc)),	\
+	     err = of_phandle_iterator_next(it);			\
+	     err == 0;							\
+	     err = of_phandle_iterator_next(it))
+
+
 /**
  * of_property_read_u64_array - Find and read an array of 64 bit integers
  * from a property.

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 02/15] of: base: add of_parse_phandle_with_optional_args()
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
  2025-08-04 14:36 ` [PATCH 01/15] of: sync of_*_phandle_with_args with Linux Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 03/15] of: device: Export of_device_make_bus_id() Marco Felsch
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

This ports Linux commit:

| commit c5d264d4b527c96ae8903376a4b195df47b05203
| Author: Michael Walle <michael@walle.cc>
| Date:   Mon Feb 6 13:43:43 2023 +0000
|
|     of: base: add of_parse_phandle_with_optional_args()
|
|     Add a new variant of the of_parse_phandle_with_args() which treats the
|     cells name as optional. If it's missing, it is assumed that the phandle
|     has no arguments.
|
|     Up until now, a nvmem node didn't have any arguments, so all the device
|     trees haven't any '#*-cells' property. But there is a need for an
|     additional argument for the phandle, for which we need a '#*-cells'
|     property. Therefore, we need to support nvmem nodes with and without
|     this property.
|
|     Signed-off-by: Michael Walle <michael@walle.cc>
|     Reviewed-by: Rob Herring <robh@kernel.org>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20230206134356.839737-10-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 include/of.h | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/include/of.h b/include/of.h
index 841ab0408b19f6341e67685891bc4da4e82771c8..55ae5f369793e97abd78cd959a76d18ad17d5e3a 100644
--- a/include/of.h
+++ b/include/of.h
@@ -366,6 +366,31 @@ static inline int of_parse_phandle_with_args(const struct device_node *np,
 					    cell_count, index, out_args);
 }
 
+/**
+ * of_parse_phandle_with_optional_args() - Find a node pointed by phandle in a list
+ * @np:		pointer to a device tree node containing a list
+ * @list_name:	property name that contains a list
+ * @cells_name:	property name that specifies phandles' arguments count
+ * @index:	index of a phandle to parse out
+ * @out_args:	optional pointer to output arguments structure (will be filled)
+ *
+ * Same as of_parse_phandle_with_args() except that if the cells_name property
+ * is not found, cell_count of 0 is assumed.
+ *
+ * This is used to useful, if you have a phandle which didn't have arguments
+ * before and thus doesn't have a '#*-cells' property but is now migrated to
+ * having arguments while retaining backwards compatibility.
+ */
+static inline int of_parse_phandle_with_optional_args(const struct device_node *np,
+						      const char *list_name,
+						      const char *cells_name,
+						      int index,
+						      struct of_phandle_args *out_args)
+{
+	return __of_parse_phandle_with_args(np, list_name, cells_name,
+					    0, index, out_args);
+}
+
 extern int of_count_phandle_with_args(const struct device_node *np,
 	const char *list_name, const char *cells_name);
 

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 03/15] of: device: Export of_device_make_bus_id()
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
  2025-08-04 14:36 ` [PATCH 01/15] of: sync of_*_phandle_with_args with Linux Marco Felsch
  2025-08-04 14:36 ` [PATCH 02/15] of: base: add of_parse_phandle_with_optional_args() Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 04/15] nvmem: core: fix nvmem_register error path Marco Felsch
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Port Linux commit:

| commit 7f38b70042fcaa49219045bd1a9a2836e27a58ac
| Author: Miquel Raynal <miquel.raynal@bootlin.com>
| Date:   Fri Dec 15 11:15:27 2023 +0000
|
|     of: device: Export of_device_make_bus_id()
|
|     This helper is really handy to create unique device names based on their
|     device tree path, we may need it outside of the OF core (in the NVMEM
|     subsystem) so let's export it. As this helper has nothing patform
|     specific, let's move it to of/device.c instead of of/platform.c so we
|     can add its prototype to of_device.h.
|
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Acked-by: Rob Herring <robh@kernel.org>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20231215111536.316972-2-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

This is required for the upcoming nvmem layout support.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/of/device.c   | 37 +++++++++++++++++++++++++++++++++++++
 drivers/of/platform.c | 36 +-----------------------------------
 include/of_device.h   |  3 +++
 3 files changed, 41 insertions(+), 35 deletions(-)

diff --git a/drivers/of/device.c b/drivers/of/device.c
index 77c027b57e3fd0edaed2270d7694b7367ac174d9..e28b8229c46f19c8403b688fba9255aae3bf31f0 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -2,6 +2,7 @@
 #include <common.h>
 #include <of.h>
 #include <of_device.h>
+#include <of_address.h>
 
 /**
  * of_match_device - Tell if a struct device matches an of_device_id list
@@ -44,3 +45,39 @@ const char *of_device_get_match_compatible(const struct device *dev)
 	return match->compatible;
 }
 EXPORT_SYMBOL(of_device_get_match_compatible);
+
+/**
+ * of_device_make_bus_id - Use the device node data to assign a unique name
+ * @dev: pointer to device structure that is linked to a device tree node
+ *
+ * This routine will first try using the translated bus address to
+ * derive a unique name. If it cannot, then it will prepend names from
+ * parent nodes until a unique name can be derived.
+ */
+void of_device_make_bus_id(struct device *dev)
+{
+	struct device_node *node = dev->of_node;
+	const __be32 *reg;
+	u64 addr;
+
+	/* Construct the name, using parent nodes if necessary to ensure uniqueness */
+	while (node->parent) {
+		/*
+		 * If the address can be translated, then that is as much
+		 * uniqueness as we need. Make it the first component and return
+		 */
+		reg = of_get_property(node, "reg", NULL);
+		if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
+			dev_set_name(dev, dev->name ? "%llx.%s:%s" : "%llx.%s.of",
+				     (unsigned long long)addr, node->name,
+				     dev->name);
+			return;
+		}
+
+		/* format arguments only used if dev_name() resolves to NULL */
+		dev_set_name(dev, dev->name ? "%s:%s" : "%s.of",
+			     kbasename(node->full_name), dev->name);
+		node = node->parent;
+	}
+}
+EXPORT_SYMBOL_GPL(of_device_make_bus_id);
diff --git a/drivers/of/platform.c b/drivers/of/platform.c
index 3600b5f9c10b1968007f84c60e266299c7be8e9e..760ad9527374e559a39ed655419b991bc3a8e6a0 100644
--- a/drivers/of/platform.c
+++ b/drivers/of/platform.c
@@ -12,6 +12,7 @@
 #include <malloc.h>
 #include <of.h>
 #include <of_address.h>
+#include <of_device.h>
 #include <linux/amba/bus.h>
 #include <mmu.h>
 
@@ -38,41 +39,6 @@ struct device *of_find_device_by_node(struct device_node *np)
 }
 EXPORT_SYMBOL(of_find_device_by_node);
 
-/**
- * of_device_make_bus_id - Use the device node data to assign a unique name
- * @dev: pointer to device structure that is linked to a device tree node
- *
- * This routine will first try using the translated bus address to
- * derive a unique name. If it cannot, then it will prepend names from
- * parent nodes until a unique name can be derived.
- */
-static void of_device_make_bus_id(struct device *dev)
-{
-	struct device_node *node = dev->of_node;
-	const __be32 *reg;
-	u64 addr;
-
-	/* Construct the name, using parent nodes if necessary to ensure uniqueness */
-	while (node->parent) {
-		/*
-		 * If the address can be translated, then that is as much
-		 * uniqueness as we need. Make it the first component and return
-		 */
-		reg = of_get_property(node, "reg", NULL);
-		if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) {
-			dev_set_name(dev, dev->name ? "%llx.%s:%s" : "%llx.%s.of",
-				     (unsigned long long)addr, node->name,
-				     dev->name);
-			return;
-		}
-
-		/* format arguments only used if dev_name() resolves to NULL */
-		dev_set_name(dev, dev->name ? "%s:%s" : "%s.of",
-			     kbasename(node->full_name), dev->name);
-		node = node->parent;
-	}
-}
-
 static struct device_node *of_get_next_dma_parent(const struct device_node *np)
 {
 	struct of_phandle_args args;
diff --git a/include/of_device.h b/include/of_device.h
index 5afcc5ff94b1095031659dd4a418f6c42442147f..14f9062c6c2c61a3b05b6e6ee94f59960812d26e 100644
--- a/include/of_device.h
+++ b/include/of_device.h
@@ -23,6 +23,7 @@ static inline int of_driver_match_device(struct device *dev,
 
 extern const void *of_device_get_match_data(const struct device *dev);
 extern const char *of_device_get_match_compatible(const struct device *dev);
+void of_device_make_bus_id(struct device *dev);
 
 #else /* CONFIG_OFTREE */
 
@@ -50,6 +51,8 @@ static inline const struct of_device_id *__of_match_device(
 #define of_match_device(matches, dev)	\
 	__of_match_device(matches, (dev))
 
+static inline void of_device_make_bus_id(struct device *dev) {}
+
 #endif /* CONFIG_OFTREE */
 
 #endif /* _LINUX_OF_DEVICE_H */

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 04/15] nvmem: core: fix nvmem_register error path
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (2 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 03/15] of: device: Export of_device_make_bus_id() Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 05/15] nvmem: core: sync with Linux Marco Felsch
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

The nvmem->dev device is still registered even if nvmem_register_cdev()
fails and the 'nvmem' memory was freed. Fix this by unregister the
device first before freeing the memory.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 7acd26474b503a6a6f8c6297c7c36a3a1990105a..562d25ef1fd6cc06c8308cc342d1d1f83edc1585 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -228,15 +228,19 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 
 	if (!config->cdev) {
 		rval = nvmem_register_cdev(nvmem, config->name);
-		if (rval) {
-			kfree(nvmem);
-			return ERR_PTR(rval);
-		}
+		if (rval)
+			goto err_unregister;
 	}
 
 	list_add_tail(&nvmem->node, &nvmem_devs);
 
 	return nvmem;
+
+err_unregister:
+	unregister_device(&nvmem->dev);
+	kfree(nvmem);
+
+	return ERR_PTR(rval);
 }
 EXPORT_SYMBOL_GPL(nvmem_register);
 

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 05/15] nvmem: core: sync with Linux
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (3 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 04/15] nvmem: core: fix nvmem_register error path Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 06/15] nvmem: core: expose nvmem cells as cdev Marco Felsch
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Partly (re-)sync the nvmem core with Linux. The main changes are:
  - The nvmem_cell struct was split into nvmem_cell and nvmem_cell_entry
  - The cells are now parsed and registered during nvmem_register(), no
    longer during of_nvmem_cell_get()
  - The registered cells are now tracked per nvmem device, no longer in
    a global nvmem_cells list

The sync is in preparation of adding nvmem-layout driver support and
features like accessing nvmem-cells via cdevs.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 428 ++++++++++++++++++++++++++---------------
 include/linux/nvmem-consumer.h |  17 +-
 include/linux/nvmem-provider.h |  58 +++++-
 3 files changed, 326 insertions(+), 177 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 562d25ef1fd6cc06c8308cc342d1d1f83edc1585..c9afbb242f945df8a770b783bb02c8132dc59347 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -25,6 +25,7 @@ struct nvmem_device {
 	bool			read_only;
 	struct cdev		cdev;
 	void			*priv;
+	struct list_head	cells;
 	nvmem_cell_post_process_t cell_post_process;
 	int			(*reg_write)(void *ctx, unsigned int reg,
 					     const void *val, size_t val_size);
@@ -32,18 +33,23 @@ struct nvmem_device {
 					    void *val, size_t val_size);
 };
 
-struct nvmem_cell {
+struct nvmem_cell_entry {
 	const char		*name;
-	const char		*id;
 	int			offset;
+	size_t			raw_len;
 	int			bytes;
 	int			bit_offset;
 	int			nbits;
+	struct device_node	*np;
 	struct nvmem_device	*nvmem;
 	struct list_head	node;
 };
 
-static LIST_HEAD(nvmem_cells);
+struct nvmem_cell {
+	struct nvmem_cell_entry *entry;
+	const char		*id;
+};
+
 static LIST_HEAD(nvmem_devs);
 
 void nvmem_devices_print(void)
@@ -130,40 +136,40 @@ static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
 	return NULL;
 }
 
-static struct nvmem_cell *nvmem_find_cell(const char *cell_id)
+static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
 {
-	struct nvmem_cell *p;
-
-	list_for_each_entry(p, &nvmem_cells, node)
-		if (!strcmp(p->name, cell_id))
-			return p;
-
-	return NULL;
+	list_del(&cell->node);
+	of_node_put(cell->np);
+	kfree_const(cell->name);
+	kfree(cell);
 }
 
-static void nvmem_cell_drop(struct nvmem_cell *cell)
+static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
 {
-	list_del(&cell->node);
-	kfree_const(cell->id);
-	kfree(cell);
+	struct nvmem_cell_entry *cell, *p;
+
+	list_for_each_entry_safe(cell, p, &nvmem->cells, node)
+		nvmem_cell_entry_drop(cell);
 }
 
-static void nvmem_cell_add(struct nvmem_cell *cell)
+static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
 {
-	list_add_tail(&cell->node, &nvmem_cells);
+	list_add_tail(&cell->node, &cell->nvmem->cells);
 }
 
-static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem,
-				   const struct nvmem_cell_info *info,
-				   struct nvmem_cell *cell)
+static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
+						     const struct nvmem_cell_info *info,
+						     struct nvmem_cell_entry *cell)
 {
 	cell->nvmem = nvmem;
 	cell->offset = info->offset;
+	cell->raw_len = info->raw_len ?: info->bytes;
 	cell->bytes = info->bytes;
 	cell->name = info->name;
 
 	cell->bit_offset = info->bit_offset;
 	cell->nbits = info->nbits;
+	cell->np = info->np;
 
 	if (cell->nbits)
 		cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
@@ -172,13 +178,110 @@ static int nvmem_cell_info_to_nvmem_cell(struct nvmem_device *nvmem,
 	if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
 		dev_err(&nvmem->dev,
 			"cell %s unaligned to nvmem stride %d\n",
-			cell->name, nvmem->stride);
+			cell->name ?: "<unknown>", nvmem->stride);
 		return -EINVAL;
 	}
 
 	return 0;
 }
 
+static int nvmem_cell_info_to_nvmem_cell_entry(struct nvmem_device *nvmem,
+					       const struct nvmem_cell_info *info,
+					       struct nvmem_cell_entry *cell)
+{
+	int err;
+
+	err = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, cell);
+	if (err)
+		return err;
+
+	cell->name = kstrdup_const(info->name, GFP_KERNEL);
+	if (!cell->name)
+		return -ENOMEM;
+
+	return 0;
+}
+
+/**
+ * nvmem_add_one_cell() - Add one cell information to an nvmem device
+ *
+ * @nvmem: nvmem device to add cells to.
+ * @info: nvmem cell info to add to the device
+ *
+ * Return: 0 or negative error code on failure.
+ */
+int nvmem_add_one_cell(struct nvmem_device *nvmem,
+		       const struct nvmem_cell_info *info)
+{
+	struct nvmem_cell_entry *cell;
+	int rval;
+
+	cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+	if (!cell)
+		return -ENOMEM;
+
+	rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell);
+	if (rval) {
+		kfree(cell);
+		return rval;
+	}
+
+	nvmem_cell_entry_add(cell);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nvmem_add_one_cell);
+
+static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_node *np)
+{
+	struct device *dev = &nvmem->dev;
+	struct device_node *child;
+	const __be32 *addr;
+	int len, ret;
+
+	if (!IS_ENABLED(CONFIG_OFTREE))
+		return 0;
+
+	for_each_child_of_node(np, child) {
+		struct nvmem_cell_info info = {0};
+
+		addr = of_get_property(child, "reg", &len);
+		if (!addr)
+			continue;
+		if (len < 2 * sizeof(u32)) {
+			dev_err(dev, "nvmem: invalid reg on %pOF\n", child);
+			of_node_put(child);
+			return -EINVAL;
+		}
+
+		info.offset = be32_to_cpup(addr++);
+		info.bytes = be32_to_cpup(addr);
+		info.name = basprintf("%pOFn", child);
+
+		addr = of_get_property(child, "bits", &len);
+		if (addr && len == (2 * sizeof(u32))) {
+			info.bit_offset = be32_to_cpup(addr++);
+			info.nbits = be32_to_cpup(addr);
+		}
+
+		info.np = of_node_get(child);
+
+		ret = nvmem_add_one_cell(nvmem, &info);
+		kfree(info.name);
+		if (ret) {
+			of_node_put(child);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem)
+{
+	return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node);
+}
+
 /**
  * nvmem_register() - Register a nvmem device for given nvmem_config.
  *
@@ -210,8 +313,13 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	np = config->cdev ? cdev_of_node(config->cdev) : config->dev->of_node;
 	nvmem->dev.of_node = np;
 	nvmem->priv = config->priv;
+	INIT_LIST_HEAD(&nvmem->cells);
 	nvmem->cell_post_process = config->cell_post_process;
 
+	rval = nvmem_add_cells_from_legacy_of(nvmem);
+	if (rval)
+		goto err_remove_cells;
+
 	if (config->read_only || !config->reg_write || of_property_read_bool(np, "read-only"))
 		nvmem->read_only = true;
 
@@ -221,10 +329,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	dev_dbg(nvmem->dev.parent, "Registering nvmem device %s\n", config->name);
 
 	rval = register_device(&nvmem->dev);
-	if (rval) {
-		kfree(nvmem);
-		return ERR_PTR(rval);
-	}
+	if (rval)
+		goto err_remove_cells;
 
 	if (!config->cdev) {
 		rval = nvmem_register_cdev(nvmem, config->name);
@@ -238,6 +344,8 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 
 err_unregister:
 	unregister_device(&nvmem->dev);
+err_remove_cells:
+	nvmem_device_remove_all_cells(nvmem);
 	kfree(nvmem);
 
 	return ERR_PTR(rval);
@@ -252,32 +360,21 @@ static int of_nvmem_device_ensure_probed(struct device_node *np)
 	return of_device_ensure_probed(np);
 }
 
-static struct nvmem_device *__nvmem_device_get(struct device_node *np,
-					       struct nvmem_cell **cellp,
-					       const char *cell_id)
+static struct nvmem_device *__nvmem_device_get(struct device_node *np)
 {
 	struct nvmem_device *nvmem = NULL;
 	int ret;
 
-	if (np) {
-		ret = of_nvmem_device_ensure_probed(np);
-		if (ret)
-			return ERR_PTR(ret);
-
-		nvmem = of_nvmem_find(np);
-		if (!nvmem)
-			return ERR_PTR(-EPROBE_DEFER);
-	} else {
-		struct nvmem_cell *cell = nvmem_find_cell(cell_id);
+	if (!np)
+		return ERR_PTR(-EINVAL);
 
-		if (cell) {
-			nvmem = cell->nvmem;
-			*cellp = cell;
-		}
+	ret = of_nvmem_device_ensure_probed(np);
+	if (ret)
+		return ERR_PTR(ret);
 
-		if (!nvmem)
-			return ERR_PTR(-ENOENT);
-	}
+	nvmem = of_nvmem_find(np);
+	if (!nvmem)
+		return ERR_PTR(-EPROBE_DEFER);
 
 	nvmem->users++;
 
@@ -308,6 +405,7 @@ struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
 {
 
 	struct device_node *nvmem_np;
+	struct nvmem_device *nvmem;
 	int index = 0;
 
 	if (id)
@@ -317,7 +415,9 @@ struct nvmem_device *of_nvmem_device_get(struct device_node *np, const char *id)
 	if (!nvmem_np)
 		return ERR_PTR(-ENOENT);
 
-	return __nvmem_device_get(nvmem_np, NULL, NULL);
+	nvmem = __nvmem_device_get(nvmem_np);
+	of_node_put(nvmem_np);
+	return nvmem;
 }
 EXPORT_SYMBOL_GPL(of_nvmem_device_get);
 #endif
@@ -359,105 +459,100 @@ void nvmem_device_put(struct nvmem_device *nvmem)
 }
 EXPORT_SYMBOL_GPL(nvmem_device_put);
 
-static struct nvmem_cell *nvmem_cell_get_from_list(const char *cell_id)
+static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, const char *id)
 {
-	struct nvmem_cell *cell = NULL;
-	struct nvmem_device *nvmem;
+	struct nvmem_cell *cell;
+	const char *name = NULL;
 
-	nvmem = __nvmem_device_get(NULL, &cell, cell_id);
-	if (IS_ERR(nvmem))
-		return ERR_CAST(nvmem);
+	cell = kzalloc(sizeof(*cell), GFP_KERNEL);
+	if (!cell)
+		return ERR_PTR(-ENOMEM);
+
+	if (id) {
+		name = kstrdup_const(id, GFP_KERNEL);
+		if (!name) {
+			kfree(cell);
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	cell->id = name;
+	cell->entry = entry;
 
 	return cell;
 }
 
 #if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OFTREE)
+static struct nvmem_cell_entry *
+nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np)
+{
+	struct nvmem_cell_entry *iter, *cell = NULL;
+
+	list_for_each_entry(iter, &nvmem->cells, node) {
+		if (np == iter->np) {
+			cell = iter;
+			break;
+		}
+	}
+
+	return cell;
+}
+
 /**
  * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
  *
- * @dev node: Device tree node that uses the nvmem cell
- * @id: nvmem cell name from nvmem-cell-names property.
+ * @np: Device tree node that uses the nvmem cell.
+ * @id: nvmem cell name from nvmem-cell-names property, or NULL
+ *      for the cell at index 0 (the lone cell with no accompanying
+ *      nvmem-cell-names property).
  *
  * Return: Will be an ERR_PTR() on error or a valid pointer
  * to a struct nvmem_cell.  The nvmem_cell will be freed by the
  * nvmem_cell_put().
  */
-struct nvmem_cell *of_nvmem_cell_get(struct device_node *np,
-					    const char *name)
+struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
 {
 	struct device_node *cell_np, *nvmem_np;
-	struct nvmem_cell *cell;
 	struct nvmem_device *nvmem;
-	const __be32 *addr;
-	int rval, len, index;
+	struct nvmem_cell_entry *cell_entry;
+	struct nvmem_cell *cell;
+	int index = 0;
 
-	index = of_property_match_string(np, "nvmem-cell-names", name);
+	/* if cell name exists, find index to the name */
+	if (id)
+		index = of_property_match_string(np, "nvmem-cell-names", id);
 
 	cell_np = of_parse_phandle(np, "nvmem-cells", index);
 	if (!cell_np)
-		return ERR_PTR(-EINVAL);
+		return ERR_PTR(-ENOENT);
 
 	nvmem_np = of_get_parent(cell_np);
 	if (nvmem_np && of_device_is_compatible(nvmem_np, "fixed-layout"))
 		nvmem_np = of_get_parent(nvmem_np);
-	if (!nvmem_np)
+	if (!nvmem_np) {
+		of_node_put(cell_np);
 		return ERR_PTR(-EINVAL);
-
-	nvmem = __nvmem_device_get(nvmem_np, NULL, NULL);
-	if (IS_ERR(nvmem))
-		return ERR_CAST(nvmem);
-
-	addr = of_get_property(cell_np, "reg", &len);
-	if (!addr || (len < 2 * sizeof(u32))) {
-		dev_err(&nvmem->dev, "nvmem: invalid reg on %pOF\n", cell_np);
-		rval  = -EINVAL;
-		goto err_mem;
-	}
-
-	cell = kzalloc(sizeof(*cell), GFP_KERNEL);
-	if (!cell) {
-		rval = -ENOMEM;
-		goto err_mem;
 	}
 
-	cell->nvmem = nvmem;
-	cell->offset = be32_to_cpup(addr++);
-	cell->bytes = be32_to_cpup(addr);
-	cell->name = cell_np->name;
-	cell->id = kstrdup_const(name, GFP_KERNEL);
-
-	addr = of_get_property(cell_np, "bits", &len);
-	if (addr && len == (2 * sizeof(u32))) {
-		cell->bit_offset = be32_to_cpup(addr++);
-		cell->nbits = be32_to_cpup(addr);
+	nvmem = __nvmem_device_get(nvmem_np);
+	of_node_put(nvmem_np);
+	if (IS_ERR(nvmem)) {
+		of_node_put(cell_np);
+		return ERR_CAST(nvmem);
 	}
 
-	if (cell->nbits)
-		cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
-					   BITS_PER_BYTE);
-
-	if (cell->bytes < nvmem->word_size)
-		cell->bytes = nvmem->word_size;
-
-	if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
-			dev_err(&nvmem->dev,
-				"cell %s unaligned to nvmem stride %d\n",
-				cell->name, nvmem->stride);
-		rval  = -EINVAL;
-		goto err_sanity;
+	cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np);
+	of_node_put(cell_np);
+	if (!cell_entry) {
+		__nvmem_device_put(nvmem);
+		return ERR_PTR(-ENOENT);
 	}
 
-	nvmem_cell_add(cell);
+	cell = nvmem_create_cell(cell_entry, id);
+	if (IS_ERR(cell))
+		__nvmem_device_put(nvmem);
 
 	return cell;
-
-err_sanity:
-	kfree(cell);
-
-err_mem:
-	__nvmem_device_put(nvmem);
-
-	return ERR_PTR(rval);
 }
 EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
 #endif
@@ -465,8 +560,9 @@ EXPORT_SYMBOL_GPL(of_nvmem_cell_get);
 /**
  * nvmem_cell_get() - Get nvmem cell of device form a given cell name
  *
- * @dev node: Device tree node that uses the nvmem cell
- * @id: nvmem cell name to get.
+ * @dev: Device that requests the nvmem cell.
+ * @id: nvmem cell name to get (this corresponds with the name from the
+ *      nvmem-cell-names property for DT systems)
  *
  * Return: Will be an ERR_PTR() on error or a valid pointer
  * to a struct nvmem_cell.  The nvmem_cell will be freed by the
@@ -482,29 +578,31 @@ struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *cell_id)
 			return cell;
 	}
 
-	return nvmem_cell_get_from_list(cell_id);
+	return NULL;
 }
 EXPORT_SYMBOL_GPL(nvmem_cell_get);
 
 /**
  * nvmem_cell_put() - Release previously allocated nvmem cell.
  *
- * @cell: Previously allocated nvmem cell by nvmem_cell_get()
+ * @cell: Previously allocated nvmem cell by nvmem_cell_get().
  */
 void nvmem_cell_put(struct nvmem_cell *cell)
 {
-	struct nvmem_device *nvmem = cell->nvmem;
+	struct nvmem_device *nvmem = cell->entry->nvmem;
+
+	if (cell->id)
+		kfree_const(cell->id);
 
+	kfree(cell);
 	__nvmem_device_put(nvmem);
-	nvmem_cell_drop(cell);
 }
 EXPORT_SYMBOL_GPL(nvmem_cell_put);
 
-static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell,
-						    void *buf)
+static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
 {
 	u8 *p, *b;
-	int i, bit_offset = cell->bit_offset;
+	int i, extra, bit_offset = cell->bit_offset;
 
 	p = b = buf;
 	if (bit_offset) {
@@ -519,22 +617,28 @@ static inline void nvmem_shift_read_buffer_in_place(struct nvmem_cell *cell,
 			p = b;
 			*b++ >>= bit_offset;
 		}
-
-		/* result fits in less bytes */
-		if (cell->bytes != DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE))
-			*p-- = 0;
+	} else {
+		/* point to the msb */
+		p += cell->bytes - 1;
 	}
+
+	/* result fits in less bytes */
+	extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
+	while (--extra >= 0)
+		*p-- = 0;
+
 	/* clear msb bits if any leftover in the last byte */
-	*p &= GENMASK((cell->nbits%BITS_PER_BYTE) - 1, 0);
+	if (cell->nbits % BITS_PER_BYTE)
+		*p &= GENMASK((cell->nbits % BITS_PER_BYTE) - 1, 0);
 }
 
 static int __nvmem_cell_read(struct nvmem_device *nvmem,
-		      struct nvmem_cell *cell,
-		      void *buf, size_t *len)
+			     struct nvmem_cell_entry *cell,
+			     void *buf, size_t *len, const char *id)
 {
 	int rc;
 
-	rc = nvmem->reg_read(nvmem->priv, cell->offset, buf, cell->bytes);
+	rc = nvmem->reg_read(nvmem->priv, cell->offset, buf, cell->raw_len);
 	if (rc < 0)
 		return rc;
 
@@ -543,13 +647,14 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 		nvmem_shift_read_buffer_in_place(cell, buf);
 
 	if (nvmem->cell_post_process) {
-		rc = nvmem->cell_post_process(nvmem->priv, cell->id,
+		rc = nvmem->cell_post_process(nvmem->priv, id,
 					      cell->offset, buf, cell->bytes);
 		if (rc)
 			return rc;
 	}
 
-	*len = cell->bytes;
+	if (len)
+		*len = cell->bytes;
 
 	return 0;
 }
@@ -558,26 +663,28 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
  * nvmem_cell_read() - Read a given nvmem cell
  *
  * @cell: nvmem cell to be read.
- * @len: pointer to length of cell which will be populated on successful read.
+ * @len: pointer to length of cell which will be populated on successful read;
+ *	 can be NULL.
  *
- * Return: ERR_PTR() on error or a valid pointer to a char * buffer on success.
- * The buffer should be freed by the consumer with a kfree().
+ * Return: ERR_PTR() on error or a valid pointer to a buffer on success. The
+ * buffer should be freed by the consumer with a kfree().
  */
 void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
 {
-	struct nvmem_device *nvmem = cell->nvmem;
+	struct nvmem_cell_entry *entry = cell->entry;
+	struct nvmem_device *nvmem = entry->nvmem;
 	u8 *buf;
 	int rc;
 
 	if (!nvmem)
 		return ERR_PTR(-EINVAL);
 
-	buf = kzalloc(cell->bytes, GFP_KERNEL);
+	buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL);
 	if (!buf)
 		return ERR_PTR(-ENOMEM);
 
-	rc = __nvmem_cell_read(nvmem, cell, buf, len);
-	if (rc < 0) {
+	rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id);
+	if (rc) {
 		kfree(buf);
 		return ERR_PTR(rc);
 	}
@@ -586,8 +693,8 @@ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
 }
 EXPORT_SYMBOL_GPL(nvmem_cell_read);
 
-static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
-						    u8 *_buf, int len)
+static inline const void *nvmem_cell_prepare_write_buffer(struct nvmem_cell_entry *cell,
+							  const u8 *_buf, int len)
 {
 	struct nvmem_device *nvmem = cell->nvmem;
 	int i, rc, nbits, bit_offset = cell->bit_offset;
@@ -638,16 +745,7 @@ static inline void *nvmem_cell_prepare_write_buffer(struct nvmem_cell *cell,
 	return buf;
 }
 
-/**
- * nvmem_cell_write() - Write to a given nvmem cell
- *
- * @cell: nvmem cell to be written.
- * @buf: Buffer to be written.
- * @len: length of buffer to be written to nvmem cell.
- *
- * Return: length of bytes written or negative on failure.
- */
-int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
+static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, const void *buf, size_t len)
 {
 	struct nvmem_device *nvmem = cell->nvmem;
 	int rc;
@@ -668,11 +766,25 @@ int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len)
 	if (cell->bit_offset || cell->nbits)
 		kfree(buf);
 
-	if (rc < 0)
+	if (rc)
 		return rc;
 
 	return len;
 }
+
+/**
+ * nvmem_cell_write() - Write to a given nvmem cell
+ *
+ * @cell: nvmem cell to be written.
+ * @buf: Buffer to be written.
+ * @len: length of buffer to be written to nvmem cell.
+ *
+ * Return: length of bytes written or negative on failure.
+ */
+int nvmem_cell_write(struct nvmem_cell *cell, const void *buf, size_t len)
+{
+	return __nvmem_cell_entry_write(cell->entry, buf, len);
+}
 EXPORT_SYMBOL_GPL(nvmem_cell_write);
 
 /**
@@ -688,19 +800,19 @@ EXPORT_SYMBOL_GPL(nvmem_cell_write);
 ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
 			   struct nvmem_cell_info *info, void *buf)
 {
-	struct nvmem_cell cell;
+	struct nvmem_cell_entry cell;
 	int rc;
 	ssize_t len = 0;
 
 	if (!nvmem)
 		return -EINVAL;
 
-	rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
-	if (rc < 0)
+	rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell);
+	if (rc)
 		return rc;
 
-	rc = __nvmem_cell_read(nvmem, &cell, buf, &len);
-	if (rc < 0)
+	rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL);
+	if (rc)
 		return rc;
 
 	return len;
@@ -717,19 +829,19 @@ EXPORT_SYMBOL_GPL(nvmem_device_cell_read);
  * Return: length of bytes written or negative error code on failure.
  * */
 int nvmem_device_cell_write(struct nvmem_device *nvmem,
-			    struct nvmem_cell_info *info, void *buf)
+			    struct nvmem_cell_info *info, const void *buf)
 {
-	struct nvmem_cell cell;
+	struct nvmem_cell_entry cell;
 	int rc;
 
 	if (!nvmem)
 		return -EINVAL;
 
-	rc = nvmem_cell_info_to_nvmem_cell(nvmem, info, &cell);
+	rc = nvmem_cell_info_to_nvmem_cell_entry_nodup(nvmem, info, &cell);
 	if (rc < 0)
 		return rc;
 
-	return nvmem_cell_write(&cell, buf, cell.bytes);
+	return __nvmem_cell_entry_write(&cell, buf, cell.bytes);
 }
 EXPORT_SYMBOL_GPL(nvmem_device_cell_write);
 
diff --git a/include/linux/nvmem-consumer.h b/include/linux/nvmem-consumer.h
index 397c4c29dafde67b0c0f22416852d58a24b61e0a..478f469c35606769358b05257e67e775bd80aa8c 100644
--- a/include/linux/nvmem-consumer.h
+++ b/include/linux/nvmem-consumer.h
@@ -17,14 +17,7 @@ struct device_node;
 /* consumer cookie */
 struct nvmem_cell;
 struct nvmem_device;
-
-struct nvmem_cell_info {
-	const char		*name;
-	unsigned int		offset;
-	unsigned int		bytes;
-	unsigned int		bit_offset;
-	unsigned int		nbits;
-};
+struct nvmem_cell_info;
 
 #if IS_ENABLED(CONFIG_NVMEM)
 
@@ -37,7 +30,7 @@ void *nvmem_cell_get_and_read(struct device_node *np, const char *cell_name,
 int nvmem_cell_read_variable_le_u32(struct device *dev, const char *cell_id,
 				    u32 *val);
 
-int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len);
+int nvmem_cell_write(struct nvmem_cell *cell, const void *buf, size_t len);
 
 /* direct nvmem device read/write interface */
 struct nvmem_device *nvmem_device_get(struct device *dev, const char *name);
@@ -49,7 +42,7 @@ int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset,
 ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
 			       struct nvmem_cell_info *info, void *buf);
 int nvmem_device_cell_write(struct nvmem_device *nvmem,
-			    struct nvmem_cell_info *info, void *buf);
+			    struct nvmem_cell_info *info, const void *buf);
 
 void nvmem_devices_print(void);
 
@@ -85,7 +78,7 @@ static inline int nvmem_cell_read_variable_le_u32(struct device *dev,
 }
 
 static inline int nvmem_cell_write(struct nvmem_cell *cell,
-				    void *buf, size_t len)
+				   const void *buf, size_t len)
 {
 	return -EOPNOTSUPP;
 }
@@ -109,7 +102,7 @@ static inline ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
 
 static inline int nvmem_device_cell_write(struct nvmem_device *nvmem,
 					  struct nvmem_cell_info *info,
-					  void *buf)
+					  const void *buf)
 {
 	return -EOPNOTSUPP;
 }
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index c1765673eb235f8939e9a77d8399902ac22ac4d2..a7d1653afb5805adb4edb6b136bac13d3c63797c 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -16,24 +16,59 @@
 #include <linux/types.h>
 
 struct nvmem_device;
-
+typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
+				void *val, size_t bytes);
+typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
+				 const void *val, size_t bytes);
 /* used for vendor specific post processing of cell data */
 typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id,
 					 unsigned int offset, void *buf,
 					 size_t bytes);
 
+/**
+ * struct nvmem_cell_info - NVMEM cell description
+ * @name:	Name.
+ * @offset:	Offset within the NVMEM device.
+ * @raw_len:	Length of raw data (without post processing).
+ * @bytes:	Length of the cell.
+ * @bit_offset:	Bit offset if cell is smaller than a byte.
+ * @nbits:	Number of bits.
+ * @np:		Optional device_node pointer.
+ */
+struct nvmem_cell_info {
+	const char		*name;
+	unsigned int		offset;
+	size_t			raw_len;
+	unsigned int		bytes;
+	unsigned int		bit_offset;
+	unsigned int		nbits;
+	struct device_node	*np;
+};
+
+/**
+ * struct nvmem_config - NVMEM device configuration
+ *
+ * @dev:	Parent device.
+ * @name:	Optional name.
+ * @id:		Optional device ID used in full name. Ignored if name is NULL.
+ * @read_only:	Device is read-only.
+ * @reg_read:	Callback to read data; return zero if successful.
+ * @reg_write:	Callback to write data; return zero if successful.
+ * @size:	Device size.
+ * @word_size:	Minimum read/write access granularity.
+ * @stride:	Minimum read/write access stride.
+ * @priv:	User context passed to read/write callbacks.
+ */
 struct nvmem_config {
 	struct device		*dev;
 	const char		*name;
 	bool			read_only;
 	struct cdev		*cdev;
-	int			stride;
-	int			word_size;
+	nvmem_reg_read_t	reg_read;
+	nvmem_reg_write_t	reg_write;
 	int			size;
-	int			(*reg_write)(void *ctx, unsigned int reg,
-					     const void *val, size_t val_size);
-	int			(*reg_read)(void *ctx, unsigned int reg,
-					    void *val, size_t val_size);
+	int			word_size;
+	int			stride;
 	void			*priv;
 	nvmem_cell_post_process_t cell_post_process;
 };
@@ -48,6 +83,8 @@ struct nvmem_device *nvmem_regmap_register(struct regmap *regmap, const char *na
 struct nvmem_device *nvmem_regmap_register_with_pp(struct regmap *regmap,
 		const char *name, nvmem_cell_post_process_t cell_post_process);
 struct device *nvmem_device_get_device(struct nvmem_device *nvmem);
+int nvmem_add_one_cell(struct nvmem_device *nvmem,
+		       const struct nvmem_cell_info *info);
 
 #else
 
@@ -72,5 +109,12 @@ static inline struct device *nvmem_device_get_device(struct nvmem_device *nvmem)
 {
 	return ERR_PTR(-ENOSYS);
 }
+
+static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
+				     const struct nvmem_cell_info *info)
+{
+	return -EOPNOTSUPP;
+}
+
 #endif /* CONFIG_NVMEM */
 #endif  /* ifndef _LINUX_NVMEM_PROVIDER_H */

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 06/15] nvmem: core: expose nvmem cells as cdev
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (4 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 05/15] nvmem: core: sync with Linux Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 07/15] nvmem: core: allow single and dynamic device ids Marco Felsch
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Linux has added the support to expose nvmem-cells via the sysfs by
commit 0331c611949f ("nvmem: core: Expose cells through sysfs"). This
commit adds an equivalent mechanism by exposing the nvmem-cells via
cdevs. The name scheme is: <nvmem-dev-name>.<cell-name>.

With this in place it is possible for board code and/or shell to query
the nvmem-cell values without specifying the consumer API within DT. At
the moment the access is RO like Linux does since write support requires
more corner case handling.

The naming scheme for nvmem_populate_sysfs_cells() was kept to reduce the
Linux diff.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 93 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index c9afbb242f945df8a770b783bb02c8132dc59347..328e2839cc47326de53a5950ffaa676926882c32 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -43,6 +43,8 @@ struct nvmem_cell_entry {
 	struct device_node	*np;
 	struct nvmem_device	*nvmem;
 	struct list_head	node;
+
+	struct cdev		cdev;
 };
 
 struct nvmem_cell {
@@ -136,6 +138,71 @@ static struct nvmem_device *of_nvmem_find(struct device_node *nvmem_np)
 	return NULL;
 }
 
+static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
+					    const char *id, int index);
+
+static ssize_t nvmem_cell_cdev_read(struct cdev *cdev, void *buf, size_t count,
+				    loff_t offset, unsigned long flags)
+{
+	struct nvmem_cell_entry *entry;
+	struct nvmem_cell *cell = NULL;
+	size_t cell_sz, read_len;
+	void *content;
+
+	entry = container_of(cdev, struct nvmem_cell_entry, cdev);
+	cell = nvmem_create_cell(entry, entry->name, 0);
+	if (IS_ERR(cell))
+		return PTR_ERR(cell);
+
+	content = nvmem_cell_read(cell, &cell_sz);
+	if (IS_ERR(content)) {
+		read_len = PTR_ERR(content);
+		goto destroy_cell;
+	}
+
+	read_len = min_t(unsigned int, cell_sz - offset, count);
+	memcpy(buf, content + offset, read_len);
+	kfree(content);
+
+destroy_cell:
+	kfree_const(cell->id);
+	kfree(cell);
+
+	return read_len;
+}
+
+static struct cdev_operations nvmem_cell_chrdev_ops = {
+	.read  = nvmem_cell_cdev_read,
+};
+
+static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
+{
+	struct device *dev = &nvmem->dev;
+	struct nvmem_cell_entry *entry;
+
+	list_for_each_entry(entry, &nvmem->cells, node) {
+		struct cdev *cdev;
+		int ret;
+
+		cdev = &entry->cdev;
+		cdev->name = xasprintf("%s.%s", dev_name(dev),
+				       kbasename(entry->name));
+		cdev->ops = &nvmem_cell_chrdev_ops;
+		cdev->dev = dev;
+		cdev->size = entry->bytes;
+
+		/* Ignore the error but throw a warning for the user */
+		ret = devfs_create(cdev);
+		if (ret) {
+			dev_err(dev, "Failed to register %s with %pe\n",
+				cdev->name, ERR_PTR(ret));
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
 static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
 {
 	list_del(&cell->node);
@@ -152,6 +219,26 @@ static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
 		nvmem_cell_entry_drop(cell);
 }
 
+static void nvmem_device_remove_all_cdevs(struct nvmem_device *nvmem)
+{
+	struct nvmem_cell_entry *cell, *p;
+
+	list_for_each_entry_safe(cell, p, &nvmem->cells, node) {
+		if (cell->cdev.name && cdev_by_name(cell->cdev.name)) {
+			devfs_remove(&cell->cdev);
+			free(cell->cdev.name);
+			cell->cdev.name = NULL;
+		}
+	}
+
+	if (!nvmem->cdev.name)
+		return;
+
+	devfs_remove(&nvmem->cdev);
+	free(nvmem->cdev.name);
+	nvmem->cdev.name = NULL;
+}
+
 static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
 {
 	list_add_tail(&cell->node, &cell->nvmem->cells);
@@ -338,10 +425,16 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 			goto err_unregister;
 	}
 
+	rval = nvmem_populate_sysfs_cells(nvmem);
+	if (rval)
+		goto err_remove_cdevs;
+
 	list_add_tail(&nvmem->node, &nvmem_devs);
 
 	return nvmem;
 
+err_remove_cdevs:
+	nvmem_device_remove_all_cdevs(nvmem);
 err_unregister:
 	unregister_device(&nvmem->dev);
 err_remove_cells:

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 07/15] nvmem: core: allow single and dynamic device ids
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (5 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 06/15] nvmem: core: expose nvmem cells as cdev Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 08/15] eeprom: at24: fix device name handling Marco Felsch
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

At the moment only DEVICE_ID_DYNAMIC is supported. Add support for
NVMEM_DEVID_NONE to allow static device names via of-aliases and to
align our core with Linux.

While on it fix the nvmem_register_cdev() by using the dev_name() which
honors the DEVICE_ID_* cases else we end up with different names for the
devfs and the device itself.

The dev_dbg() message can be dropped as well since the register_device()
can print this message too.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 16 +++++++++++-----
 include/linux/nvmem-provider.h |  4 ++++
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 328e2839cc47326de53a5950ffaa676926882c32..29a18cbeb517bdf23a8620f19a0ec21a1ac1b4e2 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -410,17 +410,23 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	if (config->read_only || !config->reg_write || of_property_read_bool(np, "read-only"))
 		nvmem->read_only = true;
 
-	dev_set_name(&nvmem->dev, "%s", config->name);
-	nvmem->dev.id = DEVICE_ID_DYNAMIC;
-
-	dev_dbg(nvmem->dev.parent, "Registering nvmem device %s\n", config->name);
+	dev_set_name(&nvmem->dev, "%s", config->name ? : "nvmem");
+	switch (config->id) {
+	case NVMEM_DEVID_NONE:
+		nvmem->dev.id = DEVICE_ID_SINGLE;
+		break;
+	case NVMEM_DEVID_AUTO:
+	default:
+		nvmem->dev.id = DEVICE_ID_DYNAMIC;
+		break;
+	}
 
 	rval = register_device(&nvmem->dev);
 	if (rval)
 		goto err_remove_cells;
 
 	if (!config->cdev) {
-		rval = nvmem_register_cdev(nvmem, config->name);
+		rval = nvmem_register_cdev(nvmem, dev_name(&nvmem->dev));
 		if (rval)
 			goto err_unregister;
 	}
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index a7d1653afb5805adb4edb6b136bac13d3c63797c..08e16d49497a9e71f55e3827a0b28c6c6f9e24ae 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -25,6 +25,9 @@ typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id,
 					 unsigned int offset, void *buf,
 					 size_t bytes);
 
+#define NVMEM_DEVID_NONE	(-1)
+#define NVMEM_DEVID_AUTO	(-2)
+
 /**
  * struct nvmem_cell_info - NVMEM cell description
  * @name:	Name.
@@ -62,6 +65,7 @@ struct nvmem_cell_info {
 struct nvmem_config {
 	struct device		*dev;
 	const char		*name;
+	int			id;
 	bool			read_only;
 	struct cdev		*cdev;
 	nvmem_reg_read_t	reg_read;

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 08/15] eeprom: at24: fix device name handling
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (6 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 07/15] nvmem: core: allow single and dynamic device ids Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 09/15] nvmem: core: create a header for internal sharing Marco Felsch
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

The nvmem_core already handles the ids/numbering, there is no need for
the driver to do it too. Furthermore it's wrong because the final device
would be: eeprom00 instead of eeprom0.

While on it fix the alias name handlng too since the device is never
named as described by the alias, but 'alias<ID>'. Since the
nvmem_core supports NVMEM_DEVID_{NONE,AUTO}, pass this information to
the core so the name would be: 'alias'.

Also while on it, make devname a 'const char *' pointer since the name is
allocated later on by the dev_set_name() during the nvmem_register()
call.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/eeprom/at24.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/drivers/eeprom/at24.c b/drivers/eeprom/at24.c
index 3054709ec055665a82b22c1b210a2bfc0910ca94..0a9aa75cf1dd63f6f97e90509b76e25d04ec361d 100644
--- a/drivers/eeprom/at24.c
+++ b/drivers/eeprom/at24.c
@@ -371,7 +371,7 @@ static int at24_probe(struct device *dev)
 	struct at24_data *at24;
 	int err;
 	unsigned i, num_addresses;
-	char *devname;
+	const char *devname;
 	const char *alias;
 
 	if (dev->platform_data) {
@@ -425,16 +425,10 @@ static int at24_probe(struct device *dev)
 	at24->num_addresses = num_addresses;
 
 	alias = of_alias_get(dev->of_node);
-	if (alias) {
-		devname = xstrdup(alias);
-	} else {
-		err = cdev_find_free_index("eeprom");
-		if (err < 0) {
-			dev_err(&client->dev, "no index found to name device\n");
-			goto err_device_name;
-		}
-		devname = xasprintf("eeprom%d", err);
-	}
+	if (alias)
+		devname = alias;
+	else
+		devname = "eeprom";
 
 	writable = !(chip.flags & AT24_FLAG_READONLY);
 
@@ -489,6 +483,7 @@ static int at24_probe(struct device *dev)
 	at24->nvmem_config.stride = 1;
 	at24->nvmem_config.word_size = 1;
 	at24->nvmem_config.size = chip.byte_len;
+	at24->nvmem_config.id = alias ? NVMEM_DEVID_NONE : NVMEM_DEVID_AUTO;
 
 	at24->nvmem = nvmem_register(&at24->nvmem_config);
 	err = PTR_ERR_OR_ZERO(at24->nvmem);
@@ -505,7 +500,6 @@ static int at24_probe(struct device *dev)
 	if (gpio_is_valid(at24->wp_gpio))
 		gpio_free(at24->wp_gpio);
 	kfree(at24->writebuf);
-err_device_name:
 	kfree(at24);
 err_out:
 	dev_dbg(&client->dev, "probe error %d\n", err);

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 09/15] nvmem: core: create a header for internal sharing
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (7 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 08/15] eeprom: at24: fix device name handling Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 10/15] nvmem: core: add nvmem-layout support Marco Felsch
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Port Linux commit:

| commit ec9c08a1cb8dc5e8e003f95f5f62de41dde235bb
| Author: Miquel Raynal <miquel.raynal@bootlin.com>
| Date:   Fri Dec 15 11:15:29 2023 +0000
|
|     nvmem: Create a header for internal sharing
|
|     Before adding all the NVMEM layout bus infrastructure to the core, let's
|     move the main nvmem_device structure in an internal header, only
|     available to the core. This way all the additional code can be added in
|     a dedicated file in order to keep the current core file tidy.
|
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20231215111536.316972-4-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

which is required for the upcoming nvmem-layout support.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c      | 20 +-------------------
 drivers/nvmem/internals.h | 30 ++++++++++++++++++++++++++++++
 2 files changed, 31 insertions(+), 19 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 29a18cbeb517bdf23a8620f19a0ec21a1ac1b4e2..86f4f43a3084bf0c0e71b9af002334975c5e5e0e 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -13,25 +13,7 @@
 #include <linux/nvmem-consumer.h>
 #include <linux/nvmem-provider.h>
 
-struct nvmem_device {
-	const char		*name;
-	struct device		dev;
-	struct list_head	node;
-	int			stride;
-	int			word_size;
-	int			ncells;
-	int			users;
-	size_t			size;
-	bool			read_only;
-	struct cdev		cdev;
-	void			*priv;
-	struct list_head	cells;
-	nvmem_cell_post_process_t cell_post_process;
-	int			(*reg_write)(void *ctx, unsigned int reg,
-					     const void *val, size_t val_size);
-	int			(*reg_read)(void *ctx, unsigned int reg,
-					    void *val, size_t val_size);
-};
+#include "internals.h"
 
 struct nvmem_cell_entry {
 	const char		*name;
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
new file mode 100644
index 0000000000000000000000000000000000000000..4faa9a7f9e76e93cb67192ab11475f466df31473
--- /dev/null
+++ b/drivers/nvmem/internals.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _LINUX_NVMEM_INTERNALS_H
+#define _LINUX_NVMEM_INTERNALS_H
+
+#include <device.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+
+struct nvmem_device {
+	const char		*name;
+	struct device		dev;
+	struct list_head	node;
+	int			stride;
+	int			word_size;
+	int			ncells;
+	int			users;
+	size_t			size;
+	bool			read_only;
+	struct cdev		cdev;
+	void			*priv;
+	struct list_head	cells;
+	nvmem_cell_post_process_t cell_post_process;
+	int			(*reg_write)(void *ctx, unsigned int reg,
+					     const void *val, size_t val_size);
+	int			(*reg_read)(void *ctx, unsigned int reg,
+					    void *val, size_t val_size);
+};
+
+#endif  /* ifndef _LINUX_NVMEM_INTERNALS_H */

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 10/15] nvmem: core: add nvmem-layout support
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (8 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 09/15] nvmem: core: create a header for internal sharing Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 11/15] nvmem: core: add an index parameter to the cell Marco Felsch
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

This ports the Linux nvmem-layout core infrastructure required for the
actual nvmem-layout drivers. This commit doesn't add any specific
nvmem-layout driver.

A nvmem-layout is the new way to describe the nvmem storage format. The
nvmem-layout driver parses the nvmem sotrage format and provides the
information via nvmem-cells.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/Kconfig          |   7 ++
 drivers/nvmem/Makefile         |   3 +
 drivers/nvmem/core.c           | 100 +++++++++++++++++++++++-
 drivers/nvmem/internals.h      |  22 ++++++
 drivers/nvmem/layouts.c        | 173 +++++++++++++++++++++++++++++++++++++++++
 drivers/nvmem/layouts/Kconfig  |  13 ++++
 drivers/nvmem/layouts/Makefile |   7 ++
 include/linux/nvmem-provider.h |  63 +++++++++++++++
 8 files changed, 385 insertions(+), 3 deletions(-)

diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 349471ce393275298acbe684328f731260a5f2ba..41a3bf26dda04155e7457a5bd9473ff4be646574 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -1,6 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 menuconfig NVMEM
 	bool "NVMEM Support"
+	imply NVMEM_LAYOUTS
 	help
 	  Support for NVMEM(Non Volatile Memory) devices like EEPROM, EFUSES...
 
@@ -10,6 +11,12 @@ menuconfig NVMEM
 
 if NVMEM
 
+# Layouts
+
+source "drivers/nvmem/layouts/Kconfig"
+
+# Devices
+
 config NVMEM_RMEM
 	bool "Reserved Memory Based Driver Support"
 	help
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 31db05e5a71c4fd398f84e08bf45da1e6ba23312..fdd5c63e321db009f5eaa282e0b37fe0c8f28572 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -5,6 +5,9 @@
 
 obj-$(CONFIG_NVMEM)		+= nvmem_core.o
 nvmem_core-y			:= core.o regmap.o partition.o
+obj-$(CONFIG_NVMEM_LAYOUTS)	+= nvmem_layouts.o
+nvmem_layouts-y			:= layouts.o
+obj-y				+= layouts/
 
 obj-$(CONFIG_NVMEM_RMEM)	+= rmem.o
 
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 86f4f43a3084bf0c0e71b9af002334975c5e5e0e..bd80283f2be061c55c286006f6f318eccb10897e 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -35,6 +35,7 @@ struct nvmem_cell {
 };
 
 static LIST_HEAD(nvmem_devs);
+static LIST_HEAD(nvmem_layouts);
 
 void nvmem_devices_print(void)
 {
@@ -162,6 +163,9 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
 	struct device *dev = &nvmem->dev;
 	struct nvmem_cell_entry *entry;
 
+	if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated)
+		return 0;
+
 	list_for_each_entry(entry, &nvmem->cells, node) {
 		struct cdev *cdev;
 		int ret;
@@ -182,6 +186,8 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
 		}
 	}
 
+	nvmem->sysfs_cells_populated = true;
+
 	return 0;
 }
 
@@ -351,6 +357,52 @@ static int nvmem_add_cells_from_legacy_of(struct nvmem_device *nvmem)
 	return nvmem_add_cells_from_dt(nvmem, nvmem->dev.of_node);
 }
 
+static int nvmem_add_cells_from_fixed_layout(struct nvmem_device *nvmem)
+{
+	struct device_node *layout_np;
+	int err = 0;
+
+	layout_np = of_nvmem_layout_get_container(nvmem);
+	if (!layout_np)
+		return 0;
+
+	if (of_device_is_compatible(layout_np, "fixed-layout"))
+		err = nvmem_add_cells_from_dt(nvmem, layout_np);
+
+	of_node_put(layout_np);
+
+	return err;
+}
+
+int nvmem_layout_register(struct nvmem_layout *layout)
+{
+	int ret;
+
+	if (!layout->add_cells)
+		return -EINVAL;
+
+	/* Populate the cells */
+	ret = layout->add_cells(layout);
+	if (ret)
+		return ret;
+
+	ret = nvmem_populate_sysfs_cells(layout->nvmem);
+	if (ret) {
+		nvmem_device_remove_all_cdevs(layout->nvmem);
+		nvmem_device_remove_all_cells(layout->nvmem);
+		return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_register);
+
+void nvmem_layout_unregister(struct nvmem_layout *layout)
+{
+	/* Keep the API even with an empty stub in case we need it later */
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_unregister);
+
 /**
  * nvmem_register() - Register a nvmem device for given nvmem_config.
  *
@@ -389,6 +441,10 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	if (rval)
 		goto err_remove_cells;
 
+	rval = nvmem_add_cells_from_fixed_layout(nvmem);
+	if (rval)
+		goto err_remove_cells;
+
 	if (config->read_only || !config->reg_write || of_property_read_bool(np, "read-only"))
 		nvmem->read_only = true;
 
@@ -413,14 +469,20 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 			goto err_unregister;
 	}
 
-	rval = nvmem_populate_sysfs_cells(nvmem);
+	rval = nvmem_populate_layout(nvmem);
 	if (rval)
 		goto err_remove_cdevs;
 
+	rval = nvmem_populate_sysfs_cells(nvmem);
+	if (rval)
+		goto err_destroy_layout;
+
 	list_add_tail(&nvmem->node, &nvmem_devs);
 
 	return nvmem;
 
+err_destroy_layout:
+	nvmem_destroy_layout(nvmem);
 err_remove_cdevs:
 	nvmem_device_remove_all_cdevs(nvmem);
 err_unregister:
@@ -579,6 +641,17 @@ nvmem_find_cell_entry_by_node(struct nvmem_device *nvmem, struct device_node *np
 	return cell;
 }
 
+static int nvmem_layout_module_get_optional(struct nvmem_device *nvmem)
+{
+	if (!nvmem->layout)
+		return 0;
+
+	if (!nvmem->layout->dev.driver)
+		return -EPROBE_DEFER;
+
+	return 0;
+}
+
 /**
  * of_nvmem_cell_get() - Get a nvmem cell from given device node and cell id
  *
@@ -608,13 +681,20 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
 		return ERR_PTR(-ENOENT);
 
 	nvmem_np = of_get_parent(cell_np);
-	if (nvmem_np && of_device_is_compatible(nvmem_np, "fixed-layout"))
-		nvmem_np = of_get_parent(nvmem_np);
 	if (!nvmem_np) {
 		of_node_put(cell_np);
 		return ERR_PTR(-EINVAL);
 	}
 
+	/* nvmem layouts produce cells within the nvmem-layout container */
+	if (of_node_name_eq(nvmem_np, "nvmem-layout")) {
+		nvmem_np = of_get_parent(nvmem_np);
+		if (!nvmem_np) {
+			of_node_put(cell_np);
+			return ERR_PTR(-EINVAL);
+		}
+	}
+
 	nvmem = __nvmem_device_get(nvmem_np);
 	of_node_put(nvmem_np);
 	if (IS_ERR(nvmem)) {
@@ -622,6 +702,13 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
 		return ERR_CAST(nvmem);
 	}
 
+	ret = nvmem_layout_module_get_optional(nvmem);
+	if (ret) {
+		of_node_put(cell_np);
+		__nvmem_device_put(nvmem);
+		return ERR_PTR(ret);
+	}
+
 	cell_entry = nvmem_find_cell_entry_by_node(nvmem, cell_np);
 	of_node_put(cell_np);
 	if (!cell_entry) {
@@ -1057,3 +1144,10 @@ struct device *nvmem_device_get_device(struct nvmem_device *nvmem)
 	return &nvmem->dev;
 }
 EXPORT_SYMBOL_GPL(nvmem_device_get_device);
+
+static int __init nvmem_init(void)
+{
+	/* TODO: add support for the NVMEM bus too */
+	return nvmem_layout_bus_register();
+}
+pure_initcall(nvmem_init);
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4faa9a7f9e76e93cb67192ab11475f466df31473..97ffdfe79dfe6c5459d1bba5ba717c861678e9a2 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -19,12 +19,34 @@ struct nvmem_device {
 	bool			read_only;
 	struct cdev		cdev;
 	void			*priv;
+	struct nvmem_layout	*layout;
 	struct list_head	cells;
 	nvmem_cell_post_process_t cell_post_process;
 	int			(*reg_write)(void *ctx, unsigned int reg,
 					     const void *val, size_t val_size);
 	int			(*reg_read)(void *ctx, unsigned int reg,
 					    void *val, size_t val_size);
+	bool			sysfs_cells_populated;
 };
 
+#if IS_ENABLED(CONFIG_OF)
+int nvmem_layout_bus_register(void);
+int nvmem_populate_layout(struct nvmem_device *nvmem);
+void nvmem_destroy_layout(struct nvmem_device *nvmem);
+#else /* CONFIG_OF */
+static inline int nvmem_layout_bus_register(void)
+{
+	return 0;
+}
+
+static inline void nvmem_layout_bus_unregister(void) {}
+
+static inline int nvmem_populate_layout(struct nvmem_device *nvmem)
+{
+	return 0;
+}
+
+static inline void nvmem_destroy_layout(struct nvmem_device *nvmem) { }
+#endif /* CONFIG_OF */
+
 #endif  /* ifndef _LINUX_NVMEM_INTERNALS_H */
diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c
new file mode 100644
index 0000000000000000000000000000000000000000..dfb7dc35895ad249585224ae69a39069ea852a1b
--- /dev/null
+++ b/drivers/nvmem/layouts.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * NVMEM layout bus handling
+ *
+ * Copyright (C) 2023 Bootlin
+ * Author: Miquel Raynal <miquel.raynal@bootlin.com
+ */
+
+#include <device.h>
+#include <of_device.h>
+#include <module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/nvmem-provider.h>
+
+#include "internals.h"
+
+#define to_nvmem_layout_driver(drv) \
+	(container_of_const((drv), struct nvmem_layout_driver, driver))
+#define to_nvmem_layout_device(_dev) \
+	container_of((_dev), struct nvmem_layout, dev)
+
+static int nvmem_layout_bus_match(struct device *dev, const struct device_driver *drv)
+{
+	return of_driver_match_device(dev, drv);
+}
+
+static int nvmem_layout_bus_probe(struct device *dev)
+{
+	struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver);
+	struct nvmem_layout *layout = to_nvmem_layout_device(dev);
+
+	if (!drv->probe || !drv->remove)
+		return -EINVAL;
+
+	return drv->probe(layout);
+}
+
+static void nvmem_layout_bus_remove(struct device *dev)
+{
+	struct nvmem_layout_driver *drv = to_nvmem_layout_driver(dev->driver);
+	struct nvmem_layout *layout = to_nvmem_layout_device(dev);
+
+	return drv->remove(layout);
+}
+
+static struct bus_type nvmem_layout_bus_type = {
+	.name		= "nvmem-layout",
+	.match		= nvmem_layout_bus_match,
+	.probe		= nvmem_layout_bus_probe,
+	.remove		= nvmem_layout_bus_remove,
+};
+
+int nvmem_layout_driver_register(struct nvmem_layout_driver *drv)
+{
+	drv->driver.bus = &nvmem_layout_bus_type;
+
+	return register_driver(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(nvmem_layout_driver_register);
+
+static int nvmem_layout_create_device(struct nvmem_device *nvmem,
+				      struct device_node *np)
+{
+	struct nvmem_layout *layout;
+	struct device *dev;
+	int ret;
+
+	layout = kzalloc(sizeof(*layout), GFP_KERNEL);
+	if (!layout)
+		return -ENOMEM;
+
+	/* Create a bidirectional link */
+	layout->nvmem = nvmem;
+	nvmem->layout = layout;
+
+	/* Device model registration */
+	dev = &layout->dev;
+	dev->parent = &nvmem->dev;
+	dev->bus = &nvmem_layout_bus_type;
+	dev->dma_mask = DMA_BIT_MASK(32);
+	dev->of_node = of_node_get(np);
+	of_device_make_bus_id(dev);
+
+	ret = register_device(dev);
+	if (ret) {
+		put_device(dev);
+		return ret;
+	}
+
+	np->dev = dev;
+
+	return 0;
+}
+
+static const struct of_device_id of_nvmem_layout_skip_table[] = {
+	{ .compatible = "fixed-layout", },
+	{}
+};
+
+static int nvmem_layout_bus_populate(struct nvmem_device *nvmem,
+				     struct device_node *layout_dn)
+{
+	int ret;
+
+	/* Make sure it has a compatible property */
+	if (!of_property_present(layout_dn, "compatible")) {
+		pr_debug("%s() - skipping %pOF, no compatible prop\n",
+			 __func__, layout_dn);
+		return 0;
+	}
+
+	/* Fixed layouts are parsed manually somewhere else for now */
+	if (of_match_node(of_nvmem_layout_skip_table, layout_dn)) {
+		pr_debug("%s() - skipping %pOF node\n", __func__, layout_dn);
+		return 0;
+	}
+
+	if (layout_dn->dev) {
+		pr_debug("%s() - skipping %pOF, already populated\n",
+			 __func__, layout_dn);
+
+		return 0;
+	}
+
+	/* NVMEM layout buses expect only a single device representing the layout */
+	ret = nvmem_layout_create_device(nvmem, layout_dn);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+{
+	return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+}
+EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
+
+/*
+ * Returns the number of devices populated, 0 if the operation was not relevant
+ * for this nvmem device, an error code otherwise.
+ */
+int nvmem_populate_layout(struct nvmem_device *nvmem)
+{
+	struct device_node *layout_dn;
+	int ret;
+
+	layout_dn = of_nvmem_layout_get_container(nvmem);
+	if (!layout_dn)
+		return 0;
+
+	/* Populate the layout device */
+	ret = nvmem_layout_bus_populate(nvmem, layout_dn);
+
+	of_node_put(layout_dn);
+	return ret;
+}
+
+void nvmem_destroy_layout(struct nvmem_device *nvmem)
+{
+	struct device *dev;
+
+	if (!nvmem->layout)
+		return;
+
+	dev = &nvmem->layout->dev;
+	unregister_device(dev);
+}
+
+int nvmem_layout_bus_register(void)
+{
+	return bus_register(&nvmem_layout_bus_type);
+}
diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..5b2848b23825d70ecd1026819e51d2612681e3f9
--- /dev/null
+++ b/drivers/nvmem/layouts/Kconfig
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config NVMEM_LAYOUTS
+	bool
+	depends on OF
+
+if NVMEM_LAYOUTS
+
+menu "Layout Types"
+
+endmenu
+
+endif
diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..2202410b966b96a4cd0b992d04d62b7bc8e70d84
--- /dev/null
+++ b/drivers/nvmem/layouts/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for nvmem layouts.
+#
+
+# Dummy, can be delete once we do have a driver
+obj- := __dummy__.o
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 08e16d49497a9e71f55e3827a0b28c6c6f9e24ae..eca8cd0400a2d20a9c62ec59d6031e8508025a81 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -14,6 +14,7 @@
 
 #include <common.h>
 #include <linux/types.h>
+#include <linux/device.h>
 
 struct nvmem_device;
 typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
@@ -77,6 +78,32 @@ struct nvmem_config {
 	nvmem_cell_post_process_t cell_post_process;
 };
 
+/**
+ * struct nvmem_layout - NVMEM layout definitions
+ *
+ * @dev:		Device-model layout device.
+ * @nvmem:		The underlying NVMEM device
+ * @add_cells:		Will be called if a nvmem device is found which
+ *			has this layout. The function will add layout
+ *			specific cells with nvmem_add_one_cell().
+ *
+ * A nvmem device can hold a well defined structure which can just be
+ * evaluated during runtime. For example a TLV list, or a list of "name=val"
+ * pairs. A nvmem layout can parse the nvmem device and add appropriate
+ * cells.
+ */
+struct nvmem_layout {
+	struct device dev;
+	struct nvmem_device *nvmem;
+	int (*add_cells)(struct nvmem_layout *layout);
+};
+
+struct nvmem_layout_driver {
+	struct device_driver driver;
+	int (*probe)(struct nvmem_layout *layout);
+	void (*remove)(struct nvmem_layout *layout);
+};
+
 struct regmap;
 struct cdev;
 
@@ -90,6 +117,13 @@ struct device *nvmem_device_get_device(struct nvmem_device *nvmem);
 int nvmem_add_one_cell(struct nvmem_device *nvmem,
 		       const struct nvmem_cell_info *info);
 
+int nvmem_layout_register(struct nvmem_layout *layout);
+void nvmem_layout_unregister(struct nvmem_layout *layout);
+
+int nvmem_layout_driver_register(struct nvmem_layout_driver *drv);
+#define module_nvmem_layout_driver(drv)		\
+	register_driver_macro(device, nvmem_layout, drv)
+
 #else
 
 static inline struct nvmem_device *nvmem_register(const struct nvmem_config *c)
@@ -120,5 +154,34 @@ static inline int nvmem_add_one_cell(struct nvmem_device *nvmem,
 	return -EOPNOTSUPP;
 }
 
+static inline int nvmem_layout_register(struct nvmem_layout *layout)
+{
+	return -EOPNOTSUPP;
+}
+
+static inline void nvmem_layout_unregister(struct nvmem_layout *layout) {}
+
 #endif /* CONFIG_NVMEM */
+
+#if IS_ENABLED(CONFIG_NVMEM) && IS_ENABLED(CONFIG_OF)
+
+/**
+ * of_nvmem_layout_get_container() - Get OF node of layout container
+ *
+ * @nvmem: nvmem device
+ *
+ * Return: a node pointer with refcount incremented or NULL if no
+ * container exists. Use of_node_put() on it when done.
+ */
+struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem);
+
+#else  /* CONFIG_NVMEM && CONFIG_OF */
+
+static inline struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
+{
+	return NULL;
+}
+
+#endif /* CONFIG_NVMEM && CONFIG_OF */
+
 #endif  /* ifndef _LINUX_NVMEM_PROVIDER_H */

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 11/15] nvmem: core: add an index parameter to the cell
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (9 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 10/15] nvmem: core: add nvmem-layout support Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 12/15] nvmem: core: add per-cell post processing Marco Felsch
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

To further  sync the nvmem code base port Linux commit:

| commit 5d8e6e6c10a3d37486d263b16ddc15991a7e4a88
| Author: Michael Walle <michael@walle.cc>
| Date:   Mon Feb 6 13:43:46 2023 +0000
|
|     nvmem: core: add an index parameter to the cell
|
|     Sometimes a cell can represend multiple values. For example, a base
|     ethernet address stored in the NVMEM can be expanded into multiple
|     discreet ones by adding an offset.
|
|     For this use case, introduce an index parameter which is then used to
|     distiguish between values. This parameter will then be passed to the
|     post process hook which can then use it to create different values
|     during reading.
|
|     At the moment, there is only support for the device tree path. You can
|     add the index to the phandle, e.g.
|
|       &net {
|               nvmem-cells = <&base_mac_address 2>;
|               nvmem-cell-names = "mac-address";
|       };
|
|       &nvmem_provider {
|               base_mac_address: base-mac-address@0 {
|                       #nvmem-cell-cells = <1>;
|                       reg = <0 6>;
|               };
|       };
|
|     Signed-off-by: Michael Walle <michael@walle.cc>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20230206134356.839737-13-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 33 ++++++++++++++++++++++++---------
 drivers/nvmem/imx-ocotp-ele.c  |  4 ++--
 drivers/nvmem/ocotp.c          |  4 ++--
 include/linux/nvmem-provider.h |  2 +-
 4 files changed, 29 insertions(+), 14 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index bd80283f2be061c55c286006f6f318eccb10897e..abb576af01ae0f8fc2d1e2f30ec9d0d22cd2a4fa 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -32,6 +32,7 @@ struct nvmem_cell_entry {
 struct nvmem_cell {
 	struct nvmem_cell_entry *entry;
 	const char		*id;
+	int			index;
 };
 
 static LIST_HEAD(nvmem_devs);
@@ -602,7 +603,8 @@ void nvmem_device_put(struct nvmem_device *nvmem)
 }
 EXPORT_SYMBOL_GPL(nvmem_device_put);
 
-static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, const char *id)
+static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry,
+					    const char *id, int index)
 {
 	struct nvmem_cell *cell;
 	const char *name = NULL;
@@ -621,6 +623,7 @@ static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, cons
 
 	cell->id = name;
 	cell->entry = entry;
+	cell->index = index;
 
 	return cell;
 }
@@ -670,15 +673,27 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
 	struct nvmem_device *nvmem;
 	struct nvmem_cell_entry *cell_entry;
 	struct nvmem_cell *cell;
+	struct of_phandle_args cell_spec;
 	int index = 0;
+	int cell_index = 0;
+	int ret;
 
 	/* if cell name exists, find index to the name */
 	if (id)
 		index = of_property_match_string(np, "nvmem-cell-names", id);
 
-	cell_np = of_parse_phandle(np, "nvmem-cells", index);
-	if (!cell_np)
-		return ERR_PTR(-ENOENT);
+	ret = of_parse_phandle_with_optional_args(np, "nvmem-cells",
+						  "#nvmem-cell-cells",
+						  index, &cell_spec);
+	if (ret)
+		return ERR_PTR(ret);
+
+	if (cell_spec.args_count > 1)
+		return ERR_PTR(-EINVAL);
+
+	cell_np = cell_spec.np;
+	if (cell_spec.args_count)
+		cell_index = cell_spec.args[0];
 
 	nvmem_np = of_get_parent(cell_np);
 	if (!nvmem_np) {
@@ -716,7 +731,7 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id)
 		return ERR_PTR(-ENOENT);
 	}
 
-	cell = nvmem_create_cell(cell_entry, id);
+	cell = nvmem_create_cell(cell_entry, id, cell_index);
 	if (IS_ERR(cell))
 		__nvmem_device_put(nvmem);
 
@@ -802,7 +817,7 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void
 
 static int __nvmem_cell_read(struct nvmem_device *nvmem,
 			     struct nvmem_cell_entry *cell,
-			     void *buf, size_t *len, const char *id)
+			     void *buf, size_t *len, const char *id, int index)
 {
 	int rc;
 
@@ -815,7 +830,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 		nvmem_shift_read_buffer_in_place(cell, buf);
 
 	if (nvmem->cell_post_process) {
-		rc = nvmem->cell_post_process(nvmem->priv, id,
+		rc = nvmem->cell_post_process(nvmem->priv, id, index,
 					      cell->offset, buf, cell->bytes);
 		if (rc)
 			return rc;
@@ -851,7 +866,7 @@ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len)
 	if (!buf)
 		return ERR_PTR(-ENOMEM);
 
-	rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id);
+	rc = __nvmem_cell_read(nvmem, cell->entry, buf, len, cell->id, cell->index);
 	if (rc) {
 		kfree(buf);
 		return ERR_PTR(rc);
@@ -979,7 +994,7 @@ ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem,
 	if (rc)
 		return rc;
 
-	rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL);
+	rc = __nvmem_cell_read(nvmem, &cell, buf, &len, NULL, 0);
 	if (rc)
 		return rc;
 
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 5fb5dfc87e221bc244f52a1fa3bf2d1342b606d5..8ac4e2a9a6c87f128ff9aaf16d1238ef129d91ca 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -109,8 +109,8 @@ static int imx_ocotp_reg_write(void *context, unsigned int offset, unsigned int
 	return ret;
 }
 
-static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
-			     void *data, size_t bytes)
+static int imx_ocotp_cell_pp(void *context, const char *id, int index,
+			     unsigned int offset, void *data, size_t bytes)
 {
 	/* Deal with some post processing of nvmem cell data */
 	if (id && !strcmp(id, "mac-address")) {
diff --git a/drivers/nvmem/ocotp.c b/drivers/nvmem/ocotp.c
index 5d47377678665b7d5d48b58cb297fe784d27438c..d4510d4b89ba404c0b0537afc01075cac501042c 100644
--- a/drivers/nvmem/ocotp.c
+++ b/drivers/nvmem/ocotp.c
@@ -788,8 +788,8 @@ static struct regmap_bus imx_ocotp_regmap_bus = {
 	.reg_read = imx_ocotp_reg_read,
 };
 
-static int imx_ocotp_cell_pp(void *context, const char *id, unsigned int offset,
-			     void *data, size_t bytes)
+static int imx_ocotp_cell_pp(void *context, const char *id, int index,
+			     unsigned int offset, void *data, size_t bytes)
 {
 	/* Deal with some post processing of nvmem cell data */
 	if (id && !strcmp(id, "mac-address")) {
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index eca8cd0400a2d20a9c62ec59d6031e8508025a81..44c6b76ccc5212a72bffe694308321ce1b27e470 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -22,7 +22,7 @@ typedef int (*nvmem_reg_read_t)(void *priv, unsigned int offset,
 typedef int (*nvmem_reg_write_t)(void *priv, unsigned int offset,
 				 const void *val, size_t bytes);
 /* used for vendor specific post processing of cell data */
-typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id,
+typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
 					 unsigned int offset, void *buf,
 					 size_t bytes);
 

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 12/15] nvmem: core: add per-cell post processing
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (10 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 11/15] nvmem: core: add an index parameter to the cell Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:36 ` [PATCH 13/15] nvmem: core: add cell based fixup logic Marco Felsch
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

To further sync the nvmem code base port Linux commit:

| commit 345ec382cd4b736c36e01f155d08c913b225b736
| Author: Michael Walle <michael@walle.cc>
| Date:   Tue Apr 4 18:21:24 2023 +0100
|
|     nvmem: core: add per-cell post processing
|
|     Instead of relying on the name the consumer is using for the cell, like
|     it is done for the nvmem .cell_post_process configuration parameter,
|     provide a per-cell post processing hook. This can then be populated by
|     the NVMEM provider (or the NVMEM layout) when adding the cell.
|
|     Signed-off-by: Michael Walle <michael@walle.cc>
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20230404172148.82422-17-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 17 +++++++++++++++++
 include/linux/nvmem-provider.h |  3 +++
 2 files changed, 20 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index abb576af01ae0f8fc2d1e2f30ec9d0d22cd2a4fa..0c78d61e3c58fbe019a1def08d06e2ecf561a9b1 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -22,6 +22,7 @@ struct nvmem_cell_entry {
 	int			bytes;
 	int			bit_offset;
 	int			nbits;
+	nvmem_cell_post_process_t read_post_process;
 	struct device_node	*np;
 	struct nvmem_device	*nvmem;
 	struct list_head	node;
@@ -242,6 +243,7 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
 	cell->raw_len = info->raw_len ?: info->bytes;
 	cell->bytes = info->bytes;
 	cell->name = info->name;
+	cell->read_post_process = info->read_post_process;
 
 	cell->bit_offset = info->bit_offset;
 	cell->nbits = info->nbits;
@@ -829,6 +831,13 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 	if (cell->bit_offset || cell->nbits)
 		nvmem_shift_read_buffer_in_place(cell, buf);
 
+	if (cell->read_post_process) {
+		rc = cell->read_post_process(nvmem->priv, id, index,
+				cell->offset, buf, cell->bytes);
+		if (rc)
+			return rc;
+	}
+
 	if (nvmem->cell_post_process) {
 		rc = nvmem->cell_post_process(nvmem->priv, id, index,
 					      cell->offset, buf, cell->bytes);
@@ -937,6 +946,14 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, const void *b
 	    (cell->bit_offset == 0 && len != cell->bytes))
 		return -EINVAL;
 
+	/*
+	 * Any cells which have a cell_post_process hook are read-only because
+	 * we cannot reverse the operation and it might affect other cells,
+	 * too.
+	 */
+	if (cell->read_post_process)
+		return -EINVAL;
+
 	if (cell->bit_offset || cell->nbits) {
 		buf = nvmem_cell_prepare_write_buffer(cell, buf, len);
 		if (IS_ERR(buf))
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 44c6b76ccc5212a72bffe694308321ce1b27e470..892e3604ef83ef8b616041f5d4dbfdcae1136fc9 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -38,6 +38,8 @@ typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
  * @bit_offset:	Bit offset if cell is smaller than a byte.
  * @nbits:	Number of bits.
  * @np:		Optional device_node pointer.
+ * @read_post_process:	Callback for optional post processing of cell data
+ *			on reads.
  */
 struct nvmem_cell_info {
 	const char		*name;
@@ -47,6 +49,7 @@ struct nvmem_cell_info {
 	unsigned int		bit_offset;
 	unsigned int		nbits;
 	struct device_node	*np;
+	nvmem_cell_post_process_t read_post_process;
 };
 
 /**

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 13/15] nvmem: core: add cell based fixup logic
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (11 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 12/15] nvmem: core: add per-cell post processing Marco Felsch
@ 2025-08-04 14:36 ` Marco Felsch
  2025-08-04 14:37 ` [PATCH 14/15] nvmem: core: provide own priv pointer in post process callback Marco Felsch
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:36 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

To further sync the nvmem code base port Linux commits:

| commit de12c9691501ccba41a154c223869f82be4c12fd
| Author: Michael Walle <michael@walle.cc>
| Date:   Tue Apr 4 18:21:25 2023 +0100
|
|     nvmem: core: allow to modify a cell before adding it
|
|     Provide a way to modify a cell before it will get added. This is useful
|     to attach a custom post processing hook via a layout.
|
|     Signed-off-by: Michael Walle <michael@walle.cc>
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20230404172148.82422-18-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

| commit 1172460e716784ac7e1049a537bdca8edbf97360
| Author: Miquel Raynal <miquel.raynal@bootlin.com>
| Date:   Fri Dec 15 11:15:31 2023 +0000
|
|     nvmem: Move and rename ->fixup_cell_info()
|
|     This hook is meant to be used by any provider and instantiating a layout
|     just for this is useless. Let's instead move this hook to the nvmem
|     device and add it to the config structure to be easily shared by the
|     providers.
|
|     While at moving this hook, rename it ->fixup_dt_cell_info() to clarify
|     its main intended purpose.
|
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20231215111536.316972-6-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

The next commit will get rid of the global nvmem->cell_post_process()
hook.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 4 ++++
 drivers/nvmem/internals.h      | 2 ++
 include/linux/nvmem-provider.h | 4 ++++
 3 files changed, 10 insertions(+)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 0c78d61e3c58fbe019a1def08d06e2ecf561a9b1..d9b3e3bc8c1b9016975480d45da53f8c77a52881 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -344,6 +344,9 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod
 
 		info.np = of_node_get(child);
 
+		if (nvmem->fixup_dt_cell_info)
+			nvmem->fixup_dt_cell_info(nvmem, &info);
+
 		ret = nvmem_add_one_cell(nvmem, &info);
 		kfree(info.name);
 		if (ret) {
@@ -438,6 +441,7 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	nvmem->dev.of_node = np;
 	nvmem->priv = config->priv;
 	INIT_LIST_HEAD(&nvmem->cells);
+	nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
 	nvmem->cell_post_process = config->cell_post_process;
 
 	rval = nvmem_add_cells_from_legacy_of(nvmem);
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 97ffdfe79dfe6c5459d1bba5ba717c861678e9a2..10e11ad79bc32364d542c5320e70fb0377e83478 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -22,6 +22,8 @@ struct nvmem_device {
 	struct nvmem_layout	*layout;
 	struct list_head	cells;
 	nvmem_cell_post_process_t cell_post_process;
+	void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+				   struct nvmem_cell_info *cell);
 	int			(*reg_write)(void *ctx, unsigned int reg,
 					     const void *val, size_t val_size);
 	int			(*reg_read)(void *ctx, unsigned int reg,
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index 892e3604ef83ef8b616041f5d4dbfdcae1136fc9..a8aa6b741395f810315d7f018d62e4ef64e618a5 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -58,6 +58,8 @@ struct nvmem_cell_info {
  * @dev:	Parent device.
  * @name:	Optional name.
  * @id:		Optional device ID used in full name. Ignored if name is NULL.
+ * @fixup_dt_cell_info: Will be called before a cell is added. Can be
+ *		used to modify the nvmem_cell_info.
  * @read_only:	Device is read-only.
  * @reg_read:	Callback to read data; return zero if successful.
  * @reg_write:	Callback to write data; return zero if successful.
@@ -70,6 +72,8 @@ struct nvmem_config {
 	struct device		*dev;
 	const char		*name;
 	int			id;
+	void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+				   struct nvmem_cell_info *cell);
 	bool			read_only;
 	struct cdev		*cdev;
 	nvmem_reg_read_t	reg_read;

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 14/15] nvmem: core: provide own priv pointer in post process callback
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (12 preceding siblings ...)
  2025-08-04 14:36 ` [PATCH 13/15] nvmem: core: add cell based fixup logic Marco Felsch
@ 2025-08-04 14:37 ` Marco Felsch
  2025-08-04 14:37 ` [PATCH 15/15] nvmem: core: drop global cell_post_process Marco Felsch
  2025-08-05 10:44 ` [PATCH 00/15] NVMEM: Add support for layout drivers Sascha Hauer
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:37 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

To remove the global nvmem->cell_post_process() and further sync our
code base with Linux, port the following commit:

| commit 8a134fd9f9323f4c39ec27055b3d3723cfb5c1e9
| Author: Michael Walle <michael@walle.cc>
| Date:   Tue Apr 4 18:21:28 2023 +0100
|
|     nvmem: core: provide own priv pointer in post process callback
|
|     It doesn't make any more sense to have a opaque pointer set up by the
|     nvmem device. Usually, the layout isn't associated with a particular
|     nvmem device. Instead, let the caller who set the post process callback
|     provide the priv pointer.
|
|     Signed-off-by: Michael Walle <michael@walle.cc>
|     Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
|     Signed-off-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org>
|     Link: https://lore.kernel.org/r/20230404172148.82422-21-srinivas.kandagatla@linaro.org
|     Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 4 +++-
 include/linux/nvmem-provider.h | 2 ++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index d9b3e3bc8c1b9016975480d45da53f8c77a52881..7f4fd329c196efa3baac2c97016d6651d4c140a3 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -23,6 +23,7 @@ struct nvmem_cell_entry {
 	int			bit_offset;
 	int			nbits;
 	nvmem_cell_post_process_t read_post_process;
+	void			*priv;
 	struct device_node	*np;
 	struct nvmem_device	*nvmem;
 	struct list_head	node;
@@ -244,6 +245,7 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
 	cell->bytes = info->bytes;
 	cell->name = info->name;
 	cell->read_post_process = info->read_post_process;
+	cell->priv = info->priv;
 
 	cell->bit_offset = info->bit_offset;
 	cell->nbits = info->nbits;
@@ -836,7 +838,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 		nvmem_shift_read_buffer_in_place(cell, buf);
 
 	if (cell->read_post_process) {
-		rc = cell->read_post_process(nvmem->priv, id, index,
+		rc = cell->read_post_process(cell->priv, id, index,
 				cell->offset, buf, cell->bytes);
 		if (rc)
 			return rc;
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index a8aa6b741395f810315d7f018d62e4ef64e618a5..e1e7d8871d67465157191e7e6a46b400a285e834 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -40,6 +40,7 @@ typedef int (*nvmem_cell_post_process_t)(void *priv, const char *id, int index,
  * @np:		Optional device_node pointer.
  * @read_post_process:	Callback for optional post processing of cell data
  *			on reads.
+ * @priv:	Opaque data passed to the read_post_process hook.
  */
 struct nvmem_cell_info {
 	const char		*name;
@@ -50,6 +51,7 @@ struct nvmem_cell_info {
 	unsigned int		nbits;
 	struct device_node	*np;
 	nvmem_cell_post_process_t read_post_process;
+	void			*priv;
 };
 
 /**

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* [PATCH 15/15] nvmem: core: drop global cell_post_process
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (13 preceding siblings ...)
  2025-08-04 14:37 ` [PATCH 14/15] nvmem: core: provide own priv pointer in post process callback Marco Felsch
@ 2025-08-04 14:37 ` Marco Felsch
  2025-08-05 10:44 ` [PATCH 00/15] NVMEM: Add support for layout drivers Sascha Hauer
  15 siblings, 0 replies; 17+ messages in thread
From: Marco Felsch @ 2025-08-04 14:37 UTC (permalink / raw)
  To: Sascha Hauer, BAREBOX; +Cc: Marco Felsch

Switch to the cell local read_post_process() hook and drop the global
nvmem cell_post_process() hook to align our code base with Linux, to
make it easier to port nvmem(-layout) drivers.

Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
---
 drivers/nvmem/core.c           | 10 +---------
 drivers/nvmem/imx-ocotp-ele.c  |  8 +++++++-
 drivers/nvmem/internals.h      |  1 -
 drivers/nvmem/ocotp.c          |  8 +++++++-
 drivers/nvmem/regmap.c         |  5 +++--
 include/linux/nvmem-provider.h | 12 ++++++------
 6 files changed, 24 insertions(+), 20 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 7f4fd329c196efa3baac2c97016d6651d4c140a3..fb5860bf03956170cb1d32ca21c36568387f1cf9 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -444,7 +444,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
 	nvmem->priv = config->priv;
 	INIT_LIST_HEAD(&nvmem->cells);
 	nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
-	nvmem->cell_post_process = config->cell_post_process;
 
 	rval = nvmem_add_cells_from_legacy_of(nvmem);
 	if (rval)
@@ -844,13 +843,6 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem,
 			return rc;
 	}
 
-	if (nvmem->cell_post_process) {
-		rc = nvmem->cell_post_process(nvmem->priv, id, index,
-					      cell->offset, buf, cell->bytes);
-		if (rc)
-			return rc;
-	}
-
 	if (len)
 		*len = cell->bytes;
 
@@ -953,7 +945,7 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, const void *b
 		return -EINVAL;
 
 	/*
-	 * Any cells which have a cell_post_process hook are read-only because
+	 * Any cells which have a read_post_process hook are read-only because
 	 * we cannot reverse the operation and it might affect other cells,
 	 * too.
 	 */
diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c
index 8ac4e2a9a6c87f128ff9aaf16d1238ef129d91ca..797c6f8d7a2a069fbf342c517031eb5dc3f09d7a 100644
--- a/drivers/nvmem/imx-ocotp-ele.c
+++ b/drivers/nvmem/imx-ocotp-ele.c
@@ -128,6 +128,12 @@ static int imx_ocotp_cell_pp(void *context, const char *id, int index,
 	return 0;
 }
 
+static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
+					 struct nvmem_cell_info *cell)
+{
+	cell->read_post_process = imx_ocotp_cell_pp;
+}
+
 static struct regmap_bus imx_ocotp_regmap_bus = {
 	.reg_write = imx_ocotp_reg_write,
 	.reg_read = imx_ocotp_reg_read,
@@ -192,7 +198,7 @@ static int imx_ele_ocotp_probe(struct device *dev)
 		imx_ocotp_set_unique_machine_id(priv);
 
 	nvmem = nvmem_regmap_register_with_pp(priv->map, "imx_ocotp",
-					      imx_ocotp_cell_pp);
+					      imx_ocotp_fixup_dt_cell_info);
 	if (IS_ERR(nvmem))
 		return PTR_ERR(nvmem);
 
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 10e11ad79bc32364d542c5320e70fb0377e83478..1e050df78621f8cce751577365107bfa16766374 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -21,7 +21,6 @@ struct nvmem_device {
 	void			*priv;
 	struct nvmem_layout	*layout;
 	struct list_head	cells;
-	nvmem_cell_post_process_t cell_post_process;
 	void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
 				   struct nvmem_cell_info *cell);
 	int			(*reg_write)(void *ctx, unsigned int reg,
diff --git a/drivers/nvmem/ocotp.c b/drivers/nvmem/ocotp.c
index d4510d4b89ba404c0b0537afc01075cac501042c..7bca27540417ad9ba0ce5c5f8ec43ad1c63638b9 100644
--- a/drivers/nvmem/ocotp.c
+++ b/drivers/nvmem/ocotp.c
@@ -803,6 +803,12 @@ static int imx_ocotp_cell_pp(void *context, const char *id, int index,
 	return 0;
 }
 
+static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem,
+					 struct nvmem_cell_info *cell)
+{
+	cell->read_post_process = imx_ocotp_cell_pp;
+}
+
 static int imx_ocotp_init_dt(struct ocotp_priv *priv)
 {
 	char mac[MAC_BYTES];
@@ -898,7 +904,7 @@ static int imx_ocotp_probe(struct device *dev)
 		return PTR_ERR(priv->map);
 
 	nvmem = nvmem_regmap_register_with_pp(priv->map, "imx-ocotp",
-					      imx_ocotp_cell_pp);
+					      imx_ocotp_fixup_dt_cell_info);
 	if (IS_ERR(nvmem))
 		return PTR_ERR(nvmem);
 
diff --git a/drivers/nvmem/regmap.c b/drivers/nvmem/regmap.c
index 24712fbb0f332e98474955aa433c0a5e02502054..d35814c9a2a955926781768fb3e1126b6f041c26 100644
--- a/drivers/nvmem/regmap.c
+++ b/drivers/nvmem/regmap.c
@@ -65,7 +65,8 @@ static int nvmem_regmap_read(void *ctx, unsigned offset, void *buf, size_t bytes
 
 struct nvmem_device *
 nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
-			      nvmem_cell_post_process_t cell_post_process)
+			      void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+							 struct nvmem_cell_info *cell))
 {
 	struct nvmem_config config = {};
 
@@ -79,7 +80,7 @@ nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
 	config.stride = 1;
 	config.word_size = 1;
 	config.size = regmap_size_bytes(map);
-	config.cell_post_process = cell_post_process;
+	config.fixup_dt_cell_info = fixup_dt_cell_info;
 	config.reg_write = nvmem_regmap_write;
 	config.reg_read = nvmem_regmap_read;
 
diff --git a/include/linux/nvmem-provider.h b/include/linux/nvmem-provider.h
index e1e7d8871d67465157191e7e6a46b400a285e834..2ea2a20d9efbcd704c347da9d0bd77bfaf14be61 100644
--- a/include/linux/nvmem-provider.h
+++ b/include/linux/nvmem-provider.h
@@ -84,7 +84,6 @@ struct nvmem_config {
 	int			word_size;
 	int			stride;
 	void			*priv;
-	nvmem_cell_post_process_t cell_post_process;
 };
 
 /**
@@ -120,8 +119,9 @@ struct cdev;
 
 struct nvmem_device *nvmem_register(const struct nvmem_config *cfg);
 struct nvmem_device *nvmem_regmap_register(struct regmap *regmap, const char *name);
-struct nvmem_device *nvmem_regmap_register_with_pp(struct regmap *regmap,
-		const char *name, nvmem_cell_post_process_t cell_post_process);
+struct nvmem_device *nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
+			      void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+							 struct nvmem_cell_info *cell));
 struct device *nvmem_device_get_device(struct nvmem_device *nvmem);
 int nvmem_add_one_cell(struct nvmem_device *nvmem,
 		       const struct nvmem_cell_info *info);
@@ -145,9 +145,9 @@ static inline struct nvmem_device *nvmem_regmap_register(struct regmap *regmap,
 	return ERR_PTR(-ENOSYS);
 }
 
-static inline struct nvmem_device *
-nvmem_regmap_register_with_pp(struct regmap *regmap, const char *name,
-			      nvmem_cell_post_process_t cell_post_process)
+struct nvmem_device *nvmem_regmap_register_with_pp(struct regmap *map, const char *name,
+			      void (*fixup_dt_cell_info)(struct nvmem_device *nvmem,
+							 struct nvmem_cell_info *cell))
 {
 	return ERR_PTR(-ENOSYS);
 }

-- 
2.39.5




^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [PATCH 00/15] NVMEM: Add support for layout drivers
  2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
                   ` (14 preceding siblings ...)
  2025-08-04 14:37 ` [PATCH 15/15] nvmem: core: drop global cell_post_process Marco Felsch
@ 2025-08-05 10:44 ` Sascha Hauer
  15 siblings, 0 replies; 17+ messages in thread
From: Sascha Hauer @ 2025-08-05 10:44 UTC (permalink / raw)
  To: Marco Felsch; +Cc: BAREBOX

Hi Marco,

On Mon, Aug 04, 2025 at 04:36:46PM +0200, Marco Felsch wrote:
> Hi,
> 
> with this patchset the barebox nvmem-core is prepared for the Linux
> nvmem-layout drivers. Layout drivers are used to describe the NVMEM
> storage format. The patchset also adds the support to read nvmem-cells
> via the devfs. This makes it possible to read the cells from the board
> code or shell without the need of referencing the cells within the DT.
> 
> A long with the alignment and feature work I fixed a few issues like:
> honor the DT alias for a nvmem provider correctly.
> 
> For the new features to work correctly the NVMEM core had to be partly
> (re-)synced with the one from Linux. This involved porting different DT
> helpers.
> 
> Unfortunately this patchset doesn't add a NVMEM layout driver therefore
> a __dummy__.o is added within the Makefile.
> 
> I decided to start from a fresh v1 because of the rework I've done. But
> for reference, the previous patchset can be found here:
> 
>  - https://lore.kernel.org/barebox/20240613131531.364894-1-m.felsch@pengutronix.de/

This conflicts with Oleksijs nvmem-protect series. Could you rebase on
master?

Sascha

-- 
Pengutronix e.K.                           |                             |
Steuerwalder Str. 21                       | http://www.pengutronix.de/  |
31137 Hildesheim, Germany                  | Phone: +49-5121-206917-0    |
Amtsgericht Hildesheim, HRA 2686           | Fax:   +49-5121-206917-5555 |



^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2025-08-05 11:01 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-08-04 14:36 [PATCH 00/15] NVMEM: Add support for layout drivers Marco Felsch
2025-08-04 14:36 ` [PATCH 01/15] of: sync of_*_phandle_with_args with Linux Marco Felsch
2025-08-04 14:36 ` [PATCH 02/15] of: base: add of_parse_phandle_with_optional_args() Marco Felsch
2025-08-04 14:36 ` [PATCH 03/15] of: device: Export of_device_make_bus_id() Marco Felsch
2025-08-04 14:36 ` [PATCH 04/15] nvmem: core: fix nvmem_register error path Marco Felsch
2025-08-04 14:36 ` [PATCH 05/15] nvmem: core: sync with Linux Marco Felsch
2025-08-04 14:36 ` [PATCH 06/15] nvmem: core: expose nvmem cells as cdev Marco Felsch
2025-08-04 14:36 ` [PATCH 07/15] nvmem: core: allow single and dynamic device ids Marco Felsch
2025-08-04 14:36 ` [PATCH 08/15] eeprom: at24: fix device name handling Marco Felsch
2025-08-04 14:36 ` [PATCH 09/15] nvmem: core: create a header for internal sharing Marco Felsch
2025-08-04 14:36 ` [PATCH 10/15] nvmem: core: add nvmem-layout support Marco Felsch
2025-08-04 14:36 ` [PATCH 11/15] nvmem: core: add an index parameter to the cell Marco Felsch
2025-08-04 14:36 ` [PATCH 12/15] nvmem: core: add per-cell post processing Marco Felsch
2025-08-04 14:36 ` [PATCH 13/15] nvmem: core: add cell based fixup logic Marco Felsch
2025-08-04 14:37 ` [PATCH 14/15] nvmem: core: provide own priv pointer in post process callback Marco Felsch
2025-08-04 14:37 ` [PATCH 15/15] nvmem: core: drop global cell_post_process Marco Felsch
2025-08-05 10:44 ` [PATCH 00/15] NVMEM: Add support for layout drivers Sascha Hauer

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox