From 697e85bc6a9aa44ecd73392586fe9cfd7e0467ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:22 +0100 Subject: [PATCH 1/8] regmap: Add support for discarding parts of the register cache Allow drivers to discard parts of the register cache, for example if part of the hardware has been reset. Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regcache.c | 37 ++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 9 +++++++++ include/trace/events/regmap.h | 23 +++++++++++++++++++++ 4 files changed, 70 insertions(+) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index c130536e0ab0..b33a4ff67adf 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -148,6 +148,7 @@ struct regcache_ops { int (*read)(struct regmap *map, unsigned int reg, unsigned int *value); int (*write)(struct regmap *map, unsigned int reg, unsigned int value); int (*sync)(struct regmap *map, unsigned int min, unsigned int max); + int (*drop)(struct regmap *map, unsigned int min, unsigned int max); }; bool regmap_writeable(struct regmap *map, unsigned int reg); diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 75923f2396bd..8a0ab5fa75f5 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -358,6 +358,43 @@ int regcache_sync_region(struct regmap *map, unsigned int min, } EXPORT_SYMBOL_GPL(regcache_sync_region); +/** + * regcache_drop_region: Discard part of the register cache + * + * @map: map to operate on + * @min: first register to discard + * @max: last register to discard + * + * Discard part of the register cache. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_drop_region(struct regmap *map, unsigned int min, + unsigned int max) +{ + unsigned int reg; + int ret = 0; + + if (!map->cache_present && !(map->cache_ops && map->cache_ops->drop)) + return -EINVAL; + + map->lock(map); + + trace_regcache_drop_region(map->dev, min, max); + + if (map->cache_present) + for (reg = min; reg < max + 1; reg++) + clear_bit(reg, map->cache_present); + + if (map->cache_ops && map->cache_ops->drop) + ret = map->cache_ops->drop(map, min, max); + + map->unlock(map); + + return ret; +} +EXPORT_SYMBOL_GPL(regcache_drop_region); + /** * regcache_cache_only: Put a register map into cache only mode * diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 02d84e24b7c2..5067ee94eb92 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -394,6 +394,8 @@ bool regmap_can_raw_write(struct regmap *map); int regcache_sync(struct regmap *map); int regcache_sync_region(struct regmap *map, unsigned int min, unsigned int max); +int regcache_drop_region(struct regmap *map, unsigned int min, + unsigned int max); void regcache_cache_only(struct regmap *map, bool enable); void regcache_cache_bypass(struct regmap *map, bool enable); void regcache_mark_dirty(struct regmap *map); @@ -562,6 +564,13 @@ static inline int regcache_sync_region(struct regmap *map, unsigned int min, return -EINVAL; } +static inline int regcache_drop_region(struct regmap *map, unsigned int min, + unsigned int max) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline void regcache_cache_only(struct regmap *map, bool enable) { WARN_ONCE(1, "regmap API is disabled"); diff --git a/include/trace/events/regmap.h b/include/trace/events/regmap.h index a43a2f67bd8e..23d561512f64 100644 --- a/include/trace/events/regmap.h +++ b/include/trace/events/regmap.h @@ -223,6 +223,29 @@ DEFINE_EVENT(regmap_async, regmap_async_complete_done, ); +TRACE_EVENT(regcache_drop_region, + + TP_PROTO(struct device *dev, unsigned int from, + unsigned int to), + + TP_ARGS(dev, from, to), + + TP_STRUCT__entry( + __string( name, dev_name(dev) ) + __field( unsigned int, from ) + __field( unsigned int, to ) + ), + + TP_fast_assign( + __assign_str(name, dev_name(dev)); + __entry->from = from; + __entry->to = to; + ), + + TP_printk("%s %u-%u", __get_str(name), (unsigned int)__entry->from, + (unsigned int)__entry->to) +); + #endif /* _TRACE_REGMAP_H */ /* This part must be outside protection */ From 154881e59b8dbf84121e3e78c4e613e840752aa9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:23 +0100 Subject: [PATCH 2/8] regmap: Make regmap_check_range_table() a public API Allow drivers to use an access table as part of their implementation. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 14 +++++++------- include/linux/regmap.h | 3 +++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index a941dcfe7590..307f5a1c1fe8 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -65,9 +65,8 @@ bool regmap_reg_in_ranges(unsigned int reg, } EXPORT_SYMBOL_GPL(regmap_reg_in_ranges); -static bool _regmap_check_range_table(struct regmap *map, - unsigned int reg, - const struct regmap_access_table *table) +bool regmap_check_range_table(struct regmap *map, unsigned int reg, + const struct regmap_access_table *table) { /* Check "no ranges" first */ if (regmap_reg_in_ranges(reg, table->no_ranges, table->n_no_ranges)) @@ -80,6 +79,7 @@ static bool _regmap_check_range_table(struct regmap *map, return regmap_reg_in_ranges(reg, table->yes_ranges, table->n_yes_ranges); } +EXPORT_SYMBOL_GPL(regmap_check_range_table); bool regmap_writeable(struct regmap *map, unsigned int reg) { @@ -90,7 +90,7 @@ bool regmap_writeable(struct regmap *map, unsigned int reg) return map->writeable_reg(map->dev, reg); if (map->wr_table) - return _regmap_check_range_table(map, reg, map->wr_table); + return regmap_check_range_table(map, reg, map->wr_table); return true; } @@ -107,7 +107,7 @@ bool regmap_readable(struct regmap *map, unsigned int reg) return map->readable_reg(map->dev, reg); if (map->rd_table) - return _regmap_check_range_table(map, reg, map->rd_table); + return regmap_check_range_table(map, reg, map->rd_table); return true; } @@ -121,7 +121,7 @@ bool regmap_volatile(struct regmap *map, unsigned int reg) return map->volatile_reg(map->dev, reg); if (map->volatile_table) - return _regmap_check_range_table(map, reg, map->volatile_table); + return regmap_check_range_table(map, reg, map->volatile_table); return true; } @@ -135,7 +135,7 @@ bool regmap_precious(struct regmap *map, unsigned int reg) return map->precious_reg(map->dev, reg); if (map->precious_table) - return _regmap_check_range_table(map, reg, map->precious_table); + return regmap_check_range_table(map, reg, map->precious_table); return false; } diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 5067ee94eb92..d6f3221e29d4 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -400,6 +400,9 @@ void regcache_cache_only(struct regmap *map, bool enable); void regcache_cache_bypass(struct regmap *map, bool enable); void regcache_mark_dirty(struct regmap *map); +bool regmap_check_range_table(struct regmap *map, unsigned int reg, + const struct regmap_access_table *table); + int regmap_register_patch(struct regmap *map, const struct reg_default *regs, int num_regs); From 0186645d2549f94c3a8067c97cad261c678d6718 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:24 +0100 Subject: [PATCH 3/8] regmap: rbtree: Factor out node allocation In preparation for being slightly smarter about how we allocate memory factor out the node allocation. Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 41 ++++++++++++++++++--------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index aa0875f6f1b7..3a000145ac6e 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -304,6 +304,27 @@ static int regcache_rbtree_insert_to_block(struct regmap *map, return 0; } +static struct regcache_rbtree_node * +regcache_rbtree_node_alloc(struct regmap *map, unsigned int reg) +{ + struct regcache_rbtree_node *rbnode; + + rbnode = kzalloc(sizeof(*rbnode), GFP_KERNEL); + if (!rbnode) + return NULL; + + rbnode->blklen = sizeof(*rbnode); + rbnode->base_reg = reg; + rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size, + GFP_KERNEL); + if (!rbnode->block) { + kfree(rbnode); + return NULL; + } + + return rbnode; +} + static int regcache_rbtree_write(struct regmap *map, unsigned int reg, unsigned int value) { @@ -354,23 +375,15 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, return 0; } } - /* we did not manage to find a place to insert it in an existing - * block so create a new rbnode with a single register in its block. - * This block will get populated further if any other adjacent - * registers get modified in the future. + + /* We did not manage to find a place to insert it in + * an existing block so create a new rbnode. */ - rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL); + rbnode = regcache_rbtree_node_alloc(map, reg); if (!rbnode) return -ENOMEM; - rbnode->blklen = sizeof(*rbnode); - rbnode->base_reg = reg; - rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size, - GFP_KERNEL); - if (!rbnode->block) { - kfree(rbnode); - return -ENOMEM; - } - regcache_rbtree_set_register(map, rbnode, 0, value); + regcache_rbtree_set_register(map, rbnode, + reg - rbnode->base_reg, value); regcache_rbtree_insert(map, &rbtree_ctx->root, rbnode); rbtree_ctx->cached_rbnode = rbnode; } From 7278af5fb3eb7247449fd4489dacb75b9ba86f73 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:25 +0100 Subject: [PATCH 4/8] regmap: rbtree: Use range information to allocate nodes If range information has been provided then when we allocate a rbnode within a range allocate the entire range. The goal is to minimise the number of reallocations done when combining or extending blocks. At present only readability and yes_ranges are taken into account, this is expected to cover most cases efficiently. Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 3a000145ac6e..4e131c5df844 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -308,13 +308,34 @@ static struct regcache_rbtree_node * regcache_rbtree_node_alloc(struct regmap *map, unsigned int reg) { struct regcache_rbtree_node *rbnode; + const struct regmap_range *range; + int i; rbnode = kzalloc(sizeof(*rbnode), GFP_KERNEL); if (!rbnode) return NULL; - rbnode->blklen = sizeof(*rbnode); - rbnode->base_reg = reg; + /* If there is a read table then use it to guess at an allocation */ + if (map->rd_table) { + for (i = 0; i < map->rd_table->n_yes_ranges; i++) { + if (regmap_reg_in_range(reg, + &map->rd_table->yes_ranges[i])) + break; + } + + if (i != map->rd_table->n_yes_ranges) { + range = &map->rd_table->yes_ranges[i]; + rbnode->blklen = range->range_max - range->range_min + + 1; + rbnode->base_reg = range->range_min; + } + } + + if (!rbnode->blklen) { + rbnode->blklen = sizeof(*rbnode); + rbnode->base_reg = reg; + } + rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size, GFP_KERNEL); if (!rbnode->block) { From 81485f5220770c381ac076573642ac44f13723af Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 23 May 2013 15:06:15 +0200 Subject: [PATCH 5/8] regmap: regcache: Fixup locking for custom lock callbacks The parameter passed to the regmap lock/unlock callbacks needs to be map->lock_arg, regcache passes just map. This works fine in the case that no custom locking callbacks are used, since in this case map->lock_arg equals map, but will break when custom locking callbacks are used. The issue was introduced in commit 0d4529c5 ("regmap: make lock/unlock functions customizable") and is fixed by this patch. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 4 ++-- drivers/base/regmap/regcache.c | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 4e131c5df844..69b443f204ac 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -143,7 +143,7 @@ static int rbtree_show(struct seq_file *s, void *ignored) int registers = 0; int this_registers, average; - map->lock(map); + map->lock(map->lock_arg); mem_size = sizeof(*rbtree_ctx); mem_size += BITS_TO_LONGS(map->cache_present_nbits) * sizeof(long); @@ -170,7 +170,7 @@ static int rbtree_show(struct seq_file *s, void *ignored) seq_printf(s, "%d nodes, %d registers, average %d registers, used %zu bytes\n", nodes, registers, average, mem_size); - map->unlock(map); + map->unlock(map->lock_arg); return 0; } diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 8a0ab5fa75f5..4bfa219ef37d 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -270,7 +270,7 @@ int regcache_sync(struct regmap *map) BUG_ON(!map->cache_ops || !map->cache_ops->sync); - map->lock(map); + map->lock(map->lock_arg); /* Remember the initial bypass state */ bypass = map->cache_bypass; dev_dbg(map->dev, "Syncing %s cache\n", @@ -306,7 +306,7 @@ int regcache_sync(struct regmap *map) trace_regcache_sync(map->dev, name, "stop"); /* Restore the bypass state */ map->cache_bypass = bypass; - map->unlock(map); + map->unlock(map->lock_arg); return ret; } @@ -333,7 +333,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, BUG_ON(!map->cache_ops || !map->cache_ops->sync); - map->lock(map); + map->lock(map->lock_arg); /* Remember the initial bypass state */ bypass = map->cache_bypass; @@ -352,7 +352,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, trace_regcache_sync(map->dev, name, "stop region"); /* Restore the bypass state */ map->cache_bypass = bypass; - map->unlock(map); + map->unlock(map->lock_arg); return ret; } @@ -378,7 +378,7 @@ int regcache_drop_region(struct regmap *map, unsigned int min, if (!map->cache_present && !(map->cache_ops && map->cache_ops->drop)) return -EINVAL; - map->lock(map); + map->lock(map->lock_arg); trace_regcache_drop_region(map->dev, min, max); @@ -389,7 +389,7 @@ int regcache_drop_region(struct regmap *map, unsigned int min, if (map->cache_ops && map->cache_ops->drop) ret = map->cache_ops->drop(map, min, max); - map->unlock(map); + map->unlock(map->lock_arg); return ret; } @@ -409,11 +409,11 @@ EXPORT_SYMBOL_GPL(regcache_drop_region); */ void regcache_cache_only(struct regmap *map, bool enable) { - map->lock(map); + map->lock(map->lock_arg); WARN_ON(map->cache_bypass && enable); map->cache_only = enable; trace_regmap_cache_only(map->dev, enable); - map->unlock(map); + map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_only); @@ -428,9 +428,9 @@ EXPORT_SYMBOL_GPL(regcache_cache_only); */ void regcache_mark_dirty(struct regmap *map) { - map->lock(map); + map->lock(map->lock_arg); map->cache_dirty = true; - map->unlock(map); + map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_mark_dirty); @@ -447,11 +447,11 @@ EXPORT_SYMBOL_GPL(regcache_mark_dirty); */ void regcache_cache_bypass(struct regmap *map, bool enable) { - map->lock(map); + map->lock(map->lock_arg); WARN_ON(map->cache_only && enable); map->cache_bypass = enable; trace_regmap_cache_bypass(map->dev, enable); - map->unlock(map); + map->unlock(map->lock_arg); } EXPORT_SYMBOL_GPL(regcache_cache_bypass); From 92ab1aab59c61b3e05200b9aa0e05ab770059142 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 24 May 2013 10:29:22 +0200 Subject: [PATCH 6/8] regmap: Make regmap-mmio usable from atomic contexts regmap-mmio uses a spinlock with spin_lock() and spin_unlock() for locking. To be able to use the regmap API from different contexts (atomic vs non-atomic), without the risk of race conditions, we need to use spin_lock_irqsave() and spin_lock_irqrestore() instead. A new field, the spinlock_flags field, is added to regmap struct to store the flags between regmap_{,un}lock_spinlock(). The spinlock_flags field itself is also protected by the spinlock. Thanks to Stephen Warren for the suggestion of this particular solution. Signed-off-by: Lars-Peter Clausen Reviewed-by: Stephen Warren Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regmap.c | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index b33a4ff67adf..ae23d8391aa0 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -52,6 +52,7 @@ struct regmap_async { struct regmap { struct mutex mutex; spinlock_t spinlock; + unsigned long spinlock_flags; regmap_lock lock; regmap_unlock unlock; void *lock_arg; /* This is passed to lock/unlock functions */ diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 307f5a1c1fe8..1a01553189b3 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -302,13 +302,16 @@ static void regmap_unlock_mutex(void *__map) static void regmap_lock_spinlock(void *__map) { struct regmap *map = __map; - spin_lock(&map->spinlock); + unsigned long flags; + + spin_lock_irqsave(&map->spinlock, flags); + map->spinlock_flags = flags; } static void regmap_unlock_spinlock(void *__map) { struct regmap *map = __map; - spin_unlock(&map->spinlock); + spin_unlock_irqrestore(&map->spinlock, map->spinlock_flags); } static void dev_get_regmap_release(struct device *dev, void *res) From d856fce41b74ecf2004d99f781e44abd7815d7f8 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 3 Jun 2013 00:15:26 +0200 Subject: [PATCH 7/8] regmap: Implemented default cache sync operation This can be used for cache types for which syncing values one by one is equally efficient as syncing a range, such as the flat cache. Signed-off-by: Maarten ter Huurne Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 46 +++++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 4bfa219ef37d..e69102696533 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -250,6 +250,38 @@ int regcache_write(struct regmap *map, return 0; } +static int regcache_default_sync(struct regmap *map, unsigned int min, + unsigned int max) +{ + unsigned int reg; + + for (reg = min; reg <= max; reg++) { + unsigned int val; + int ret; + + if (regmap_volatile(map, reg)) + continue; + + ret = regcache_read(map, reg, &val); + if (ret) + return ret; + + /* Is this the hardware default? If so skip. */ + ret = regcache_lookup_reg(map, reg); + if (ret >= 0 && val == map->reg_defaults[ret].def) + continue; + + map->cache_bypass = 1; + ret = _regmap_write(map, reg, val); + map->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", reg, val); + } + + return 0; +} + /** * regcache_sync: Sync the register cache with the hardware. * @@ -268,7 +300,7 @@ int regcache_sync(struct regmap *map) const char *name; unsigned int bypass; - BUG_ON(!map->cache_ops || !map->cache_ops->sync); + BUG_ON(!map->cache_ops); map->lock(map->lock_arg); /* Remember the initial bypass state */ @@ -297,7 +329,10 @@ int regcache_sync(struct regmap *map) } map->cache_bypass = 0; - ret = map->cache_ops->sync(map, 0, map->max_register); + if (map->cache_ops->sync) + ret = map->cache_ops->sync(map, 0, map->max_register); + else + ret = regcache_default_sync(map, 0, map->max_register); if (ret == 0) map->cache_dirty = false; @@ -331,7 +366,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, const char *name; unsigned int bypass; - BUG_ON(!map->cache_ops || !map->cache_ops->sync); + BUG_ON(!map->cache_ops); map->lock(map->lock_arg); @@ -346,7 +381,10 @@ int regcache_sync_region(struct regmap *map, unsigned int min, if (!map->cache_dirty) goto out; - ret = map->cache_ops->sync(map, min, max); + if (map->cache_ops->sync) + ret = map->cache_ops->sync(map, min, max); + else + ret = regcache_default_sync(map, min, max); out: trace_regcache_sync(map->dev, name, "stop region"); From d6814a7dafa590ec5fe0597922ea76354f9bec59 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 May 2013 15:54:54 +0100 Subject: [PATCH 8/8] regmap: debugfs: Suppress cache for partial register files The cache is based on the full register map so confuses things if used for a partial map. Reported-by: Bard Liao Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 23b701f5fd2f..686400994868 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -84,6 +84,10 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, unsigned int fpos_offset; unsigned int reg_offset; + /* Suppress the cache if we're using a subrange */ + if (from) + return from; + /* * If we don't have a cache build one so we don't have to do a * linear scan each time.