mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 00/10] regmap: clean up and improve __regmap_init()
@ 2026-11-13 15:31 Peng Fan
  2026-11-13 15:31 ` [PATCH v2 01/10] regmap: fix reinit cache failure handling Peng Fan
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan, Sashiko

This series includes three fixes for pre-existing regmap issues noticed
during review, improves the __regmap_init() error handling and removes the
last user of regmap_bus::free_on_exit. Patches 1-3 are fixes suitable for stable;
patches 4-9 are cleanups with no functional change intended; patch 10 adds
KUnit coverage for the new bus callback.

Patch 1 fixes the cache reinitialisation failure paths. regcache_exit()
freed reg_defaults_raw without clearing cache_free, and a failed
regmap_reinit_cache() left map->cache_type set, so a later regmap_exit()
could free the raw defaults a second time. It also drops the old cache_ops
after tearing down the cache, so a successful reinitialisation to
REGCACHE_NONE cannot leave stale cache ops behind. Clear the ownership
state and leave the map without a cache if reinitialisation fails.

Patch 2 fixes regmap_get_i2c_bus() so that applying an adapter read quirk
no longer drops the raw write limit advertised by the selected bus (and
vice versa). It currently starts from zero-initialised locals and
overwrites both limits of the bus copy.

Patch 3 fixes the raw write limit calculation when the adapter's
max_write_len quirk is applied. It compares the bus limit against the
quirk payload size rather than the whole-message size, avoids the
underflow when the register address alone exceeds the quirk limit, and
keeps the existing limit when the quirk cannot fit a single value, which
would otherwise lead to a division by zero in the raw write chunking.
The same minimum-size guard is applied to the read quirk.

Patches 4-7 are mechanical: evaluate fast_io once, and factor out
regmap_set_lock(), regmap_init_ranges() and the format selection into
helpers. The forward goto and the label-based unwinding are reduced to a
single error path.

Patch 8 drops regmap_bus::free_on_exit. Instead of kmemdup()ing the
static I2C/SPI bus to store per-device raw I/O limits, add an optional
regmap_bus::init_limits() callback. __regmap_init() invokes it before
regcache_init(), which is required because regcache_hw_init() may read
raw register defaults from the device during initialization and must
already see the effective max_raw_read limit. The static bus structures
can then be used directly and the per-device allocations disappear.

Patch 9 converts the error unwinding to the cleanup API and drops all
err_* labels. map->cache_type is now assigned just before
regcache_init() and reset to REGCACHE_NONE on failure, so the release
helper can safely call regcache_exit() on a partially initialised map.

Patch 10 adds a KUnit test which verifies that init_limits() is invoked
before regcache_init() by checking that the raw default readback is split
according to the limit installed by the callback.

Testing:
- Built drivers/base/regmap/ with W=1 on x86_64.
- checkpatch.pl clean for all patches.
- regmap KUnit suite pass.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
Changes in v2:
- Rebased onto next-20261112.
- New patch 1: regmap: fix reinit cache failure handling
- Patch 3: regmap-i2c: fix write limit clamping for adapter quirks
  Correct fixes tag
  Rename the patch subject.
  Fix write limit clamping.
- Patch 5: regmap: factor out regmap_set_lock():
  Move ret = -EINVAL below the successful regmap_set_lock() call so it
  cannot be clobbered with 0 on success.

---
Peng Fan (10):
      regmap: fix reinit cache failure handling
      regmap-i2c: keep bus raw write limit when applying read quirk
      regmap-i2c: fix write limit clamping for adapter quirks
      regmap: deduplicate fast_io checks
      regmap: factor out regmap_set_lock()
      regmap: factor out regmap_init_ranges()
      regmap: factor out format initialisation into helpers
      regmap: drop regmap_bus::free_on_exit
      regmap: use cleanup API in __regmap_init()
      regmap: kunit: add test for regmap_bus::init_limits

 drivers/base/regmap/regcache.c     |   8 +-
 drivers/base/regmap/regmap-i2c.c   |  68 ++--
 drivers/base/regmap/regmap-kunit.c |  89 +++++
 drivers/base/regmap/regmap-spi.c   |  64 ++--
 drivers/base/regmap/regmap.c       | 714 +++++++++++++++++++------------------
 include/linux/regmap.h             |   7 +-
 6 files changed, 541 insertions(+), 409 deletions(-)
---
base-commit: 6d582d78fad9d828e575ca0cdcff8ea47a77220b
change-id: 20261109-regmap-improve-915eee5a80ec

Best regards,
-- 
Peng Fan <peng.fan@nxp.com>


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

* [PATCH v2 01/10] regmap: fix reinit cache failure handling
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 02/10] regmap-i2c: keep bus raw write limit when applying read quirk Peng Fan
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan, Sashiko

From: Peng Fan <peng.fan@nxp.com>

regmap_reinit_cache() tears down the existing cache and then updates the
map configuration. regcache_exit() frees reg_defaults_raw when cache_free
is set but does not clear cache_free or the pointer, and neither
regmap_set_name() nor regcache_init() resets map->cache_type on failure.
The old cache_ops pointer also remains set when reinitialising to
REGCACHE_NONE.

If the reinitialisation then fails, a later regmap_exit() calls
regcache_exit() again. It frees reg_defaults_raw a second time and runs
cache_ops->exit() on the already torn-down cache. A stale cache_ops can
also make regmap_volatile() treat registers as cacheable on a map that
has no cache.

Clear cache_free and reg_defaults_raw in regcache_hw_exit(), and clear
the old cache_ops right after regcache_exit() so a reinitialisation to
REGCACHE_NONE cannot leave stale ops behind. Mark the map as having no
cache when regmap_reinit_cache() fails after tearing down the old one.
Assign the new cache type only after regmap_set_name() has succeeded so
the failure paths cannot leave a type without cache ops.

Reported-by: Sashiko <sashiko-bot@kernel.org>
Closes: https://sashiko.dev/#/bug/linux-5693609c-7965-4f36-ae8f-30c610e10a33
Fixes: bf315173359b ("regmap: Allow drivers to reinitialise the register cache at runtime")
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regcache.c |  8 ++++++--
 drivers/base/regmap/regmap.c   | 12 +++++++++---
 2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c
index 1447967ff00b..2954e73180ed 100644
--- a/drivers/base/regmap/regcache.c
+++ b/drivers/base/regmap/regcache.c
@@ -122,8 +122,11 @@ static int regcache_hw_init(struct regmap *map, int count)
 
 static void regcache_hw_exit(struct regmap *map)
 {
-	if (map->cache_free)
+	if (map->cache_free) {
 		kfree(map->reg_defaults_raw);
+		map->reg_defaults_raw = NULL;
+		map->cache_free = false;
+	}
 }
 
 static int regcache_locked_op(struct regmap *map,
@@ -282,12 +285,13 @@ int regcache_init(struct regmap *map, const struct regmap_config *config)
 
 void regcache_exit(struct regmap *map)
 {
+	regcache_hw_exit(map);
+
 	if (map->cache_type == REGCACHE_NONE)
 		return;
 
 	BUG_ON(!map->cache_ops);
 
-	regcache_hw_exit(map);
 	if (map->cache_ops->exit) {
 		dev_dbg(map->dev, "Destroying %s cache\n",
 			map->cache_ops->name);
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index a0e14dc1f6f6..327ae2ea9729 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1424,6 +1424,7 @@ int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config)
 
 	regmap_debugfs_exit(map);
 	regcache_exit(map);
+	map->cache_ops = NULL;
 
 	map->max_register = config->max_register;
 	map->max_register_is_set = map->max_register ?: config->max_register_is_0;
@@ -1434,18 +1435,23 @@ int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config)
 	map->writeable_noinc_reg = config->writeable_noinc_reg;
 	map->readable_noinc_reg = config->readable_noinc_reg;
 	map->reg_default_cb = config->reg_default_cb;
-	map->cache_type = config->cache_type;
 
 	ret = regmap_set_name(map, config);
-	if (ret)
+	if (ret) {
+		map->cache_type = REGCACHE_NONE;
 		return ret;
+	}
 
 	map->cache_bypass = false;
 	map->cache_only = false;
 
+	map->cache_type = config->cache_type;
 	ret = regcache_init(map, config);
-	if (ret)
+	if (ret) {
+		map->cache_ops = NULL;
+		map->cache_type = REGCACHE_NONE;
 		return ret;
+	}
 
 	regmap_debugfs_init(map);
 

-- 
2.56.0


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

* [PATCH v2 02/10] regmap-i2c: keep bus raw write limit when applying read quirk
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
  2026-11-13 15:31 ` [PATCH v2 01/10] regmap: fix reinit cache failure handling Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 03/10] regmap-i2c: fix write limit clamping for adapter quirks Peng Fan
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

regmap_get_i2c_bus() applies adapter quirks on top of the raw I/O limits
advertised by the selected bus, but it starts from zero-initialised local
variables and then overwrites both max_raw_read and max_raw_write of the
copied bus. If only one direction is clamped by the adapter, the limit of
the other direction is dropped.

For example, regmap_i2c_smbus_i2c_block sets a 31-byte limit for both
directions. An adapter that only reports max_read_len causes the copied
bus to advertise 31 bytes for reads and no limit for writes, even though
I2C_SMBUS_BLOCK_MAX - 1 still applies.

Seed the locals from the selected bus and only replace a limit when the
adapter quirk is more restrictive. Also only allocate the bus copy when
one of the limits actually changes.

Fixes: ea030ca68819 ("regmap-i2c: Set regmap max raw r/w from quirks")
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap-i2c.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index 51a04961faf7..8663a4ec0a3d 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -353,7 +353,7 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 	const struct i2c_adapter_quirks *quirks;
 	const struct regmap_bus *bus = NULL;
 	struct regmap_bus *ret_bus;
-	u16 max_read = 0, max_write = 0;
+	u16 max_read, max_write;
 
 	if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
 		bus = &regmap_i2c;
@@ -391,18 +391,22 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 	if (!bus)
 		return ERR_PTR(-ENOTSUPP);
 
+	max_read = bus->max_raw_read;
+	max_write = bus->max_raw_write;
+
 	quirks = i2c->adapter->quirks;
 	if (quirks) {
 		if (quirks->max_read_len &&
-		    (bus->max_raw_read == 0 || bus->max_raw_read > quirks->max_read_len))
+		    (max_read == 0 || max_read > quirks->max_read_len))
 			max_read = quirks->max_read_len;
 
 		if (quirks->max_write_len &&
-		    (bus->max_raw_write == 0 || bus->max_raw_write > quirks->max_write_len))
+		    (max_write == 0 || max_write > quirks->max_write_len))
 			max_write = quirks->max_write_len -
 				(config->reg_bits + config->pad_bits) / BITS_PER_BYTE;
 
-		if (max_read || max_write) {
+		if (max_read != bus->max_raw_read ||
+		    max_write != bus->max_raw_write) {
 			ret_bus = kmemdup(bus, sizeof(*bus), GFP_KERNEL);
 			if (!ret_bus)
 				return ERR_PTR(-ENOMEM);

-- 
2.56.0


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

* [PATCH v2 03/10] regmap-i2c: fix write limit clamping for adapter quirks
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
  2026-11-13 15:31 ` [PATCH v2 01/10] regmap: fix reinit cache failure handling Peng Fan
  2026-11-13 15:31 ` [PATCH v2 02/10] regmap-i2c: keep bus raw write limit when applying read quirk Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 04/10] regmap: deduplicate fast_io checks Peng Fan
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

The adapter max_write_len quirk covers the whole write message, including
the register address, while max_raw_write is the payload limit. Calculate
the payload limit before comparing it with max_raw_write, otherwise a bus
limit that fits in the message but not in the quirk payload is left
unchanged and the transfer fails in i2c_check_for_quirks().

Also avoid installing a payload limit smaller than one value.
_regmap_raw_write() splits transfers using max_raw_write / val_bytes, so a
limit below val_bytes results in a zero chunk size and a division by zero.
If the adapter cannot fit a single value after the register address, no
raw write can succeed, so keep the existing limit.

The subtraction must not underflow when max_write_len does not exceed the
register address size, so only calculate the payload limit when the quirk
leaves room for it. Apply the same minimum-size check to the read quirk
because max_raw_read is used as a divisor too.

Fixes: 611b7eb19d0a ("regmap-i2c: Subtract reg size from max_write")
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap-i2c.c | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index 8663a4ec0a3d..a4a259232980 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -8,6 +8,7 @@
 
 #include <linux/regmap.h>
 #include <linux/i2c.h>
+#include <linux/bitops.h>
 #include <linux/module.h>
 
 #include "internal.h"
@@ -353,6 +354,7 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 	const struct i2c_adapter_quirks *quirks;
 	const struct regmap_bus *bus = NULL;
 	struct regmap_bus *ret_bus;
+	size_t val_bytes;
 	u16 max_read, max_write;
 
 	if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
@@ -393,17 +395,28 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 
 	max_read = bus->max_raw_read;
 	max_write = bus->max_raw_write;
+	val_bytes = BITS_TO_BYTES(config->val_bits);
 
 	quirks = i2c->adapter->quirks;
 	if (quirks) {
 		if (quirks->max_read_len &&
+		    quirks->max_read_len >= val_bytes &&
 		    (max_read == 0 || max_read > quirks->max_read_len))
 			max_read = quirks->max_read_len;
 
-		if (quirks->max_write_len &&
-		    (max_write == 0 || max_write > quirks->max_write_len))
-			max_write = quirks->max_write_len -
-				(config->reg_bits + config->pad_bits) / BITS_PER_BYTE;
+		if (quirks->max_write_len) {
+			size_t reg_reserve_size, max_payload;
+
+			reg_reserve_size = (config->reg_bits + config->pad_bits) /
+					   BITS_PER_BYTE;
+			if (quirks->max_write_len > reg_reserve_size) {
+				max_payload = quirks->max_write_len -
+					      reg_reserve_size;
+				if (max_payload >= val_bytes &&
+				    (max_write == 0 || max_write > max_payload))
+					max_write = max_payload;
+			}
+		}
 
 		if (max_read != bus->max_raw_read ||
 		    max_write != bus->max_raw_write) {

-- 
2.56.0


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

* [PATCH v2 04/10] regmap: deduplicate fast_io checks
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (2 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 03/10] regmap-i2c: fix write limit clamping for adapter quirks Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 05/10] regmap: factor out regmap_set_lock() Peng Fan
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

__regmap_init() evaluates "(bus && bus->fast_io) || config->fast_io" in
two places to decide whether the map has to be usable from atomic
context. Compute it once into a local fast_io variable.

No functional change intended.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 327ae2ea9729..c123bae8bd66 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -682,6 +682,7 @@ struct regmap *__regmap_init(struct device *dev,
 {
 	struct regmap *map;
 	enum regmap_endian reg_endian, val_endian;
+	bool fast_io;
 	int i, j;
 	int ret;
 
@@ -702,6 +703,8 @@ struct regmap *__regmap_init(struct device *dev,
 
 	ret = -EINVAL; /* Later error paths rely on this */
 
+	fast_io = (bus && bus->fast_io) || config->fast_io;
+
 	if (config->disable_locking) {
 		map->lock = map->unlock = regmap_lock_unlock_none;
 		map->can_sleep = config->can_sleep;
@@ -735,8 +738,7 @@ struct regmap *__regmap_init(struct device *dev,
 
 		map->lock_arg = map;
 	} else {
-		if ((bus && bus->fast_io) ||
-		    config->fast_io) {
+		if (fast_io) {
 			if (config->use_raw_spinlock) {
 				raw_spin_lock_init(&map->raw_spinlock);
 				map->lock = regmap_lock_raw_spinlock;
@@ -766,10 +768,7 @@ struct regmap *__regmap_init(struct device *dev,
 	 * When we write in fast-paths with regmap_bulk_write() don't allocate
 	 * scratch buffers with sleeping allocations.
 	 */
-	if ((bus && bus->fast_io) || config->fast_io)
-		map->alloc_flags = GFP_ATOMIC;
-	else
-		map->alloc_flags = GFP_KERNEL;
+	map->alloc_flags = fast_io ? GFP_ATOMIC : GFP_KERNEL;
 
 	map->reg_base = config->reg_base;
 	map->reg_shift = config->pad_bits % 8;

-- 
2.56.0


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

* [PATCH v2 05/10] regmap: factor out regmap_set_lock()
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (3 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 04/10] regmap: deduplicate fast_io checks Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 06/10] regmap: factor out regmap_init_ranges() Peng Fan
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

The locking scheme selection in __regmap_init() is a self contained
~60 line if/else ladder covering six cases: locking disabled, caller
supplied callbacks, hardware spinlock, raw spinlock, spinlock and
mutex. It has no interaction with the rest of the initialisation
sequence beyond the map and config pointers.

Move it into regmap_set_lock(). The only error case, a failed
hwspin_lock_request_specific(), now returns -ENXIO to the caller
instead of jumping to err_name directly, and each branch returns
early rather than falling through the ladder.

No functional change intended.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap.c | 138 +++++++++++++++++++++++++------------------
 1 file changed, 79 insertions(+), 59 deletions(-)

diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index c123bae8bd66..aca63b0d03e0 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -673,53 +673,33 @@ enum regmap_endian regmap_get_val_endian(struct device *dev,
 }
 EXPORT_SYMBOL_GPL(regmap_get_val_endian);
 
-struct regmap *__regmap_init(struct device *dev,
-			     const struct regmap_bus *bus,
-			     void *bus_context,
-			     const struct regmap_config *config,
-			     struct lock_class_key *lock_key,
-			     const char *lock_name)
+/*
+ * Select the locking scheme for the map. The fast_io argument tells whether
+ * the map has to be usable from atomic context.
+ */
+static int regmap_set_lock(struct regmap *map, const struct regmap_config *config,
+			   bool fast_io, struct lock_class_key *lock_key,
+			   const char *lock_name)
 {
-	struct regmap *map;
-	enum regmap_endian reg_endian, val_endian;
-	bool fast_io;
-	int i, j;
-	int ret;
-
-	if (!config) {
-		ret = -EINVAL;
-		goto err;
-	}
-
-	map = kzalloc_obj(*map);
-	if (map == NULL) {
-		ret = -ENOMEM;
-		goto err;
-	}
-
-	ret = regmap_set_name(map, config);
-	if (ret)
-		goto err_map;
-
-	ret = -EINVAL; /* Later error paths rely on this */
-
-	fast_io = (bus && bus->fast_io) || config->fast_io;
-
 	if (config->disable_locking) {
 		map->lock = map->unlock = regmap_lock_unlock_none;
 		map->can_sleep = config->can_sleep;
 		regmap_debugfs_disable(map);
-	} else if (config->lock && config->unlock) {
+		return 0;
+	}
+
+	if (config->lock && config->unlock) {
 		map->lock = config->lock;
 		map->unlock = config->unlock;
 		map->lock_arg = config->lock_arg;
 		map->can_sleep = config->can_sleep;
-	} else if (config->use_hwlock) {
+		return 0;
+	}
+
+	if (config->use_hwlock) {
 		map->hwlock = hwspin_lock_request_specific(config->hwlock_id);
-		if (!map->hwlock) {
-			ret = -ENXIO;
-			goto err_name;
-		}
+		if (!map->hwlock)
+			return -ENXIO;
 
 		switch (config->hwlock_mode) {
 		case HWLOCK_IRQSTATE:
@@ -737,33 +717,73 @@ struct regmap *__regmap_init(struct device *dev,
 		}
 
 		map->lock_arg = map;
-	} else {
-		if (fast_io) {
-			if (config->use_raw_spinlock) {
-				raw_spin_lock_init(&map->raw_spinlock);
-				map->lock = regmap_lock_raw_spinlock;
-				map->unlock = regmap_unlock_raw_spinlock;
-				lockdep_set_class_and_name(&map->raw_spinlock,
-							   lock_key, lock_name);
-			} else {
-				spin_lock_init(&map->spinlock);
-				map->lock = regmap_lock_spinlock;
-				map->unlock = regmap_unlock_spinlock;
-				lockdep_set_class_and_name(&map->spinlock,
-							   lock_key, lock_name);
-			}
+		return 0;
+	}
+
+	if (fast_io) {
+		if (config->use_raw_spinlock) {
+			raw_spin_lock_init(&map->raw_spinlock);
+			map->lock = regmap_lock_raw_spinlock;
+			map->unlock = regmap_unlock_raw_spinlock;
+			lockdep_set_class_and_name(&map->raw_spinlock,
+						   lock_key, lock_name);
 		} else {
-			mutex_init(&map->mutex);
-			map->lock = regmap_lock_mutex;
-			map->unlock = regmap_unlock_mutex;
-			map->can_sleep = true;
-			lockdep_set_class_and_name(&map->mutex,
+			spin_lock_init(&map->spinlock);
+			map->lock = regmap_lock_spinlock;
+			map->unlock = regmap_unlock_spinlock;
+			lockdep_set_class_and_name(&map->spinlock,
 						   lock_key, lock_name);
 		}
-		map->lock_arg = map;
-		map->lock_key = lock_key;
+	} else {
+		mutex_init(&map->mutex);
+		map->lock = regmap_lock_mutex;
+		map->unlock = regmap_unlock_mutex;
+		map->can_sleep = true;
+		lockdep_set_class_and_name(&map->mutex, lock_key, lock_name);
 	}
 
+	map->lock_arg = map;
+	map->lock_key = lock_key;
+
+	return 0;
+}
+
+struct regmap *__regmap_init(struct device *dev,
+			     const struct regmap_bus *bus,
+			     void *bus_context,
+			     const struct regmap_config *config,
+			     struct lock_class_key *lock_key,
+			     const char *lock_name)
+{
+	struct regmap *map;
+	enum regmap_endian reg_endian, val_endian;
+	bool fast_io;
+	int i, j;
+	int ret;
+
+	if (!config) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	map = kzalloc_obj(*map);
+	if (map == NULL) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ret = regmap_set_name(map, config);
+	if (ret)
+		goto err_map;
+
+	fast_io = (bus && bus->fast_io) || config->fast_io;
+
+	ret = regmap_set_lock(map, config, fast_io, lock_key, lock_name);
+	if (ret)
+		goto err_name;
+
+	ret = -EINVAL; /* Later error paths rely on this */
+
 	/*
 	 * When we write in fast-paths with regmap_bulk_write() don't allocate
 	 * scratch buffers with sleeping allocations.

-- 
2.56.0


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

* [PATCH v2 06/10] regmap: factor out regmap_init_ranges()
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (4 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 05/10] regmap: factor out regmap_set_lock() Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 07/10] regmap: factor out format initialisation into helpers Peng Fan
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Move the register range validation and rbtree construction loop out of
__regmap_init() into a dedicated regmap_init_ranges() helper. This is a
mechanical code movement with no functional change apart from fixing the
block comment style of an existing comment that checkpatch flags when it
appears in the diff context.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap.c | 192 +++++++++++++++++++++++--------------------
 1 file changed, 101 insertions(+), 91 deletions(-)

diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index aca63b0d03e0..eaf7555f9765 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -748,6 +748,104 @@ static int regmap_set_lock(struct regmap *map, const struct regmap_config *confi
 	return 0;
 }
 
+/* Validate config->ranges and build the range rbtree. */
+static int regmap_init_ranges(struct regmap *map, const struct regmap_config *config)
+{
+	int i, j;
+
+	map->range_tree = RB_ROOT;
+
+	for (i = 0; i < config->num_ranges; i++) {
+		const struct regmap_range_cfg *range_cfg = &config->ranges[i];
+		struct regmap_range_node *new;
+
+		/* Sanity check */
+		if (range_cfg->range_max < range_cfg->range_min) {
+			dev_err(map->dev, "Invalid range %d: %u < %u\n", i,
+				range_cfg->range_max, range_cfg->range_min);
+			return -EINVAL;
+		}
+
+		if (range_cfg->range_max > map->max_register) {
+			dev_err(map->dev, "Invalid range %d: %u > %u\n", i,
+				range_cfg->range_max, map->max_register);
+			return -EINVAL;
+		}
+
+		if (range_cfg->selector_reg > map->max_register) {
+			dev_err(map->dev,
+				"Invalid range %d: selector out of map\n", i);
+			return -EINVAL;
+		}
+
+		if (range_cfg->window_len == 0) {
+			dev_err(map->dev, "Invalid range %d: window_len 0\n",
+				i);
+			return -EINVAL;
+		}
+
+		/*
+		 * Make sure that this register range has no selector
+		 * or data window within its boundary.
+		 */
+		for (j = 0; j < config->num_ranges; j++) {
+			unsigned int sel_reg = config->ranges[j].selector_reg;
+			unsigned int win_min = config->ranges[j].window_start;
+			unsigned int win_max = win_min +
+					       config->ranges[j].window_len - 1;
+
+			/* Allow data window inside its own virtual range */
+			if (j == i)
+				continue;
+
+			if (range_cfg->range_min <= sel_reg &&
+			    sel_reg <= range_cfg->range_max) {
+				dev_err(map->dev,
+					"Range %d: selector for %d in window\n",
+					i, j);
+				return -EINVAL;
+			}
+
+			if (!(win_max < range_cfg->range_min ||
+			      win_min > range_cfg->range_max)) {
+				dev_err(map->dev,
+					"Range %d: window for %d in window\n",
+					i, j);
+				return -EINVAL;
+			}
+		}
+
+		new = kzalloc_obj(*new);
+		if (new == NULL)
+			return -ENOMEM;
+
+		new->map = map;
+		new->name = range_cfg->name;
+		new->range_min = range_cfg->range_min;
+		new->range_max = range_cfg->range_max;
+		new->selector_reg = range_cfg->selector_reg;
+		new->selector_mask = range_cfg->selector_mask;
+		new->selector_shift = range_cfg->selector_shift;
+		new->window_start = range_cfg->window_start;
+		new->window_len = range_cfg->window_len;
+
+		if (!_regmap_range_add(map, new)) {
+			dev_err(map->dev, "Failed to add range %d\n", i);
+			kfree(new);
+			return -EINVAL;
+		}
+
+		if (map->selector_work_buf == NULL) {
+			map->selector_work_buf =
+				kzalloc(map->format.buf_size, GFP_KERNEL);
+			if (map->selector_work_buf == NULL)
+				return -ENOMEM;
+		}
+	}
+
+	return 0;
+}
+
 struct regmap *__regmap_init(struct device *dev,
 			     const struct regmap_bus *bus,
 			     void *bus_context,
@@ -758,7 +856,6 @@ struct regmap *__regmap_init(struct device *dev,
 	struct regmap *map;
 	enum regmap_endian reg_endian, val_endian;
 	bool fast_io;
-	int i, j;
 	int ret;
 
 	if (!config) {
@@ -1078,96 +1175,9 @@ struct regmap *__regmap_init(struct device *dev,
 
 skip_format_initialization:
 
-	map->range_tree = RB_ROOT;
-	for (i = 0; i < config->num_ranges; i++) {
-		const struct regmap_range_cfg *range_cfg = &config->ranges[i];
-		struct regmap_range_node *new;
-
-		/* Sanity check */
-		if (range_cfg->range_max < range_cfg->range_min) {
-			dev_err(map->dev, "Invalid range %d: %u < %u\n", i,
-				range_cfg->range_max, range_cfg->range_min);
-			goto err_range;
-		}
-
-		if (range_cfg->range_max > map->max_register) {
-			dev_err(map->dev, "Invalid range %d: %u > %u\n", i,
-				range_cfg->range_max, map->max_register);
-			goto err_range;
-		}
-
-		if (range_cfg->selector_reg > map->max_register) {
-			dev_err(map->dev,
-				"Invalid range %d: selector out of map\n", i);
-			goto err_range;
-		}
-
-		if (range_cfg->window_len == 0) {
-			dev_err(map->dev, "Invalid range %d: window_len 0\n",
-				i);
-			goto err_range;
-		}
-
-		/* Make sure, that this register range has no selector
-		   or data window within its boundary */
-		for (j = 0; j < config->num_ranges; j++) {
-			unsigned int sel_reg = config->ranges[j].selector_reg;
-			unsigned int win_min = config->ranges[j].window_start;
-			unsigned int win_max = win_min +
-					       config->ranges[j].window_len - 1;
-
-			/* Allow data window inside its own virtual range */
-			if (j == i)
-				continue;
-
-			if (range_cfg->range_min <= sel_reg &&
-			    sel_reg <= range_cfg->range_max) {
-				dev_err(map->dev,
-					"Range %d: selector for %d in window\n",
-					i, j);
-				goto err_range;
-			}
-
-			if (!(win_max < range_cfg->range_min ||
-			      win_min > range_cfg->range_max)) {
-				dev_err(map->dev,
-					"Range %d: window for %d in window\n",
-					i, j);
-				goto err_range;
-			}
-		}
-
-		new = kzalloc_obj(*new);
-		if (new == NULL) {
-			ret = -ENOMEM;
-			goto err_range;
-		}
-
-		new->map = map;
-		new->name = range_cfg->name;
-		new->range_min = range_cfg->range_min;
-		new->range_max = range_cfg->range_max;
-		new->selector_reg = range_cfg->selector_reg;
-		new->selector_mask = range_cfg->selector_mask;
-		new->selector_shift = range_cfg->selector_shift;
-		new->window_start = range_cfg->window_start;
-		new->window_len = range_cfg->window_len;
-
-		if (!_regmap_range_add(map, new)) {
-			dev_err(map->dev, "Failed to add range %d\n", i);
-			kfree(new);
-			goto err_range;
-		}
-
-		if (map->selector_work_buf == NULL) {
-			map->selector_work_buf =
-				kzalloc(map->format.buf_size, GFP_KERNEL);
-			if (map->selector_work_buf == NULL) {
-				ret = -ENOMEM;
-				goto err_range;
-			}
-		}
-	}
+	ret = regmap_init_ranges(map, config);
+	if (ret)
+		goto err_range;
 
 	ret = regcache_init(map, config);
 	if (ret != 0)

-- 
2.56.0


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

* [PATCH v2 07/10] regmap: factor out format initialisation into helpers
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (5 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 06/10] regmap: factor out regmap_init_ranges() Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 08/10] regmap: drop regmap_bus::free_on_exit Peng Fan
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Extract the two large nested switch statements that select
format_reg()/format_write() and format_val()/parse_val() into dedicated
regmap_set_reg_format() and regmap_set_val_format() helpers. Wrap the
entire format setup sequence - format selection, endianness cross-check,
work buffer allocation and bus write callback selection - into a single
regmap_init_format() function.

The two callers (config->read/write path with NATIVE endian, and the
bus->read/write path with bus-derived endian) now each call
regmap_init_format() directly, which eliminates the forward
"goto skip_format_initialization" and removes the label entirely.

The implicit "ret = -EINVAL; /* Later error paths rely on this */"
contract is replaced by explicit return codes in each helper.

No functional change.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap.c | 399 ++++++++++++++++++++++---------------------
 1 file changed, 200 insertions(+), 199 deletions(-)

diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index eaf7555f9765..ae313a8e4ffe 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -846,6 +846,197 @@ static int regmap_init_ranges(struct regmap *map, const struct regmap_config *co
 	return 0;
 }
 
+/* Pick format_reg()/format_write() for the register width and endianness. */
+static int regmap_set_reg_format(struct regmap *map, const struct regmap_config *config,
+				 enum regmap_endian reg_endian)
+{
+	switch (config->reg_bits + map->reg_shift) {
+	case 2:
+		if (config->val_bits != 6)
+			return -EINVAL;
+		map->format.format_write = regmap_format_2_6_write;
+		return 0;
+
+	case 4:
+		if (config->val_bits != 12)
+			return -EINVAL;
+		map->format.format_write = regmap_format_4_12_write;
+		return 0;
+
+	case 7:
+		switch (config->val_bits) {
+		case 9:
+			map->format.format_write = regmap_format_7_9_write;
+			return 0;
+		case 17:
+			map->format.format_write = regmap_format_7_17_write;
+			return 0;
+		}
+		return -EINVAL;
+
+	case 10:
+		if (config->val_bits != 14)
+			return -EINVAL;
+		map->format.format_write = regmap_format_10_14_write;
+		return 0;
+
+	case 12:
+		if (config->val_bits != 20)
+			return -EINVAL;
+		map->format.format_write = regmap_format_12_20_write;
+		return 0;
+
+	case 8:
+		map->format.format_reg = regmap_format_8;
+		return 0;
+
+	case 16:
+		switch (reg_endian) {
+		case REGMAP_ENDIAN_BIG:
+			map->format.format_reg = regmap_format_16_be;
+			return 0;
+		case REGMAP_ENDIAN_LITTLE:
+			map->format.format_reg = regmap_format_16_le;
+			return 0;
+		case REGMAP_ENDIAN_NATIVE:
+			map->format.format_reg = regmap_format_16_native;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+
+	case 24:
+		if (reg_endian != REGMAP_ENDIAN_BIG)
+			return -EINVAL;
+		map->format.format_reg = regmap_format_24_be;
+		return 0;
+
+	case 32:
+		switch (reg_endian) {
+		case REGMAP_ENDIAN_BIG:
+			map->format.format_reg = regmap_format_32_be;
+			return 0;
+		case REGMAP_ENDIAN_LITTLE:
+			map->format.format_reg = regmap_format_32_le;
+			return 0;
+		case REGMAP_ENDIAN_NATIVE:
+			map->format.format_reg = regmap_format_32_native;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/* Pick format_val()/parse_val() for the value width and endianness. */
+static int regmap_set_val_format(struct regmap *map, const struct regmap_config *config,
+				 enum regmap_endian val_endian)
+{
+	if (val_endian == REGMAP_ENDIAN_NATIVE)
+		map->format.parse_inplace = regmap_parse_inplace_noop;
+
+	switch (config->val_bits) {
+	case 8:
+		map->format.format_val = regmap_format_8;
+		map->format.parse_val = regmap_parse_8;
+		map->format.parse_inplace = regmap_parse_inplace_noop;
+		return 0;
+	case 16:
+		switch (val_endian) {
+		case REGMAP_ENDIAN_BIG:
+			map->format.format_val = regmap_format_16_be;
+			map->format.parse_val = regmap_parse_16_be;
+			map->format.parse_inplace = regmap_parse_16_be_inplace;
+			return 0;
+		case REGMAP_ENDIAN_LITTLE:
+			map->format.format_val = regmap_format_16_le;
+			map->format.parse_val = regmap_parse_16_le;
+			map->format.parse_inplace = regmap_parse_16_le_inplace;
+			return 0;
+		case REGMAP_ENDIAN_NATIVE:
+			map->format.format_val = regmap_format_16_native;
+			map->format.parse_val = regmap_parse_16_native;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	case 24:
+		if (val_endian != REGMAP_ENDIAN_BIG)
+			return -EINVAL;
+		map->format.format_val = regmap_format_24_be;
+		map->format.parse_val = regmap_parse_24_be;
+		return 0;
+	case 32:
+		switch (val_endian) {
+		case REGMAP_ENDIAN_BIG:
+			map->format.format_val = regmap_format_32_be;
+			map->format.parse_val = regmap_parse_32_be;
+			map->format.parse_inplace = regmap_parse_32_be_inplace;
+			return 0;
+		case REGMAP_ENDIAN_LITTLE:
+			map->format.format_val = regmap_format_32_le;
+			map->format.parse_val = regmap_parse_32_le;
+			map->format.parse_inplace = regmap_parse_32_le_inplace;
+			return 0;
+		case REGMAP_ENDIAN_NATIVE:
+			map->format.format_val = regmap_format_32_native;
+			map->format.parse_val = regmap_parse_32_native;
+			return 0;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Set up the format/parse functions, cross-validate endianness,
+ * allocate the work buffer and select the bus write path.
+ */
+static int regmap_init_format(struct regmap *map,
+			      const struct regmap_config *config,
+			      enum regmap_endian reg_endian,
+			      enum regmap_endian val_endian)
+{
+	int ret;
+
+	ret = regmap_set_reg_format(map, config, reg_endian);
+	if (ret)
+		return ret;
+
+	ret = regmap_set_val_format(map, config, val_endian);
+	if (ret)
+		return ret;
+
+	if (map->format.format_write) {
+		if (reg_endian != REGMAP_ENDIAN_BIG ||
+		    val_endian != REGMAP_ENDIAN_BIG)
+			return -EINVAL;
+		map->use_single_write = true;
+	}
+
+	if (!map->format.format_write &&
+	    !(map->format.format_reg && map->format.format_val))
+		return -EINVAL;
+
+	map->work_buf = kzalloc(map->format.buf_size, GFP_KERNEL);
+	if (map->work_buf == NULL)
+		return -ENOMEM;
+
+	if (map->format.format_write) {
+		map->defer_caching = false;
+		map->reg_write = _regmap_bus_formatted_write;
+	} else if (map->format.format_val) {
+		map->defer_caching = true;
+		map->reg_write = _regmap_bus_raw_write;
+	}
+
+	return 0;
+}
+
 struct regmap *__regmap_init(struct device *dev,
 			     const struct regmap_bus *bus,
 			     void *bus_context,
@@ -854,7 +1045,6 @@ struct regmap *__regmap_init(struct device *dev,
 			     const char *lock_name)
 {
 	struct regmap *map;
-	enum regmap_endian reg_endian, val_endian;
 	bool fast_io;
 	int ret;
 
@@ -879,8 +1069,6 @@ struct regmap *__regmap_init(struct device *dev,
 	if (ret)
 		goto err_name;
 
-	ret = -EINVAL; /* Later error paths rely on this */
-
 	/*
 	 * When we write in fast-paths with regmap_bulk_write() don't allocate
 	 * scratch buffers with sleeping allocations.
@@ -956,22 +1144,23 @@ struct regmap *__regmap_init(struct device *dev,
 		map->read = config->read;
 		map->write = config->write;
 
-		reg_endian = REGMAP_ENDIAN_NATIVE;
-		val_endian = REGMAP_ENDIAN_NATIVE;
+		ret = regmap_init_format(map, config,
+					 REGMAP_ENDIAN_NATIVE,
+					 REGMAP_ENDIAN_NATIVE);
+		if (ret)
+			goto err_hwlock;
 	} else if (!bus) {
 		map->reg_read  = config->reg_read;
 		map->reg_write = config->reg_write;
 		map->reg_update_bits = config->reg_update_bits;
 
 		map->defer_caching = false;
-		goto skip_format_initialization;
 	} else if (!bus->read || !bus->write) {
 		map->reg_read = _regmap_bus_reg_read;
 		map->reg_write = _regmap_bus_reg_write;
 		map->reg_update_bits = bus->reg_update_bits;
 
 		map->defer_caching = false;
-		goto skip_format_initialization;
 	} else {
 		map->reg_read  = _regmap_bus_read;
 		map->reg_update_bits = bus->reg_update_bits;
@@ -979,201 +1168,13 @@ struct regmap *__regmap_init(struct device *dev,
 		map->read = bus->read;
 		map->write = bus->write;
 
-		reg_endian = regmap_get_reg_endian(bus, config);
-		val_endian = regmap_get_val_endian(dev, bus, config);
-	}
-
-	switch (config->reg_bits + map->reg_shift) {
-	case 2:
-		switch (config->val_bits) {
-		case 6:
-			map->format.format_write = regmap_format_2_6_write;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 4:
-		switch (config->val_bits) {
-		case 12:
-			map->format.format_write = regmap_format_4_12_write;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 7:
-		switch (config->val_bits) {
-		case 9:
-			map->format.format_write = regmap_format_7_9_write;
-			break;
-		case 17:
-			map->format.format_write = regmap_format_7_17_write;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 10:
-		switch (config->val_bits) {
-		case 14:
-			map->format.format_write = regmap_format_10_14_write;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 12:
-		switch (config->val_bits) {
-		case 20:
-			map->format.format_write = regmap_format_12_20_write;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 8:
-		map->format.format_reg = regmap_format_8;
-		break;
-
-	case 16:
-		switch (reg_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_reg = regmap_format_16_be;
-			break;
-		case REGMAP_ENDIAN_LITTLE:
-			map->format.format_reg = regmap_format_16_le;
-			break;
-		case REGMAP_ENDIAN_NATIVE:
-			map->format.format_reg = regmap_format_16_native;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 24:
-		switch (reg_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_reg = regmap_format_24_be;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	case 32:
-		switch (reg_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_reg = regmap_format_32_be;
-			break;
-		case REGMAP_ENDIAN_LITTLE:
-			map->format.format_reg = regmap_format_32_le;
-			break;
-		case REGMAP_ENDIAN_NATIVE:
-			map->format.format_reg = regmap_format_32_native;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-
-	default:
-		goto err_hwlock;
-	}
-
-	if (val_endian == REGMAP_ENDIAN_NATIVE)
-		map->format.parse_inplace = regmap_parse_inplace_noop;
-
-	switch (config->val_bits) {
-	case 8:
-		map->format.format_val = regmap_format_8;
-		map->format.parse_val = regmap_parse_8;
-		map->format.parse_inplace = regmap_parse_inplace_noop;
-		break;
-	case 16:
-		switch (val_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_val = regmap_format_16_be;
-			map->format.parse_val = regmap_parse_16_be;
-			map->format.parse_inplace = regmap_parse_16_be_inplace;
-			break;
-		case REGMAP_ENDIAN_LITTLE:
-			map->format.format_val = regmap_format_16_le;
-			map->format.parse_val = regmap_parse_16_le;
-			map->format.parse_inplace = regmap_parse_16_le_inplace;
-			break;
-		case REGMAP_ENDIAN_NATIVE:
-			map->format.format_val = regmap_format_16_native;
-			map->format.parse_val = regmap_parse_16_native;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-	case 24:
-		switch (val_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_val = regmap_format_24_be;
-			map->format.parse_val = regmap_parse_24_be;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-	case 32:
-		switch (val_endian) {
-		case REGMAP_ENDIAN_BIG:
-			map->format.format_val = regmap_format_32_be;
-			map->format.parse_val = regmap_parse_32_be;
-			map->format.parse_inplace = regmap_parse_32_be_inplace;
-			break;
-		case REGMAP_ENDIAN_LITTLE:
-			map->format.format_val = regmap_format_32_le;
-			map->format.parse_val = regmap_parse_32_le;
-			map->format.parse_inplace = regmap_parse_32_le_inplace;
-			break;
-		case REGMAP_ENDIAN_NATIVE:
-			map->format.format_val = regmap_format_32_native;
-			map->format.parse_val = regmap_parse_32_native;
-			break;
-		default:
-			goto err_hwlock;
-		}
-		break;
-	}
-
-	if (map->format.format_write) {
-		if ((reg_endian != REGMAP_ENDIAN_BIG) ||
-		    (val_endian != REGMAP_ENDIAN_BIG))
+		ret = regmap_init_format(map, config,
+					 regmap_get_reg_endian(bus, config),
+					 regmap_get_val_endian(dev, bus, config));
+		if (ret)
 			goto err_hwlock;
-		map->use_single_write = true;
-	}
-
-	if (!map->format.format_write &&
-	    !(map->format.format_reg && map->format.format_val))
-		goto err_hwlock;
-
-	map->work_buf = kzalloc(map->format.buf_size, GFP_KERNEL);
-	if (map->work_buf == NULL) {
-		ret = -ENOMEM;
-		goto err_hwlock;
-	}
-
-	if (map->format.format_write) {
-		map->defer_caching = false;
-		map->reg_write = _regmap_bus_formatted_write;
-	} else if (map->format.format_val) {
-		map->defer_caching = true;
-		map->reg_write = _regmap_bus_raw_write;
 	}
 
-skip_format_initialization:
 
 	ret = regmap_init_ranges(map, config);
 	if (ret)

-- 
2.56.0


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

* [PATCH v2 08/10] regmap: drop regmap_bus::free_on_exit
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (6 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 07/10] regmap: factor out format initialisation into helpers Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 09/10] regmap: use cleanup API in __regmap_init() Peng Fan
  2026-11-13 15:31 ` [PATCH v2 10/10] regmap: kunit: add test for regmap_bus::init_limits Peng Fan
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

The SPI and I2C bus helpers kmemdup the static regmap_bus to override
max_raw_read and max_raw_write per device and mark the copy with
free_on_exit so that the regmap core frees it. This is the only user of
free_on_exit, and the per-device limits can instead be applied to the
map while it is being initialized.

Add an optional regmap_bus::init_limits() callback which is invoked by
__regmap_init() after the bus raw I/O limits have been copied and before
the cache is initialized. The latter matters for maps with raw register
defaults: regcache_hw_init() reads them from the device while the map is
still being initialized and uses max_raw_read to chunk the transfer.

Use the callback for the I2C and SPI buses, which allows using the static
bus structures directly. This removes the per-device bus allocations and
lets us drop free_on_exit along with the associated error and exit paths
in the regmap core.

No functional change intended.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap-i2c.c | 83 ++++++++++++++++++++--------------------
 drivers/base/regmap/regmap-spi.c | 64 ++++++++++++-------------------
 drivers/base/regmap/regmap.c     |  7 ++--
 include/linux/regmap.h           |  7 +++-
 4 files changed, 75 insertions(+), 86 deletions(-)

diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index a4a259232980..0f27966157a3 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -13,6 +13,41 @@
 
 #include "internal.h"
 
+static void regmap_i2c_init_limits(struct regmap *map,
+				   const struct regmap_config *config)
+{
+	struct device *dev = map->bus_context;
+	struct i2c_client *i2c = to_i2c_client(dev);
+	const struct i2c_adapter_quirks *quirks = i2c->adapter->quirks;
+	size_t val_bytes = map->format.val_bytes;
+	size_t max_read = map->max_raw_read;
+	size_t max_write = map->max_raw_write;
+
+	if (!quirks)
+		return;
+
+	if (quirks->max_read_len &&
+	    quirks->max_read_len >= val_bytes &&
+	    (max_read == 0 || max_read > quirks->max_read_len))
+		max_read = quirks->max_read_len;
+
+	if (quirks->max_write_len) {
+		size_t reg_reserve_size, max_payload;
+
+		reg_reserve_size = (config->reg_bits + config->pad_bits) /
+				   BITS_PER_BYTE;
+		if (quirks->max_write_len > reg_reserve_size) {
+			max_payload = quirks->max_write_len - reg_reserve_size;
+			if (max_payload >= val_bytes &&
+			    (max_write == 0 || max_write > max_payload))
+				max_write = max_payload;
+		}
+	}
+
+	map->max_raw_read = max_read;
+	map->max_raw_write = max_write;
+}
+
 static int regmap_smbus_byte_reg_read(void *context, unsigned int reg,
 				      unsigned int *val)
 {
@@ -47,6 +82,7 @@ static int regmap_smbus_byte_reg_write(void *context, unsigned int reg,
 static const struct regmap_bus regmap_smbus_byte = {
 	.reg_write = regmap_smbus_byte_reg_write,
 	.reg_read = regmap_smbus_byte_reg_read,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static int regmap_smbus_word_reg_read(void *context, unsigned int reg,
@@ -83,6 +119,7 @@ static int regmap_smbus_word_reg_write(void *context, unsigned int reg,
 static const struct regmap_bus regmap_smbus_word = {
 	.reg_write = regmap_smbus_word_reg_write,
 	.reg_read = regmap_smbus_word_reg_read,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static int regmap_smbus_word_read_swapped(void *context, unsigned int reg,
@@ -119,6 +156,7 @@ static int regmap_smbus_word_write_swapped(void *context, unsigned int reg,
 static const struct regmap_bus regmap_smbus_word_swapped = {
 	.reg_write = regmap_smbus_word_write_swapped,
 	.reg_read = regmap_smbus_word_read_swapped,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static int regmap_i2c_write(void *context, const void *data, size_t count)
@@ -204,6 +242,7 @@ static const struct regmap_bus regmap_i2c = {
 	.read = regmap_i2c_read,
 	.reg_format_endian_default = REGMAP_ENDIAN_BIG,
 	.val_format_endian_default = REGMAP_ENDIAN_BIG,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static int regmap_i2c_smbus_i2c_write(void *context, const void *data,
@@ -245,6 +284,7 @@ static const struct regmap_bus regmap_i2c_smbus_i2c_block = {
 	.read = regmap_i2c_smbus_i2c_read,
 	.max_raw_read = I2C_SMBUS_BLOCK_MAX - 1,
 	.max_raw_write = I2C_SMBUS_BLOCK_MAX - 1,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static int regmap_i2c_smbus_i2c_write_reg16(void *context, const void *data,
@@ -302,6 +342,7 @@ static const struct regmap_bus regmap_i2c_smbus_i2c_block_reg16 = {
 	.read = regmap_i2c_smbus_i2c_read_reg16,
 	.max_raw_read = I2C_SMBUS_BLOCK_MAX - 2,
 	.max_raw_write = I2C_SMBUS_BLOCK_MAX - 2,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 /*
@@ -346,16 +387,13 @@ static const struct regmap_bus regmap_smbus_byte_word_reg16 = {
 	.read = regmap_i2c_smbus_i2c_read_reg16,
 	.max_raw_read = I2C_SMBUS_BLOCK_MAX - 2,
 	.max_raw_write = 1,
+	.init_limits = regmap_i2c_init_limits,
 };
 
 static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 					const struct regmap_config *config)
 {
-	const struct i2c_adapter_quirks *quirks;
 	const struct regmap_bus *bus = NULL;
-	struct regmap_bus *ret_bus;
-	size_t val_bytes;
-	u16 max_read, max_write;
 
 	if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
 		bus = &regmap_i2c;
@@ -393,43 +431,6 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
 	if (!bus)
 		return ERR_PTR(-ENOTSUPP);
 
-	max_read = bus->max_raw_read;
-	max_write = bus->max_raw_write;
-	val_bytes = BITS_TO_BYTES(config->val_bits);
-
-	quirks = i2c->adapter->quirks;
-	if (quirks) {
-		if (quirks->max_read_len &&
-		    quirks->max_read_len >= val_bytes &&
-		    (max_read == 0 || max_read > quirks->max_read_len))
-			max_read = quirks->max_read_len;
-
-		if (quirks->max_write_len) {
-			size_t reg_reserve_size, max_payload;
-
-			reg_reserve_size = (config->reg_bits + config->pad_bits) /
-					   BITS_PER_BYTE;
-			if (quirks->max_write_len > reg_reserve_size) {
-				max_payload = quirks->max_write_len -
-					      reg_reserve_size;
-				if (max_payload >= val_bytes &&
-				    (max_write == 0 || max_write > max_payload))
-					max_write = max_payload;
-			}
-		}
-
-		if (max_read != bus->max_raw_read ||
-		    max_write != bus->max_raw_write) {
-			ret_bus = kmemdup(bus, sizeof(*bus), GFP_KERNEL);
-			if (!ret_bus)
-				return ERR_PTR(-ENOMEM);
-			ret_bus->free_on_exit = true;
-			ret_bus->max_raw_read = max_read;
-			ret_bus->max_raw_write = max_write;
-			bus = ret_bus;
-		}
-	}
-
 	return bus;
 }
 
diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c
index b9fec387997e..8eaebef07833 100644
--- a/drivers/base/regmap/regmap-spi.c
+++ b/drivers/base/regmap/regmap-spi.c
@@ -98,6 +98,26 @@ static int regmap_spi_read(void *context,
 	return spi_write_then_read(spi, reg, reg_size, val, val_size);
 }
 
+static void regmap_spi_init_limits(struct regmap *map,
+				   const struct regmap_config *config)
+{
+	struct device *dev = map->bus_context;
+	struct spi_device *spi = to_spi_device(dev);
+	size_t max_size = spi_max_transfer_size(spi);
+	size_t max_msg_size, reg_reserve_size;
+
+	if (max_size == SIZE_MAX)
+		return;
+
+	max_msg_size = spi_max_message_size(spi);
+	reg_reserve_size = (config->reg_bits + config->pad_bits) / BITS_PER_BYTE;
+	if (max_size + reg_reserve_size > max_msg_size)
+		max_size -= reg_reserve_size;
+
+	map->max_raw_read = max_size;
+	map->max_raw_write = max_size;
+}
+
 static const struct regmap_bus regmap_spi = {
 	.write = regmap_spi_write,
 	.gather_write = regmap_spi_gather_write,
@@ -107,46 +127,16 @@ static const struct regmap_bus regmap_spi = {
 	.read_flag_mask = 0x80,
 	.reg_format_endian_default = REGMAP_ENDIAN_BIG,
 	.val_format_endian_default = REGMAP_ENDIAN_BIG,
+	.init_limits = regmap_spi_init_limits,
 };
 
-static const struct regmap_bus *regmap_get_spi_bus(struct spi_device *spi,
-						   const struct regmap_config *config)
-{
-	size_t max_size = spi_max_transfer_size(spi);
-	size_t max_msg_size, reg_reserve_size;
-	struct regmap_bus *bus;
-
-	if (max_size != SIZE_MAX) {
-		bus = kmemdup(&regmap_spi, sizeof(*bus), GFP_KERNEL);
-		if (!bus)
-			return ERR_PTR(-ENOMEM);
-
-		max_msg_size = spi_max_message_size(spi);
-		reg_reserve_size = (config->reg_bits + config->pad_bits) / BITS_PER_BYTE;
-		if (max_size + reg_reserve_size > max_msg_size)
-			max_size -= reg_reserve_size;
-
-		bus->free_on_exit = true;
-		bus->max_raw_read = max_size;
-		bus->max_raw_write = max_size;
-
-		return bus;
-	}
-
-	return &regmap_spi;
-}
-
 struct regmap *__regmap_init_spi(struct spi_device *spi,
 				 const struct regmap_config *config,
 				 struct lock_class_key *lock_key,
 				 const char *lock_name)
 {
-	const struct regmap_bus *bus = regmap_get_spi_bus(spi, config);
-
-	if (IS_ERR(bus))
-		return ERR_CAST(bus);
-
-	return __regmap_init(&spi->dev, bus, &spi->dev, config, lock_key, lock_name);
+	return __regmap_init(&spi->dev, &regmap_spi, &spi->dev, config,
+			     lock_key, lock_name);
 }
 EXPORT_SYMBOL_GPL(__regmap_init_spi);
 
@@ -155,12 +145,8 @@ struct regmap *__devm_regmap_init_spi(struct spi_device *spi,
 				      struct lock_class_key *lock_key,
 				      const char *lock_name)
 {
-	const struct regmap_bus *bus = regmap_get_spi_bus(spi, config);
-
-	if (IS_ERR(bus))
-		return ERR_CAST(bus);
-
-	return __devm_regmap_init(&spi->dev, bus, &spi->dev, config, lock_key, lock_name);
+	return __devm_regmap_init(&spi->dev, &regmap_spi, &spi->dev, config,
+				  lock_key, lock_name);
 }
 EXPORT_SYMBOL_GPL(__devm_regmap_init_spi);
 
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index ae313a8e4ffe..04cbf15adcb0 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1180,6 +1180,9 @@ struct regmap *__regmap_init(struct device *dev,
 	if (ret)
 		goto err_range;
 
+	if (bus && bus->init_limits)
+		bus->init_limits(map, config);
+
 	ret = regcache_init(map, config);
 	if (ret != 0)
 		goto err_range;
@@ -1207,8 +1210,6 @@ struct regmap *__regmap_init(struct device *dev,
 err_map:
 	kfree(map);
 err:
-	if (bus && bus->free_on_exit)
-		kfree(bus);
 	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(__regmap_init);
@@ -1519,8 +1520,6 @@ void regmap_exit(struct regmap *map)
 		mutex_destroy(&map->mutex);
 	kfree_const(map->name);
 	kfree(map->patch);
-	if (map->bus && map->bus->free_on_exit)
-		kfree(map->bus);
 	kfree(map);
 }
 EXPORT_SYMBOL_GPL(regmap_exit);
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 8f3034881f8a..8f4a81243013 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -580,7 +580,6 @@ typedef void (*regmap_hw_free_context)(void *context);
  *	     to perform locking. This field is ignored if custom lock/unlock
  *	     functions are used (see fields lock/unlock of
  *	     struct regmap_config).
- * @free_on_exit: kfree this on exit of regmap
  * @write: Write operation.
  * @gather_write: Write operation with split register/value, return -ENOTSUPP
  *                if not implemented  on a given device.
@@ -611,10 +610,12 @@ typedef void (*regmap_hw_free_context)(void *context);
  *     DEFAULT, BIG is assumed.
  * @max_raw_read: Max raw read size that can be used on the bus.
  * @max_raw_write: Max raw write size that can be used on the bus.
+ * @init_limits: Optional callback to adjust the maximum raw read and write
+ *     sizes for a device. Called during regmap initialization before the
+ *     cache is initialized.
  */
 struct regmap_bus {
 	bool fast_io;
-	bool free_on_exit;
 	regmap_hw_write write;
 	regmap_hw_gather_write gather_write;
 	regmap_hw_async_write async_write;
@@ -631,6 +632,8 @@ struct regmap_bus {
 	enum regmap_endian val_format_endian_default;
 	size_t max_raw_read;
 	size_t max_raw_write;
+	void (*init_limits)(struct regmap *map,
+			    const struct regmap_config *config);
 };
 
 /*

-- 
2.56.0


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

* [PATCH v2 09/10] regmap: use cleanup API in __regmap_init()
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (7 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 08/10] regmap: drop regmap_bus::free_on_exit Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  2026-11-13 15:31 ` [PATCH v2 10/10] regmap: kunit: add test for regmap_bus::init_limits Peng Fan
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Convert the error unwinding in __regmap_init() to the scope-based
cleanup API and drop all of the err_* labels. A dedicated
regmap_init_release() helper unwinds a partially initialised map in the
same order as the old labels.

The err label only returned ERR_PTR(ret) after the per-device bus
allocation was removed, so the two early failure paths now return
directly as well.

regcache_exit() requires an initialised cache, so map->cache_type is now
set right before regcache_init() instead of during the initial map setup
and reset to REGCACHE_NONE if regcache_init() fails. This makes
regcache_exit() safe to call from the cleanup path at any point. Nothing
between the old and new assignment reads cache_type.

No functional change.

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap.c | 69 +++++++++++++++++++++-----------------------
 1 file changed, 33 insertions(+), 36 deletions(-)

diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index 04cbf15adcb0..8a68c9bcebc8 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1037,6 +1037,22 @@ static int regmap_init_format(struct regmap *map,
 	return 0;
 }
 
+static void regmap_init_release(struct regmap *map)
+{
+	if (!map)
+		return;
+
+	regcache_exit(map);
+	regmap_range_exit(map);
+	kfree(map->work_buf);
+	if (map->hwlock)
+		hwspin_lock_free(map->hwlock);
+	kfree_const(map->name);
+	kfree(map);
+}
+
+DEFINE_FREE(regmap_init_release, struct regmap *, regmap_init_release(_T));
+
 struct regmap *__regmap_init(struct device *dev,
 			     const struct regmap_bus *bus,
 			     void *bus_context,
@@ -1044,30 +1060,25 @@ struct regmap *__regmap_init(struct device *dev,
 			     struct lock_class_key *lock_key,
 			     const char *lock_name)
 {
-	struct regmap *map;
 	bool fast_io;
 	int ret;
 
-	if (!config) {
-		ret = -EINVAL;
-		goto err;
-	}
+	if (!config)
+		return ERR_PTR(-EINVAL);
 
-	map = kzalloc_obj(*map);
-	if (map == NULL) {
-		ret = -ENOMEM;
-		goto err;
-	}
+	struct regmap *map __free(regmap_init_release) = kzalloc_obj(*map);
+	if (map == NULL)
+		return ERR_PTR(-ENOMEM);
 
 	ret = regmap_set_name(map, config);
 	if (ret)
-		goto err_map;
+		return ERR_PTR(ret);
 
 	fast_io = (bus && bus->fast_io) || config->fast_io;
 
 	ret = regmap_set_lock(map, config, fast_io, lock_key, lock_name);
 	if (ret)
-		goto err_name;
+		return ERR_PTR(ret);
 
 	/*
 	 * When we write in fast-paths with regmap_bulk_write() don't allocate
@@ -1119,7 +1130,6 @@ struct regmap *__regmap_init(struct device *dev,
 	map->writeable_noinc_reg = config->writeable_noinc_reg;
 	map->readable_noinc_reg = config->readable_noinc_reg;
 	map->reg_default_cb = config->reg_default_cb;
-	map->cache_type = config->cache_type;
 
 	spin_lock_init(&map->async_lock);
 	INIT_LIST_HEAD(&map->async_list);
@@ -1148,7 +1158,7 @@ struct regmap *__regmap_init(struct device *dev,
 					 REGMAP_ENDIAN_NATIVE,
 					 REGMAP_ENDIAN_NATIVE);
 		if (ret)
-			goto err_hwlock;
+			return ERR_PTR(ret);
 	} else if (!bus) {
 		map->reg_read  = config->reg_read;
 		map->reg_write = config->reg_write;
@@ -1172,45 +1182,32 @@ struct regmap *__regmap_init(struct device *dev,
 					 regmap_get_reg_endian(bus, config),
 					 regmap_get_val_endian(dev, bus, config));
 		if (ret)
-			goto err_hwlock;
+			return ERR_PTR(ret);
 	}
 
-
 	ret = regmap_init_ranges(map, config);
 	if (ret)
-		goto err_range;
+		return ERR_PTR(ret);
 
 	if (bus && bus->init_limits)
 		bus->init_limits(map, config);
 
+	map->cache_type = config->cache_type;
 	ret = regcache_init(map, config);
-	if (ret != 0)
-		goto err_range;
+	if (ret) {
+		map->cache_type = REGCACHE_NONE;
+		return ERR_PTR(ret);
+	}
 
 	if (dev) {
 		ret = regmap_attach_dev(dev, map, config);
 		if (ret != 0)
-			goto err_regcache;
+			return ERR_PTR(ret);
 	} else {
 		regmap_debugfs_init(map);
 	}
 
-	return map;
-
-err_regcache:
-	regcache_exit(map);
-err_range:
-	regmap_range_exit(map);
-	kfree(map->work_buf);
-err_hwlock:
-	if (map->hwlock)
-		hwspin_lock_free(map->hwlock);
-err_name:
-	kfree_const(map->name);
-err_map:
-	kfree(map);
-err:
-	return ERR_PTR(ret);
+	return_ptr(map);
 }
 EXPORT_SYMBOL_GPL(__regmap_init);
 

-- 
2.56.0


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

* [PATCH v2 10/10] regmap: kunit: add test for regmap_bus::init_limits
  2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
                   ` (8 preceding siblings ...)
  2026-11-13 15:31 ` [PATCH v2 09/10] regmap: use cleanup API in __regmap_init() Peng Fan
@ 2026-11-13 15:31 ` Peng Fan
  9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-13 15:31 UTC (permalink / raw)
  To: Mark Brown, Greg Kroah-Hartman, Rafael J. Wysocki,
	Danilo Krummrich, Johan Hovold, Lucas Tanure
  Cc: linux-kernel, driver-core, Peng Fan

From: Peng Fan <peng.fan@nxp.com>

Add coverage for the optional regmap_bus::init_limits() callback. The
test bus lowers the raw I/O limits and relies on raw register defaults so
that regcache_hw_init() reads from the device during map initialization.
The readback must be split according to the limits set by the callback,
which also verifies that __regmap_init() invokes the callback before
regcache_init().

Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
 drivers/base/regmap/regmap-kunit.c | 89 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 89 insertions(+)

diff --git a/drivers/base/regmap/regmap-kunit.c b/drivers/base/regmap/regmap-kunit.c
index c948219f46a3..f2fe2eef3403 100644
--- a/drivers/base/regmap/regmap-kunit.c
+++ b/drivers/base/regmap/regmap-kunit.c
@@ -2247,6 +2247,94 @@ static void raw_ranges(struct kunit *test)
 	}
 }
 
+#define TEST_INIT_LIMITS_REGS 16
+#define TEST_INIT_LIMITS_MAX_RAW 4
+
+struct test_init_limits_data {
+	int num_reads;
+	size_t read_sizes[TEST_INIT_LIMITS_REGS];
+};
+
+static void test_init_limits_bus_init_limits(struct regmap *map,
+					     const struct regmap_config *config)
+{
+	map->max_raw_read = TEST_INIT_LIMITS_MAX_RAW;
+	map->max_raw_write = TEST_INIT_LIMITS_MAX_RAW;
+}
+
+static int test_init_limits_bus_read(void *context, const void *reg_buf,
+				     size_t reg_size, void *val_buf,
+				     size_t val_size)
+{
+	struct test_init_limits_data *data = context;
+
+	if (data->num_reads < ARRAY_SIZE(data->read_sizes))
+		data->read_sizes[data->num_reads] = val_size;
+	data->num_reads++;
+
+	memset(val_buf, 0, val_size);
+
+	return 0;
+}
+
+static int test_init_limits_bus_write(void *context, const void *data,
+				      size_t count)
+{
+	return 0;
+}
+
+static const struct regmap_bus test_init_limits_bus = {
+	.read = test_init_limits_bus_read,
+	.write = test_init_limits_bus_write,
+	.init_limits = test_init_limits_bus_init_limits,
+};
+
+static const struct regmap_config test_init_limits_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = TEST_INIT_LIMITS_REGS - 1,
+	.cache_type = REGCACHE_FLAT,
+	.num_reg_defaults_raw = TEST_INIT_LIMITS_REGS,
+};
+
+static void test_init_limits(struct kunit *test)
+{
+	struct regmap_test_priv *priv = test->priv;
+	struct test_init_limits_data *data;
+	struct regmap *map;
+	int i, ret;
+
+	data = kunit_kzalloc(test, sizeof(*data), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, data);
+
+	map = regmap_init(priv->dev, &test_init_limits_bus, data,
+			  &test_init_limits_config);
+	KUNIT_ASSERT_FALSE(test, IS_ERR(map));
+	if (IS_ERR(map))
+		return;
+
+	ret = kunit_add_action_or_reset(test, regmap_exit_action, map);
+	KUNIT_ASSERT_EQ(test, 0, ret);
+	if (ret)
+		return;
+
+	KUNIT_EXPECT_EQ(test, (size_t)TEST_INIT_LIMITS_MAX_RAW,
+			regmap_get_raw_read_max(map));
+	KUNIT_EXPECT_EQ(test, (size_t)TEST_INIT_LIMITS_MAX_RAW,
+			regmap_get_raw_write_max(map));
+
+	/*
+	 * regcache_hw_init() reads raw defaults during map initialization,
+	 * so the callback must already have set the limit which chunks the
+	 * readback.
+	 */
+	KUNIT_ASSERT_EQ(test, TEST_INIT_LIMITS_REGS / TEST_INIT_LIMITS_MAX_RAW,
+			data->num_reads);
+	for (i = 0; i < data->num_reads; i++)
+		KUNIT_EXPECT_EQ(test, (size_t)TEST_INIT_LIMITS_MAX_RAW,
+				data->read_sizes[i]);
+}
+
 static struct kunit_case regmap_test_cases[] = {
 	KUNIT_CASE_PARAM(basic_read_write, regcache_types_gen_params),
 	KUNIT_CASE_PARAM(read_bypassed, real_cache_types_gen_params),
@@ -2290,6 +2378,7 @@ static struct kunit_case regmap_test_cases[] = {
 	KUNIT_CASE_PARAM(raw_noinc_write, raw_test_types_gen_params),
 	KUNIT_CASE_PARAM(raw_sync, raw_test_cache_types_gen_params),
 	KUNIT_CASE_PARAM(raw_ranges, raw_test_cache_types_gen_params),
+	KUNIT_CASE(test_init_limits),
 	{}
 };
 

-- 
2.56.0


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

end of thread, other threads:[~2026-11-13 15:32 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-13 15:31 [PATCH v2 00/10] regmap: clean up and improve __regmap_init() Peng Fan
2026-11-13 15:31 ` [PATCH v2 01/10] regmap: fix reinit cache failure handling Peng Fan
2026-11-13 15:31 ` [PATCH v2 02/10] regmap-i2c: keep bus raw write limit when applying read quirk Peng Fan
2026-11-13 15:31 ` [PATCH v2 03/10] regmap-i2c: fix write limit clamping for adapter quirks Peng Fan
2026-11-13 15:31 ` [PATCH v2 04/10] regmap: deduplicate fast_io checks Peng Fan
2026-11-13 15:31 ` [PATCH v2 05/10] regmap: factor out regmap_set_lock() Peng Fan
2026-11-13 15:31 ` [PATCH v2 06/10] regmap: factor out regmap_init_ranges() Peng Fan
2026-11-13 15:31 ` [PATCH v2 07/10] regmap: factor out format initialisation into helpers Peng Fan
2026-11-13 15:31 ` [PATCH v2 08/10] regmap: drop regmap_bus::free_on_exit Peng Fan
2026-11-13 15:31 ` [PATCH v2 09/10] regmap: use cleanup API in __regmap_init() Peng Fan
2026-11-13 15:31 ` [PATCH v2 10/10] regmap: kunit: add test for regmap_bus::init_limits Peng Fan

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

all inboxes | Powered by JetHome®