regmap: Updates for v6.19

Another small update for regmap, we have one new feature plus a little
 bit of cleanup:
 
  - Support for sparseness information in the flat cache, allowing users
    that really need the performance properties it provides to benefit
    from the interface and startup time improvements that sparsness provides
    without needing to go all the way to a more fancy data structure.
  - Cleanup work from Andy Shevchenko, refactoring the cache interface
    in preparation for some future stuff he's working on.
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCgAdFiEEreZoqmdXGLWf4p/qJNaLcl1Uh9AFAmkt6/0ACgkQJNaLcl1U
 h9A33gf/QC/O/zxVtpq31+CtC1k3ola0rWrLxOnQKywoUcBjJqX+FiFqL+MDS6Mw
 RfXzX7SmzJup71/sg7GNQjRSL3jH3UmsAy4acbNhoCXwi0VaFibHfTqVKNQGK5um
 uzsDP8Ue4zUyqym3jMKxs36iVYtt9/QcUjrubumisnB51csQKQ0tn7+2khUbN7D5
 PEjUfR7WiGAGDf2AmCLueCYaV16uuzoxnlV9U4Mp5LYvzi5KKxZZAid9ppUCUcAt
 uab8Y5Qo8uxBKJEue1vp4shfbxj69GoOdzOP8bYWNP2z6xcHXgK2ma/5y3eqK7L0
 uA1SHqKW3QXY1VckI83QWrifR6r8mQ==
 =TTeH
 -----END PGP SIGNATURE-----

Merge tag 'regmap-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap

Pull regmap updates from Mark Brown:
 "Another small update for regmap, we have one new feature plus a little
  bit of cleanup:

   - Support for sparseness information in the flat cache, allowing
     users that really need the performance properties it provides to
     benefit from the interface and startup time improvements that
     sparsness provides without needing to go all the way to a more
     fancy data structure

   - Cleanup work from Andy Shevchenko, refactoring the cache interface
     in preparation for some future stuff he's working on"

* tag 'regmap-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap:
  regmap: sdw-mbq: Reorder regmap_mbq_context struct for better packing
  regmap: i3c: Use ARRAY_SIZE()
  regcache: maple: Split ->populate() from ->init()
  regcache: flat: Split ->populate() from ->init()
  regcache: flat: Remove unneeded check and error message for -ENOMEM
  regcache: rbtree: Split ->populate() from ->init()
  regcache: Add ->populate() callback to separate from ->init()
  regmap: warn users about uninitialized flat cache
  regmap: add flat cache with sparse validity
This commit is contained in:
Linus Torvalds 2025-12-04 11:16:44 -08:00
commit ba1401f9cc
9 changed files with 189 additions and 68 deletions

View File

@ -186,6 +186,7 @@ struct regcache_ops {
enum regcache_type type;
int (*init)(struct regmap *map);
int (*exit)(struct regmap *map);
int (*populate)(struct regmap *map);
#ifdef CONFIG_DEBUG_FS
void (*debugfs_init)(struct regmap *map);
#endif
@ -288,6 +289,7 @@ enum regmap_endian regmap_get_val_endian(struct device *dev,
const struct regmap_bus *bus,
const struct regmap_config *config);
extern struct regcache_ops regcache_flat_sparse_ops;
extern struct regcache_ops regcache_rbtree_ops;
extern struct regcache_ops regcache_maple_ops;
extern struct regcache_ops regcache_flat_ops;

View File

@ -6,7 +6,11 @@
//
// Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/limits.h>
#include <linux/overflow.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
@ -18,46 +22,92 @@ static inline unsigned int regcache_flat_get_index(const struct regmap *map,
return regcache_get_index_by_order(map, reg);
}
struct regcache_flat_data {
unsigned long *valid;
unsigned int data[];
};
static int regcache_flat_init(struct regmap *map)
{
int i;
unsigned int *cache;
unsigned int cache_size;
struct regcache_flat_data *cache;
if (!map || map->reg_stride_order < 0 || !map->max_register_is_set)
return -EINVAL;
map->cache = kcalloc(regcache_flat_get_index(map, map->max_register)
+ 1, sizeof(unsigned int), map->alloc_flags);
if (!map->cache)
cache_size = regcache_flat_get_index(map, map->max_register) + 1;
cache = kzalloc(struct_size(cache, data, cache_size), map->alloc_flags);
if (!cache)
return -ENOMEM;
cache = map->cache;
cache->valid = bitmap_zalloc(cache_size, map->alloc_flags);
if (!cache->valid)
goto err_free;
map->cache = cache;
return 0;
err_free:
kfree(cache);
return -ENOMEM;
}
static int regcache_flat_exit(struct regmap *map)
{
struct regcache_flat_data *cache = map->cache;
if (cache)
bitmap_free(cache->valid);
kfree(cache);
map->cache = NULL;
return 0;
}
static int regcache_flat_populate(struct regmap *map)
{
struct regcache_flat_data *cache = map->cache;
unsigned int i;
for (i = 0; i < map->num_reg_defaults; i++) {
unsigned int reg = map->reg_defaults[i].reg;
unsigned int index = regcache_flat_get_index(map, reg);
cache[index] = map->reg_defaults[i].def;
cache->data[index] = map->reg_defaults[i].def;
__set_bit(index, cache->valid);
}
return 0;
}
static int regcache_flat_exit(struct regmap *map)
{
kfree(map->cache);
map->cache = NULL;
return 0;
}
static int regcache_flat_read(struct regmap *map,
unsigned int reg, unsigned int *value)
{
unsigned int *cache = map->cache;
struct regcache_flat_data *cache = map->cache;
unsigned int index = regcache_flat_get_index(map, reg);
*value = cache[index];
/* legacy behavior: ignore validity, but warn the user */
if (unlikely(!test_bit(index, cache->valid)))
dev_warn_once(map->dev,
"using zero-initialized flat cache, this may cause unexpected behavior");
*value = cache->data[index];
return 0;
}
static int regcache_flat_sparse_read(struct regmap *map,
unsigned int reg, unsigned int *value)
{
struct regcache_flat_data *cache = map->cache;
unsigned int index = regcache_flat_get_index(map, reg);
if (unlikely(!test_bit(index, cache->valid)))
return -ENOENT;
*value = cache->data[index];
return 0;
}
@ -65,10 +115,23 @@ static int regcache_flat_read(struct regmap *map,
static int regcache_flat_write(struct regmap *map, unsigned int reg,
unsigned int value)
{
unsigned int *cache = map->cache;
struct regcache_flat_data *cache = map->cache;
unsigned int index = regcache_flat_get_index(map, reg);
cache[index] = value;
cache->data[index] = value;
__set_bit(index, cache->valid);
return 0;
}
static int regcache_flat_drop(struct regmap *map, unsigned int min,
unsigned int max)
{
struct regcache_flat_data *cache = map->cache;
unsigned int bitmap_min = regcache_flat_get_index(map, min);
unsigned int bitmap_max = regcache_flat_get_index(map, max);
bitmap_clear(cache->valid, bitmap_min, bitmap_max + 1 - bitmap_min);
return 0;
}
@ -78,6 +141,18 @@ struct regcache_ops regcache_flat_ops = {
.name = "flat",
.init = regcache_flat_init,
.exit = regcache_flat_exit,
.populate = regcache_flat_populate,
.read = regcache_flat_read,
.write = regcache_flat_write,
};
struct regcache_ops regcache_flat_sparse_ops = {
.type = REGCACHE_FLAT_S,
.name = "flat-sparse",
.init = regcache_flat_init,
.exit = regcache_flat_exit,
.populate = regcache_flat_populate,
.read = regcache_flat_sparse_read,
.write = regcache_flat_write,
.drop = regcache_flat_drop,
};

View File

@ -289,6 +289,23 @@ static int regcache_maple_sync(struct regmap *map, unsigned int min,
return ret;
}
static int regcache_maple_init(struct regmap *map)
{
struct maple_tree *mt;
mt = kmalloc(sizeof(*mt), map->alloc_flags);
if (!mt)
return -ENOMEM;
map->cache = mt;
mt_init(mt);
if (!mt_external_lock(mt) && map->lock_key)
lockdep_set_class_and_subclass(&mt->ma_lock, map->lock_key, 1);
return 0;
}
static int regcache_maple_exit(struct regmap *map)
{
struct maple_tree *mt = map->cache;
@ -340,26 +357,12 @@ static int regcache_maple_insert_block(struct regmap *map, int first,
return ret;
}
static int regcache_maple_init(struct regmap *map)
static int regcache_maple_populate(struct regmap *map)
{
struct maple_tree *mt;
int i;
int ret;
int range_start;
mt = kmalloc(sizeof(*mt), map->alloc_flags);
if (!mt)
return -ENOMEM;
map->cache = mt;
mt_init(mt);
if (!mt_external_lock(mt) && map->lock_key)
lockdep_set_class_and_subclass(&mt->ma_lock, map->lock_key, 1);
if (!map->num_reg_defaults)
return 0;
range_start = 0;
/* Scan for ranges of contiguous registers */
@ -369,23 +372,14 @@ static int regcache_maple_init(struct regmap *map)
ret = regcache_maple_insert_block(map, range_start,
i - 1);
if (ret != 0)
goto err;
return ret;
range_start = i;
}
}
/* Add the last block */
ret = regcache_maple_insert_block(map, range_start,
map->num_reg_defaults - 1);
if (ret != 0)
goto err;
return 0;
err:
regcache_maple_exit(map);
return ret;
return regcache_maple_insert_block(map, range_start, map->num_reg_defaults - 1);
}
struct regcache_ops regcache_maple_ops = {
@ -393,6 +387,7 @@ struct regcache_ops regcache_maple_ops = {
.name = "maple",
.init = regcache_maple_init,
.exit = regcache_maple_exit,
.populate = regcache_maple_populate,
.read = regcache_maple_read,
.write = regcache_maple_write,
.drop = regcache_maple_drop,

View File

@ -184,8 +184,6 @@ static void rbtree_debugfs_init(struct regmap *map)
static int regcache_rbtree_init(struct regmap *map)
{
struct regcache_rbtree_ctx *rbtree_ctx;
int i;
int ret;
map->cache = kmalloc(sizeof *rbtree_ctx, map->alloc_flags);
if (!map->cache)
@ -195,19 +193,7 @@ static int regcache_rbtree_init(struct regmap *map)
rbtree_ctx->root = RB_ROOT;
rbtree_ctx->cached_rbnode = NULL;
for (i = 0; i < map->num_reg_defaults; i++) {
ret = regcache_rbtree_write(map,
map->reg_defaults[i].reg,
map->reg_defaults[i].def);
if (ret)
goto err;
}
return 0;
err:
regcache_rbtree_exit(map);
return ret;
}
static int regcache_rbtree_exit(struct regmap *map)
@ -239,6 +225,22 @@ static int regcache_rbtree_exit(struct regmap *map)
return 0;
}
static int regcache_rbtree_populate(struct regmap *map)
{
unsigned int i;
int ret;
for (i = 0; i < map->num_reg_defaults; i++) {
ret = regcache_rbtree_write(map,
map->reg_defaults[i].reg,
map->reg_defaults[i].def);
if (ret)
return ret;
}
return 0;
}
static int regcache_rbtree_read(struct regmap *map,
unsigned int reg, unsigned int *value)
{
@ -546,6 +548,7 @@ struct regcache_ops regcache_rbtree_ops = {
.name = "rbtree",
.init = regcache_rbtree_init,
.exit = regcache_rbtree_exit,
.populate = regcache_rbtree_populate,
#ifdef CONFIG_DEBUG_FS
.debugfs_init = rbtree_debugfs_init,
#endif

View File

@ -16,6 +16,7 @@
#include "internal.h"
static const struct regcache_ops *cache_types[] = {
&regcache_flat_sparse_ops,
&regcache_rbtree_ops,
&regcache_maple_ops,
&regcache_flat_ops,
@ -221,8 +222,24 @@ int regcache_init(struct regmap *map, const struct regmap_config *config)
if (ret)
goto err_free;
}
if (map->num_reg_defaults && map->cache_ops->populate) {
dev_dbg(map->dev, "Populating %s cache\n", map->cache_ops->name);
map->lock(map->lock_arg);
ret = map->cache_ops->populate(map);
map->unlock(map->lock_arg);
if (ret)
goto err_exit;
}
return 0;
err_exit:
if (map->cache_ops->exit) {
dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name);
map->lock(map->lock_arg);
ret = map->cache_ops->exit(map);
map->unlock(map->lock_arg);
}
err_free:
kfree(map->reg_defaults);
if (map->cache_free)

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Synopsys, Inc. and/or its affiliates.
#include <linux/array_size.h>
#include <linux/regmap.h>
#include <linux/i3c/device.h>
#include <linux/i3c/master.h>
@ -18,7 +19,7 @@ static int regmap_i3c_write(void *context, const void *data, size_t count)
},
};
return i3c_device_do_priv_xfers(i3c, xfers, 1);
return i3c_device_do_priv_xfers(i3c, xfers, ARRAY_SIZE(xfers));
}
static int regmap_i3c_read(void *context,
@ -37,7 +38,7 @@ static int regmap_i3c_read(void *context,
xfers[1].len = val_size;
xfers[1].data.in = val;
return i3c_device_do_priv_xfers(i3c, xfers, 2);
return i3c_device_do_priv_xfers(i3c, xfers, ARRAY_SIZE(xfers));
}
static const struct regmap_bus regmap_i3c = {

View File

@ -54,6 +54,8 @@ static const char *regcache_type_name(enum regcache_type type)
return "none";
case REGCACHE_FLAT:
return "flat";
case REGCACHE_FLAT_S:
return "flat-sparse";
case REGCACHE_RBTREE:
return "rbtree";
case REGCACHE_MAPLE:
@ -93,6 +95,8 @@ static const struct regmap_test_param regcache_types_list[] = {
{ .cache = REGCACHE_NONE, .fast_io = true },
{ .cache = REGCACHE_FLAT },
{ .cache = REGCACHE_FLAT, .fast_io = true },
{ .cache = REGCACHE_FLAT_S },
{ .cache = REGCACHE_FLAT_S, .fast_io = true },
{ .cache = REGCACHE_RBTREE },
{ .cache = REGCACHE_RBTREE, .fast_io = true },
{ .cache = REGCACHE_MAPLE },
@ -104,6 +108,8 @@ KUNIT_ARRAY_PARAM(regcache_types, regcache_types_list, param_to_desc);
static const struct regmap_test_param real_cache_types_only_list[] = {
{ .cache = REGCACHE_FLAT },
{ .cache = REGCACHE_FLAT, .fast_io = true },
{ .cache = REGCACHE_FLAT_S },
{ .cache = REGCACHE_FLAT_S, .fast_io = true },
{ .cache = REGCACHE_RBTREE },
{ .cache = REGCACHE_RBTREE, .fast_io = true },
{ .cache = REGCACHE_MAPLE },
@ -119,6 +125,12 @@ static const struct regmap_test_param real_cache_types_list[] = {
{ .cache = REGCACHE_FLAT, .from_reg = 0x2002 },
{ .cache = REGCACHE_FLAT, .from_reg = 0x2003 },
{ .cache = REGCACHE_FLAT, .from_reg = 0x2004 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0, .fast_io = true },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2001 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2002 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2003 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2004 },
{ .cache = REGCACHE_RBTREE, .from_reg = 0 },
{ .cache = REGCACHE_RBTREE, .from_reg = 0, .fast_io = true },
{ .cache = REGCACHE_RBTREE, .from_reg = 0x2001 },
@ -136,6 +148,12 @@ static const struct regmap_test_param real_cache_types_list[] = {
KUNIT_ARRAY_PARAM(real_cache_types, real_cache_types_list, param_to_desc);
static const struct regmap_test_param sparse_cache_types_list[] = {
{ .cache = REGCACHE_FLAT_S, .from_reg = 0 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0, .fast_io = true },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2001 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2002 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2003 },
{ .cache = REGCACHE_FLAT_S, .from_reg = 0x2004 },
{ .cache = REGCACHE_RBTREE, .from_reg = 0 },
{ .cache = REGCACHE_RBTREE, .from_reg = 0, .fast_io = true },
{ .cache = REGCACHE_RBTREE, .from_reg = 0x2001 },
@ -1597,6 +1615,8 @@ static const struct regmap_test_param raw_types_list[] = {
{ .cache = REGCACHE_NONE, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_FLAT, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_FLAT, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_FLAT_S, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_FLAT_S, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_RBTREE, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_RBTREE, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_MAPLE, .val_endian = REGMAP_ENDIAN_LITTLE },
@ -1608,6 +1628,8 @@ KUNIT_ARRAY_PARAM(raw_test_types, raw_types_list, param_to_desc);
static const struct regmap_test_param raw_cache_types_list[] = {
{ .cache = REGCACHE_FLAT, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_FLAT, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_FLAT_S, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_FLAT_S, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_RBTREE, .val_endian = REGMAP_ENDIAN_LITTLE },
{ .cache = REGCACHE_RBTREE, .val_endian = REGMAP_ENDIAN_BIG },
{ .cache = REGCACHE_MAPLE, .val_endian = REGMAP_ENDIAN_LITTLE },

View File

@ -17,10 +17,11 @@ struct regmap_mbq_context {
struct device *dev;
struct sdw_slave *sdw;
bool (*readable_reg)(struct device *dev, unsigned int reg);
struct regmap_sdw_mbq_cfg cfg;
int val_size;
bool (*readable_reg)(struct device *dev, unsigned int reg);
};
static int regmap_sdw_mbq_size(struct regmap_mbq_context *ctx, unsigned int reg)

View File

@ -55,18 +55,23 @@ struct sdw_slave;
#define REGMAP_DOWNSHIFT(s) (s)
/*
* The supported cache types, the default is no cache. Any new caches
* should usually use the maple tree cache unless they specifically
* require that there are never any allocations at runtime and can't
* provide defaults in which case they should use the flat cache. The
* rbtree cache *may* have some performance advantage for very low end
* systems that make heavy use of cache syncs but is mainly legacy.
* The supported cache types, the default is no cache. Any new caches should
* usually use the maple tree cache unless they specifically require that there
* are never any allocations at runtime in which case they should use the sparse
* flat cache. The rbtree cache *may* have some performance advantage for very
* low end systems that make heavy use of cache syncs but is mainly legacy.
* These caches are sparse and entries will be initialized from hardware if no
* default has been provided.
* The non-sparse flat cache is provided for compatibility with existing users
* and will zero-initialize cache entries for which no defaults are provided.
* New users should use the sparse flat cache.
*/
enum regcache_type {
REGCACHE_NONE,
REGCACHE_RBTREE,
REGCACHE_FLAT,
REGCACHE_MAPLE,
REGCACHE_FLAT_S,
};
/**