From 1fcf171178f021ef2005d47e281544624b93c5a5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 10 Feb 2026 17:09:08 +0100 Subject: [PATCH 01/14] regcache: Remove duplicate check in regcache_hw_init() The regcache_hw_init() is never called without preliminary check for num_reg_defaults_raw not being 0. Thus, remove duplicate in the function. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260210161058.53093-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index a35f2b20298b..d41cdb39c78f 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -50,9 +50,6 @@ static int regcache_hw_init(struct regmap *map) unsigned int reg, val; void *tmp_buf; - if (!map->num_reg_defaults_raw) - return -EINVAL; - /* calculate the size of reg_defaults */ for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) if (regmap_readable(map, i * map->reg_stride) && From c2bcf62ca75c541ec4297e6ff02a68ddc2e02029 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 10 Feb 2026 17:09:09 +0100 Subject: [PATCH 02/14] regcache: Split regcache_count_cacheable_registers() helper The introduced helper allows to check for the non-cacheable configurations earlier during initialisation. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260210161058.53093-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index d41cdb39c78f..73cfe8120669 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -42,13 +42,10 @@ void regcache_sort_defaults(struct reg_default *defaults, unsigned int ndefaults } EXPORT_SYMBOL_GPL(regcache_sort_defaults); -static int regcache_hw_init(struct regmap *map) +static int regcache_count_cacheable_registers(struct regmap *map) { - int i, j; - int ret; int count; - unsigned int reg, val; - void *tmp_buf; + int i; /* calculate the size of reg_defaults */ for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) @@ -57,10 +54,18 @@ static int regcache_hw_init(struct regmap *map) count++; /* all registers are unreadable or volatile, so just bypass */ - if (!count) { + if (!count) map->cache_bypass = true; - return 0; - } + + return count; +} + +static int regcache_hw_init(struct regmap *map, int count) +{ + int i, j; + int ret; + unsigned int reg, val; + void *tmp_buf; map->num_reg_defaults = count; map->reg_defaults = kmalloc_objs(struct reg_default, count); @@ -129,6 +134,7 @@ static int regcache_hw_init(struct regmap *map) int regcache_init(struct regmap *map, const struct regmap_config *config) { + int count = 0; int ret; int i; void *tmp_buf; @@ -193,15 +199,17 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) return -ENOMEM; map->reg_defaults = tmp_buf; } else if (map->num_reg_defaults_raw) { + count = regcache_count_cacheable_registers(map); + if (map->cache_bypass) + return 0; + /* Some devices such as PMICs don't have cache defaults, * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. */ - ret = regcache_hw_init(map); + ret = regcache_hw_init(map, count); if (ret < 0) return ret; - if (map->cache_bypass) - return 0; } if (!map->max_register_is_set && map->num_reg_defaults_raw) { From 38ab6557234d8629407a824be90e82514d6129a0 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 20 Feb 2026 17:01:11 +0100 Subject: [PATCH 03/14] regmap: sort header includes Sort the included headers to make spotting duplicates easier and avoid discussions on where to add new includes. Signed-off-by: Sander Vanheule Link: https://patch.msgid.link/20260220160112.543391-1-sander@svanheule.net Signed-off-by: Mark Brown --- include/linux/regmap.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index caff2240bdab..c8a6a05bdba1 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -10,15 +10,15 @@ * Author: Mark Brown */ -#include -#include -#include +#include #include #include -#include -#include -#include #include +#include +#include +#include +#include +#include struct module; struct clk; From 37983fad7f3ef296fa0504c8e945987459dc5487 Mon Sep 17 00:00:00 2001 From: Sander Vanheule Date: Fri, 20 Feb 2026 17:01:12 +0100 Subject: [PATCH 04/14] regmap: define cleanup helper for regmap_field For temporary field allocation, the user has to perform manual cleanup, or rely on devm_regmap_field_alloc() to (eventually) clean up the allocated resources when an error occurs. Add a cleanup helper that takes care of freeing the allocated regmap_field whenever it goes out of scope. This can simplify this example: struct regmap_field *field = regmap_field_alloc(...); if (IS_ERR(field)) return PTR_ERR(field); int err = regmap_field_read(...); if (err) goto out; /* some logic that may also error */ err = regmap_field_write(...); out: regmap_field_free(field); return err; into the shorter: struct regmap_field *field __free(regmap_field) = regmap_field_alloc(...); if (IS_ERR(field)) return PTR_ERR(field); int err = regmap_field_read(...); if (err) return err; /* some logic that may also error */ return regmap_field_write(...); Signed-off-by: Sander Vanheule Link: https://patch.msgid.link/20260220160112.543391-2-sander@svanheule.net Signed-off-by: Mark Brown --- include/linux/regmap.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index c8a6a05bdba1..f1c5cb63c171 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -1460,6 +1461,8 @@ struct regmap_field *regmap_field_alloc(struct regmap *regmap, struct reg_field reg_field); void regmap_field_free(struct regmap_field *field); +DEFINE_FREE(regmap_field, struct regmap_field *, if (_T) regmap_field_free(_T)) + struct regmap_field *devm_regmap_field_alloc(struct device *dev, struct regmap *regmap, struct reg_field reg_field); void devm_regmap_field_free(struct device *dev, struct regmap_field *field); From 8e29bc88e11928926fd97fc9acd58b8afa38de9d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Mar 2026 10:56:56 +0100 Subject: [PATCH 05/14] regcache: Define iterator inside for-loop and align their types Some of the iterators may be defined inside the respective for-loop reducing the scope and potential risk of their misuse. While at it, align their types based on the type of the upper or lower limits. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260302095847.2310066-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 2 +- drivers/base/regmap/regcache.c | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 5bf993165438..c77f3a49a89e 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -162,7 +162,7 @@ struct regmap { bool no_sync_defaults; struct reg_sequence *patch; - int patch_regs; + unsigned int patch_regs; /* if set, the regmap core can sleep */ bool can_sleep; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 73cfe8120669..e155408b454c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -44,11 +44,11 @@ EXPORT_SYMBOL_GPL(regcache_sort_defaults); static int regcache_count_cacheable_registers(struct regmap *map) { - int count; - int i; + unsigned int count; /* calculate the size of reg_defaults */ - for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) + count = 0; + for (unsigned int i = 0; i < map->num_reg_defaults_raw; i++) if (regmap_readable(map, i * map->reg_stride) && !regmap_volatile(map, i * map->reg_stride)) count++; @@ -62,7 +62,6 @@ static int regcache_count_cacheable_registers(struct regmap *map) static int regcache_hw_init(struct regmap *map, int count) { - int i, j; int ret; unsigned int reg, val; void *tmp_buf; @@ -95,7 +94,7 @@ static int regcache_hw_init(struct regmap *map, int count) } /* fill the reg_defaults */ - for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { + for (unsigned int i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { reg = i * map->reg_stride; if (!regmap_readable(map, reg)) @@ -840,13 +839,13 @@ static int regcache_sync_block_raw(struct regmap *map, void *block, unsigned int block_base, unsigned int start, unsigned int end) { - unsigned int i, val; unsigned int regtmp = 0; unsigned int base = 0; const void *data = NULL; + unsigned int val; int ret; - for (i = start; i < end; i++) { + for (unsigned int i = start; i < end; i++) { regtmp = block_base + (i * map->reg_stride); if (!regcache_reg_present(cache_present, i) || From 9ab637ac5d3826606947f4e861107da958eda324 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Mar 2026 10:56:57 +0100 Subject: [PATCH 06/14] regcache: Amend printf() specifiers when printing registers In one case the 0x is provided in the formatting string, while the rest use # for that. In a couple of more cases a decimal signed value specifier is used. Amend them to use %#x when register is printed. Note, for the case, when it's related to the read/write, use %x to be in align with the similar messages in regmap core. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260302095847.2310066-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index e155408b454c..13c480b6e21d 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -112,7 +112,7 @@ static int regcache_hw_init(struct regmap *map, int count) ret = regmap_read(map, reg, &val); map->cache_bypass = cache_bypass; if (ret != 0) { - dev_err(map->dev, "Failed to read %d: %d\n", + dev_err(map->dev, "Failed to read %x: %d\n", reg, ret); goto err_free; } @@ -508,7 +508,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, bypass = map->cache_bypass; name = map->cache_ops->name; - dev_dbg(map->dev, "Syncing %s cache from %d-%d\n", name, min, max); + dev_dbg(map->dev, "Syncing %s cache from %#x-%#x\n", name, min, max); trace_regcache_sync(map, name, "start region"); From 9891b52ba12e9d5fed5901b6b5f6e0cdcd424390 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 3 Mar 2026 10:26:24 +0100 Subject: [PATCH 07/14] regcache: Factor out regcache_hw_exit() helper Factor out regcache_hw_exit() helper to make error and exit paths clearer. This helps to avoid missing changes in case the code gets shuffled in the future. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260303092820.2818138-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 13c480b6e21d..329cdee1ae1c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -131,6 +131,13 @@ static int regcache_hw_init(struct regmap *map, int count) return ret; } +static void regcache_hw_exit(struct regmap *map) +{ + kfree(map->reg_defaults); + if (map->cache_free) + kfree(map->reg_defaults_raw); +} + int regcache_init(struct regmap *map, const struct regmap_config *config) { int count = 0; @@ -245,9 +252,7 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->unlock(map->lock_arg); } err_free: - kfree(map->reg_defaults); - if (map->cache_free) - kfree(map->reg_defaults_raw); + regcache_hw_exit(map); return ret; } @@ -259,9 +264,7 @@ void regcache_exit(struct regmap *map) BUG_ON(!map->cache_ops); - kfree(map->reg_defaults); - if (map->cache_free) - kfree(map->reg_defaults_raw); + regcache_hw_exit(map); if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", From 8e2d279724944f788edc633e4888107eae666a37 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Mar 2026 09:53:00 +0100 Subject: [PATCH 08/14] regcache: Move count check and cache_bypass assignment to the caller Make regcache_count_cacheable_registers() just a counting routine without any side effects by moving count check and cache_bypass assignment to the caller. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260305085449.3184020-2-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 329cdee1ae1c..b73de70bbf3f 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -53,10 +53,6 @@ static int regcache_count_cacheable_registers(struct regmap *map) !regmap_volatile(map, i * map->reg_stride)) count++; - /* all registers are unreadable or volatile, so just bypass */ - if (!count) - map->cache_bypass = true; - return count; } @@ -206,6 +202,10 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->reg_defaults = tmp_buf; } else if (map->num_reg_defaults_raw) { count = regcache_count_cacheable_registers(map); + if (!count) + map->cache_bypass = true; + + /* All registers are unreadable or volatile, so just bypass */ if (map->cache_bypass) return 0; From 0cb7ae981894ff1fd0283813a17253250a747cf5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Mar 2026 09:53:01 +0100 Subject: [PATCH 09/14] regcache: Allocate and free reg_defaults on the same level Currently reg_defaults buffer may be allocated on two different levels when the user provided them and we duplicate it in regcache_init() or when user wants us to read back from HW in regcache_hw_init(). This inconsistency makes code harder to follow and maintain. Allocate and free reg_defaults on the same level in regcache_init() to improve the readability and maintenance efforts. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260305085449.3184020-3-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index b73de70bbf3f..e9d95aa63938 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -56,17 +56,12 @@ static int regcache_count_cacheable_registers(struct regmap *map) return count; } -static int regcache_hw_init(struct regmap *map, int count) +static int regcache_hw_init(struct regmap *map) { int ret; unsigned int reg, val; void *tmp_buf; - map->num_reg_defaults = count; - map->reg_defaults = kmalloc_objs(struct reg_default, count); - if (!map->reg_defaults) - return -ENOMEM; - if (!map->reg_defaults_raw) { bool cache_bypass = map->cache_bypass; dev_dbg(map->dev, "No cache defaults, reading back from HW\n"); @@ -74,10 +69,8 @@ static int regcache_hw_init(struct regmap *map, int count) /* Bypass the cache access till data read from HW */ map->cache_bypass = true; tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL); - if (!tmp_buf) { - ret = -ENOMEM; - goto err_free; - } + if (!tmp_buf) + return -ENOMEM; ret = regmap_raw_read(map, 0, tmp_buf, map->cache_size_raw); map->cache_bypass = cache_bypass; @@ -110,7 +103,7 @@ static int regcache_hw_init(struct regmap *map, int count) if (ret != 0) { dev_err(map->dev, "Failed to read %x: %d\n", reg, ret); - goto err_free; + return ret; } } @@ -120,16 +113,10 @@ static int regcache_hw_init(struct regmap *map, int count) } return 0; - -err_free: - kfree(map->reg_defaults); - - return ret; } static void regcache_hw_exit(struct regmap *map) { - kfree(map->reg_defaults); if (map->cache_free) kfree(map->reg_defaults_raw); } @@ -209,13 +196,18 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) if (map->cache_bypass) return 0; + map->num_reg_defaults = count; + map->reg_defaults = kmalloc_objs(struct reg_default, count); + if (!map->reg_defaults) + return -ENOMEM; + /* Some devices such as PMICs don't have cache defaults, * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. */ - ret = regcache_hw_init(map, count); + ret = regcache_hw_init(map); if (ret < 0) - return ret; + goto err_free_reg_defaults; } if (!map->max_register_is_set && map->num_reg_defaults_raw) { @@ -253,6 +245,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) } err_free: regcache_hw_exit(map); +err_free_reg_defaults: + kfree(map->reg_defaults); return ret; } @@ -273,6 +267,8 @@ void regcache_exit(struct regmap *map) map->cache_ops->exit(map); map->unlock(map->lock_arg); } + + kfree(map->reg_defaults); } /** From e7662bced2e98ffa2c572126677deb9cf55d43b3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 5 Mar 2026 09:53:02 +0100 Subject: [PATCH 10/14] regcache: Move HW readback after cache initialisation Make sure that cache is initialised before calling any IO using regmap, this makes sure that we won't access NULL or invalid pointers in the cache which hasn't been initialised. As a side effect it also makes the ordering of cleaning up the resources in regcache_exit() to be the same (and correct) as in the error path of regcache_init(). This is not a problem right now as they do not have dependencies, but it makes code robust against potential changes in the future. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260305085449.3184020-4-andriy.shevchenko@linux.intel.com Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index e9d95aa63938..27616b05111c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -200,14 +200,6 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) map->reg_defaults = kmalloc_objs(struct reg_default, count); if (!map->reg_defaults) return -ENOMEM; - - /* Some devices such as PMICs don't have cache defaults, - * we cope with this by reading back the HW registers and - * crafting the cache defaults by hand. - */ - ret = regcache_hw_init(map); - if (ret < 0) - goto err_free_reg_defaults; } if (!map->max_register_is_set && map->num_reg_defaults_raw) { @@ -222,7 +214,18 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->init(map); map->unlock(map->lock_arg); if (ret) - goto err_free; + goto err_free_reg_defaults; + } + + /* + * Some devices such as PMICs don't have cache defaults, + * we cope with this by reading back the HW registers and + * crafting the cache defaults by hand. + */ + if (count) { + ret = regcache_hw_init(map); + if (ret) + goto err_exit; } if (map->cache_ops->populate && @@ -232,10 +235,12 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->populate(map); map->unlock(map->lock_arg); if (ret) - goto err_exit; + goto err_free; } return 0; +err_free: + regcache_hw_exit(map); err_exit: if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); @@ -243,8 +248,6 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->exit(map); map->unlock(map->lock_arg); } -err_free: - regcache_hw_exit(map); err_free_reg_defaults: kfree(map->reg_defaults); From b1ef855c62601ed4de2c4b0ff75a075877e3dac8 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 5 Mar 2026 21:13:50 +0100 Subject: [PATCH 11/14] regmap: Simplify devres handling The resource-managed devm_regmap_init() can be a bit simpler by using devm_add_action_or_reset() instead of devres_alloc(). This allows to drop the less-obvious pointer to pointer (struct regmap **ptr) and make devm_regmap_release() interface simpler. Code is functionally equivalent with minor difference: devres_alloc() will happen now after successful resource init (__regmap_init()). Signed-off-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260305201349.32734-2-krzysztof.kozlowski@oss.qualcomm.com Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 607c1246d994..f888a83aa9c3 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1182,9 +1182,9 @@ struct regmap *__regmap_init(struct device *dev, } EXPORT_SYMBOL_GPL(__regmap_init); -static void devm_regmap_release(struct device *dev, void *res) +static void devm_regmap_release(void *regmap) { - regmap_exit(*(struct regmap **)res); + regmap_exit(regmap); } struct regmap *__devm_regmap_init(struct device *dev, @@ -1194,20 +1194,17 @@ struct regmap *__devm_regmap_init(struct device *dev, struct lock_class_key *lock_key, const char *lock_name) { - struct regmap **ptr, *regmap; - - ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); + struct regmap *regmap; + int ret; regmap = __regmap_init(dev, bus, bus_context, config, lock_key, lock_name); - if (!IS_ERR(regmap)) { - *ptr = regmap; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } + if (IS_ERR(regmap)) + return regmap; + + ret = devm_add_action_or_reset(dev, devm_regmap_release, regmap); + if (ret) + return ERR_PTR(ret); return regmap; } From 1ef3e1c278eb7bda1cc09a508c3fe65d2e567c77 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 2 Mar 2026 19:43:31 +0100 Subject: [PATCH 12/14] regmap: Synchronize cache for the page selector If the selector register is represented in each page, its value according to the debugfs is stale because it gets synchronized only after the real page switch happens. Hence the regmap cache initialisation from the HW inherits outdated data in the selector register. Synchronize cache for the page selector just in time. Before (offset followed by hexdump, the first byte is selector): // Real registers 18: 05 ff 00 00 ff 0f 00 00 f0 00 00 00 ... // Virtual (per port) 40: 05 ff 00 00 e0 e0 00 00 00 00 00 1f 50: 00 ff 00 00 e0 e0 00 00 00 00 00 1f 60: 01 ff 00 00 ff ff 00 00 00 00 00 00 70: 02 ff 00 00 cf f3 00 00 00 00 00 0c 80: 03 ff 00 00 00 00 00 00 00 00 00 ff 90: 04 ff 00 00 ff 0f 00 00 f0 00 00 00 After: // Real registers 18: 05 ff 00 00 ff 0f 00 00 f0 00 00 00 ... // Virtual (per port) 40: 00 ff 00 00 e0 e0 00 00 00 00 00 1f 50: 01 ff 00 00 e0 e0 00 00 00 00 00 1f 60: 02 ff 00 00 ff ff 00 00 00 00 00 00 70: 03 ff 00 00 cf f3 00 00 00 00 00 0c 80: 04 ff 00 00 00 00 00 00 00 00 00 ff 90: 05 ff 00 00 ff 0f 00 00 f0 00 00 00 Fixes: 6863ca622759 ("regmap: Add support for register indirect addressing.") Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260302184753.2693803-1-andriy.shevchenko@linux.intel.com Tested-by: Marek Szyprowski Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index f888a83aa9c3..b2b26f07f4e3 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1542,6 +1542,7 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg, unsigned int val_num) { void *orig_work_buf; + unsigned int selector_reg; unsigned int win_offset; unsigned int win_page; bool page_chg; @@ -1560,10 +1561,31 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg, return -EINVAL; } - /* It is possible to have selector register inside data window. - In that case, selector register is located on every page and - it needs no page switching, when accessed alone. */ + /* + * Calculate the address of the selector register in the corresponding + * data window if it is located on every page. + */ + page_chg = in_range(range->selector_reg, range->window_start, range->window_len); + if (page_chg) + selector_reg = range->range_min + win_page * range->window_len + + range->selector_reg - range->window_start; + + /* + * It is possible to have selector register inside data window. + * In that case, selector register is located on every page and it + * needs no page switching, when accessed alone. + * + * Nevertheless we should synchronize the cache values for it. + * This can't be properly achieved if the selector register is + * the first and the only one to be read inside the data window. + * That's why we update it in that case as well. + * + * However, we specifically avoid updating it for the default page, + * when it's overlapped with the real data window, to prevent from + * infinite looping. + */ if (val_num > 1 || + (page_chg && selector_reg != range->selector_reg) || range->window_start + win_offset != range->selector_reg) { /* Use separate work_buf during page switching */ orig_work_buf = map->work_buf; @@ -1572,7 +1594,7 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg, ret = _regmap_update_bits(map, range->selector_reg, range->selector_mask, win_page << range->selector_shift, - &page_chg, false); + NULL, false); map->work_buf = orig_work_buf; From 7d696210cf36ed31c7c37f6eff17cb7147e83367 Mon Sep 17 00:00:00 2001 From: Zxyan Zhu Date: Thu, 9 Apr 2026 11:50:15 +0800 Subject: [PATCH 13/14] regmap: debugfs: fix race condition in dummy name allocation Use IDA instead of a simple counter for generating unique dummy names. The previous implementation used dummy_index++ which is not atomic, leading to potential duplicate names when multiple threads call regmap_debugfs_init() concurrently with name="dummy". Signed-off-by: Zxyan Zhu Link: https://patch.msgid.link/20260409035015.950764-1-zxyan0222@gmail.com Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regmap-debugfs.c | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index c77f3a49a89e..55273a6178f8 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -84,6 +84,7 @@ struct regmap { bool debugfs_disable; struct dentry *debugfs; const char *debugfs_name; + int debugfs_dummy_id; unsigned int debugfs_reg_len; unsigned int debugfs_val_len; diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 5a46ce5fee72..18f1c60749fe 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -12,6 +12,7 @@ #include #include #include +#include #include "internal.h" @@ -20,7 +21,7 @@ struct regmap_debugfs_node { struct list_head link; }; -static unsigned int dummy_index; +static DEFINE_IDA(dummy_ida); static struct dentry *regmap_debugfs_root; static LIST_HEAD(regmap_debugfs_early_list); static DEFINE_MUTEX(regmap_debugfs_early_lock); @@ -539,6 +540,7 @@ void regmap_debugfs_init(struct regmap *map) struct regmap_range_node *range_node; const char *devname = "dummy"; const char *name = map->name; + int id; /* * Userspace can initiate reads from the hardware over debugfs. @@ -567,6 +569,7 @@ void regmap_debugfs_init(struct regmap *map) INIT_LIST_HEAD(&map->debugfs_off_cache); mutex_init(&map->cache_lock); + map->debugfs_dummy_id = -1; if (map->dev) devname = dev_name(map->dev); @@ -585,12 +588,16 @@ void regmap_debugfs_init(struct regmap *map) if (!strcmp(name, "dummy")) { kfree(map->debugfs_name); - map->debugfs_name = kasprintf(GFP_KERNEL, "dummy%d", - dummy_index); - if (!map->debugfs_name) + id = ida_alloc(&dummy_ida, GFP_KERNEL); + if (id < 0) return; + map->debugfs_name = kasprintf(GFP_KERNEL, "dummy%d", id); + if (!map->debugfs_name) { + ida_free(&dummy_ida, id); + return; + } + map->debugfs_dummy_id = id; name = map->debugfs_name; - dummy_index++; } map->debugfs = debugfs_create_dir(name, regmap_debugfs_root); @@ -660,6 +667,10 @@ void regmap_debugfs_exit(struct regmap *map) mutex_lock(&map->cache_lock); regmap_debugfs_free_dump_cache(map); mutex_unlock(&map->cache_lock); + if (map->debugfs_dummy_id >= 0) { + ida_free(&dummy_ida, map->debugfs_dummy_id); + map->debugfs_dummy_id = -1; + } kfree(map->debugfs_name); map->debugfs_name = NULL; } else { From 8ad7f3b265a87cd4e5052677545f90f14c855b10 Mon Sep 17 00:00:00 2001 From: Pei Xiao Date: Fri, 10 Apr 2026 10:29:24 +0800 Subject: [PATCH 14/14] regmap: i3c: Add non-devm regmap_init_i3c() helper Add __regmap_init_i3c() and the corresponding regmap_init_i3c() macro to allow creating a regmap for I3C devices without using the device-managed version. This mirrors the pattern already established for other buses such as I2C, SPI and so on, giving drivers more flexibility when the regmap lifetime is not directly tied to the device. Signed-off-by: Pei Xiao Link: https://patch.msgid.link/a81256a8866b163979a20406abf01df7d7440104.1775788105.git.xiaopei01@kylinos.cn Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-i3c.c | 10 ++++++++++ include/linux/regmap.h | 17 +++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/drivers/base/regmap/regmap-i3c.c b/drivers/base/regmap/regmap-i3c.c index 863b348704dc..5af583d472dd 100644 --- a/drivers/base/regmap/regmap-i3c.c +++ b/drivers/base/regmap/regmap-i3c.c @@ -46,6 +46,16 @@ static const struct regmap_bus regmap_i3c = { .read = regmap_i3c_read, }; +struct regmap *__regmap_init_i3c(struct i3c_device *i3c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + return __regmap_init(&i3c->dev, ®map_i3c, &i3c->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_i3c); + struct regmap *__devm_regmap_init_i3c(struct i3c_device *i3c, const struct regmap_config *config, struct lock_class_key *lock_key, diff --git a/include/linux/regmap.h b/include/linux/regmap.h index f1c5cb63c171..df44cb30f53b 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -693,6 +693,10 @@ struct regmap *__regmap_init_sdw_mbq(struct device *dev, struct sdw_slave *sdw, const struct regmap_sdw_mbq_cfg *mbq_config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_i3c(struct i3c_device *i3c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_spi_avmm(struct spi_device *spi, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -999,6 +1003,19 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__regmap_init_sdw_mbq, #config, \ dev, sdw, config, mbq_config) +/** + * regmap_init_i3c() - Initialise register map + * + * @i3c: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_i3c(i3c, config) \ + __regmap_lockdep_wrapper(__regmap_init_i3c, #config, \ + i3c, config) + /** * regmap_init_spi_avmm() - Initialize register map for Intel SPI Slave * to AVMM Bus Bridge