* [PATCH 1/9] regmap-i2c: keep bus raw write limit when applying read quirk
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 2/9] regmap-i2c: avoid underflow when applying write length quirk Peng Fan (OSS)
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 = ®map_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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 2/9] regmap-i2c: avoid underflow when applying write length quirk
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 1/9] regmap-i2c: keep bus raw write limit when applying read quirk Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 3/9] regmap: deduplicate fast_io checks Peng Fan (OSS)
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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, so regmap_get_i2c_bus() subtracts the register
address size to obtain the maximum raw payload. If max_write_len is not
larger than the register address size, that subtraction underflows and
produces a huge limit; when the sizes are equal it produces zero, which
regmap also treats as no limit.
No raw write can succeed with such an adapter because the register
address alone exceeds the quirk limit, so keep the current limit instead
of replacing it with a bogus value.
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 | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index 8663a4ec0a3d..eb3db02ad8fb 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -401,9 +401,14 @@ static const struct regmap_bus *regmap_get_i2c_bus(struct i2c_client *i2c,
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;
+ (max_write == 0 || max_write > quirks->max_write_len)) {
+ size_t reg_reserve_size;
+
+ reg_reserve_size = (config->reg_bits + config->pad_bits) /
+ BITS_PER_BYTE;
+ if (quirks->max_write_len > reg_reserve_size)
+ max_write = quirks->max_write_len - reg_reserve_size;
+ }
if (max_read != bus->max_raw_read ||
max_write != bus->max_raw_write) {
--
2.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 3/9] regmap: deduplicate fast_io checks
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 1/9] regmap-i2c: keep bus raw write limit when applying read quirk Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 2/9] regmap-i2c: avoid underflow when applying write length quirk Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 4/9] regmap: factor out regmap_set_lock() Peng Fan (OSS)
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 changes.
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 83f1f5942135..dff6511ab50e 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -681,6 +681,7 @@ struct regmap *__regmap_init(struct device *dev,
const char *lock_name)
{
struct regmap *map;
+ bool fast_io;
int ret = -EINVAL;
enum regmap_endian reg_endian, val_endian;
int i, j;
@@ -700,6 +701,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;
@@ -733,8 +736,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;
@@ -764,10 +766,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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 4/9] regmap: factor out regmap_set_lock()
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (2 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 3/9] regmap: deduplicate fast_io checks Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 5/9] regmap: factor out regmap_init_ranges() Peng Fan (OSS)
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/base/regmap/regmap.c | 134 +++++++++++++++++++++++++------------------
1 file changed, 77 insertions(+), 57 deletions(-)
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index dff6511ab50e..b079921287c2 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -673,51 +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;
- bool fast_io;
- int ret = -EINVAL;
- enum regmap_endian reg_endian, val_endian;
- int i, j;
-
- if (!config)
- 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:
@@ -735,33 +717,71 @@ 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;
+ bool fast_io;
+ int ret = -EINVAL;
+ enum regmap_endian reg_endian, val_endian;
+ int i, j;
+
+ if (!config)
+ 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;
+
+ ret = regmap_set_lock(map, config, fast_io, lock_key, lock_name);
+ if (ret)
+ goto err_name;
+
/*
* When we write in fast-paths with regmap_bulk_write() don't allocate
* scratch buffers with sleeping allocations.
--
2.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 5/9] regmap: factor out regmap_init_ranges()
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (3 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 4/9] regmap: factor out regmap_set_lock() Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 6/9] regmap: factor out format initialisation into helpers Peng Fan (OSS)
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 b079921287c2..c554079835f5 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,
@@ -759,7 +857,6 @@ struct regmap *__regmap_init(struct device *dev,
bool fast_io;
int ret = -EINVAL;
enum regmap_endian reg_endian, val_endian;
- int i, j;
if (!config)
goto err;
@@ -1076,96 +1173,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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 6/9] regmap: factor out format initialisation into helpers
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (4 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 5/9] regmap: factor out regmap_init_ranges() Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 7/9] regmap: drop regmap_bus::free_on_exit Peng Fan (OSS)
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 | 398 ++++++++++++++++++++++---------------------
1 file changed, 200 insertions(+), 198 deletions(-)
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index c554079835f5..0a7f73fc1101 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,
@@ -856,7 +1047,6 @@ struct regmap *__regmap_init(struct device *dev,
struct regmap *map;
bool fast_io;
int ret = -EINVAL;
- enum regmap_endian reg_endian, val_endian;
if (!config)
goto err;
@@ -871,7 +1061,6 @@ struct regmap *__regmap_init(struct device *dev,
if (ret)
goto err_map;
- ret = -EINVAL; /* Later error paths rely on this */
fast_io = (bus && bus->fast_io) || config->fast_io;
@@ -954,22 +1143,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;
@@ -977,201 +1167,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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 7/9] regmap: drop regmap_bus::free_on_exit
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (5 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 6/9] regmap: factor out format initialisation into helpers Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 8/9] regmap: use cleanup API in __regmap_init() Peng Fan (OSS)
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 changes.
Signed-off-by: Peng Fan <peng.fan@nxp.com>
---
drivers/base/regmap/regmap-i2c.c | 71 +++++++++++++++++++++-------------------
drivers/base/regmap/regmap-spi.c | 64 ++++++++++++++----------------------
drivers/base/regmap/regmap.c | 7 ++--
include/linux/regmap.h | 7 ++--
4 files changed, 70 insertions(+), 79 deletions(-)
diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c
index eb3db02ad8fb..247dd20eb02a 100644
--- a/drivers/base/regmap/regmap-i2c.c
+++ b/drivers/base/regmap/regmap-i2c.c
@@ -12,6 +12,36 @@
#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 max_read = map->max_raw_read;
+ size_t max_write = map->max_raw_write;
+
+ if (!quirks)
+ return;
+
+ if (quirks->max_read_len &&
+ (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)) {
+ size_t reg_reserve_size;
+
+ reg_reserve_size = (config->reg_bits + config->pad_bits) /
+ BITS_PER_BYTE;
+ if (quirks->max_write_len > reg_reserve_size)
+ max_write = quirks->max_write_len - reg_reserve_size;
+ }
+
+ 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)
{
@@ -46,6 +76,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,
@@ -82,6 +113,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,
@@ -118,6 +150,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)
@@ -203,6 +236,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,
@@ -244,6 +278,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,
@@ -301,6 +336,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,
};
/*
@@ -345,15 +381,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;
- u16 max_read, max_write;
if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C))
bus = ®map_i2c;
@@ -391,37 +425,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;
-
- quirks = i2c->adapter->quirks;
- if (quirks) {
- if (quirks->max_read_len &&
- (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)) {
- size_t reg_reserve_size;
-
- reg_reserve_size = (config->reg_bits + config->pad_bits) /
- BITS_PER_BYTE;
- if (quirks->max_write_len > reg_reserve_size)
- max_write = quirks->max_write_len - reg_reserve_size;
- }
-
- 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(®map_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 ®map_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, ®map_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, ®map_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 0a7f73fc1101..c5acfe687d9c 100644
--- a/drivers/base/regmap/regmap.c
+++ b/drivers/base/regmap/regmap.c
@@ -1179,6 +1179,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;
@@ -1206,8 +1209,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);
@@ -1513,8 +1514,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 a72f17ab6264..161457726163 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -579,7 +579,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.
@@ -610,10 +609,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;
@@ -630,6 +631,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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 8/9] regmap: use cleanup API in __regmap_init()
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (6 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 7/9] regmap: drop regmap_bus::free_on_exit Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 8:25 ` [PATCH 9/9] regmap: kunit: add test for regmap_bus::init_limits Peng Fan (OSS)
2026-11-10 11:47 ` [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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 | 66 +++++++++++++++++++++-----------------------
1 file changed, 32 insertions(+), 34 deletions(-)
diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c
index c5acfe687d9c..fdd538afaae4 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,29 +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 = -EINVAL;
if (!config)
- goto err;
+ 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
@@ -1118,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);
@@ -1147,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;
@@ -1171,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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 9/9] regmap: kunit: add test for regmap_bus::init_limits
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (7 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 8/9] regmap: use cleanup API in __regmap_init() Peng Fan (OSS)
@ 2026-11-10 8:25 ` Peng Fan (OSS)
2026-11-10 11:47 ` [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan (OSS) @ 2026-11-10 8:25 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.51.0
^ permalink raw reply [flat|nested] 11+ messages in thread* Re: [PATCH 0/9] regmap: clean up and improve __regmap_init()
2026-11-10 8:25 [PATCH 0/9] regmap: clean up and improve __regmap_init() Peng Fan (OSS)
` (8 preceding siblings ...)
2026-11-10 8:25 ` [PATCH 9/9] regmap: kunit: add test for regmap_bus::init_limits Peng Fan (OSS)
@ 2026-11-10 11:47 ` Peng Fan
9 siblings, 0 replies; 11+ messages in thread
From: Peng Fan @ 2026-11-10 11:47 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 reported a few findings:
https://sashiko.dev/#/patchset/20261110-regmap-improve-v1-0-3a76b6c10ec6%40nxp.com
I will address in V2, so please wait V2.
Thanks,
Peng
^ permalink raw reply [flat|nested] 11+ messages in thread