From 697e85bc6a9aa44ecd73392586fe9cfd7e0467ba Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:22 +0100 Subject: 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 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) (limited to 'drivers/base') 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 @@ out: } 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 * -- cgit v1.2.3 From 154881e59b8dbf84121e3e78c4e613e840752aa9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:23 +0100 Subject: 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 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/base') 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; } -- cgit v1.2.3 From 0186645d2549f94c3a8067c97cad261c678d6718 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:24 +0100 Subject: 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(-) (limited to 'drivers/base') 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; } -- cgit v1.2.3 From 7278af5fb3eb7247449fd4489dacb75b9ba86f73 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 May 2013 13:55:25 +0100 Subject: 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(-) (limited to 'drivers/base') 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) { -- cgit v1.2.3 From f9e464a566a1c3c9b71558e5523288b5432c3e3f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 9 May 2013 14:35:49 +0100 Subject: regmap: debugfs: Don't mark lockdep as broken due to debugfs write A register write to hardware is reasonably unlikely to cause locking dependency issues, the reason we're tainting is that unexpected changes in the hardware configuration may confuse drivers. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 975719bc3450..8772fb77e6ed 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -281,7 +281,7 @@ static ssize_t regmap_map_write_file(struct file *file, return -EINVAL; /* Userspace has been fiddling around behind the kernel's back */ - add_taint(TAINT_USER, LOCKDEP_NOW_UNRELIABLE); + add_taint(TAINT_USER, LOCKDEP_STILL_OK); ret = regmap_write(map, reg, value); if (ret < 0) -- cgit v1.2.3 From 213fa5d9685b985e0c61a8db1883a3abf94b18d7 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 14 May 2013 07:54:23 +0100 Subject: regmap: debugfs: Fix return from regmap_debugfs_get_dump_start regmap_debugfs_get_dump_start should return the offset of the register it should start reading from, However in the current code at one point the code does not return correct register offset. With this patch all the returns from this function takes reg_stride in to consideration to return correct offset. Signed-off-by: Srinivas Kandagatla Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 8772fb77e6ed..98cb94e58320 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -145,7 +145,7 @@ static unsigned int regmap_debugfs_get_dump_start(struct regmap *map, reg_offset = fpos_offset / map->debugfs_tot_len; *pos = c->min + (reg_offset * map->debugfs_tot_len); mutex_unlock(&map->cache_lock); - return c->base_reg + reg_offset; + return c->base_reg + (reg_offset * map->reg_stride); } *pos = c->max; -- cgit v1.2.3 From 81485f5220770c381ac076573642ac44f13723af Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 23 May 2013 15:06:15 +0200 Subject: 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(-) (limited to 'drivers/base') 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 @@ out: 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 @@ out: 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); -- cgit v1.2.3 From 92ab1aab59c61b3e05200b9aa0e05ab770059142 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 24 May 2013 10:29:22 +0200 Subject: 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(-) (limited to 'drivers/base') 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) -- cgit v1.2.3 From d856fce41b74ecf2004d99f781e44abd7815d7f8 Mon Sep 17 00:00:00 2001 From: Maarten ter Huurne Date: Mon, 3 Jun 2013 00:15:26 +0200 Subject: 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(-) (limited to 'drivers/base') 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"); -- cgit v1.2.3 From b92be6fecc9f1e8b927d99c12dad9f9dcd729727 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 3 Jun 2013 17:24:08 +0100 Subject: regmap: core: Cache all registers by default when cache is enabled Currently all register maps with a cache need to provide a volatile callback since the default is to assume all registers are volatile. This is not sensible if we have a cache so change the default to be fully cached if a cache is provided. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index a941dcfe7590..2f1f3ff6c6bf 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -123,7 +123,10 @@ bool regmap_volatile(struct regmap *map, unsigned int reg) if (map->volatile_table) return _regmap_check_range_table(map, reg, map->volatile_table); - return true; + if (map->cache_ops) + return false; + else + return true; } bool regmap_precious(struct regmap *map, unsigned int reg) -- cgit v1.2.3 From 67252287871113deba96adf7e4df1752f3f08688 Mon Sep 17 00:00:00 2001 From: Srinivas Kandagatla Date: Tue, 11 Jun 2013 13:18:15 +0100 Subject: regmap: Add regmap_field APIs It is common to access regmap registers at bit level, using regmap_update_bits or regmap_read functions, however the end user has to take care of a mask or shifting. This becomes overhead when such use cases are high. Having a common function to do this is much convenient and less error prone. The idea of regmap_field is simple, regmap_field gives a logical structure to bits of the regmap register, and the driver can use this logical entity without the knowledge of the bit positions and masks all over the code. This way code looks much neat and it need not handle the masks, shifts every time it access the those entities. With this new regmap_field_read/write apis the end user can setup a regmap field using regmap_field_init and use the return regmap_field to read write the register field without worrying about the masks or shifts. Also this apis will be useful for drivers which are based on regmaps, like some clocks or pinctrls which can work on the regmap_fields directly without having to worry about bit positions. Signed-off-by: Srinivas Kandagatla Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 8 +++ drivers/base/regmap/regmap.c | 130 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index c130536e0ab0..c5f6ebd0466d 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -174,6 +174,14 @@ struct regmap_range_node { unsigned int window_len; }; +struct regmap_field { + struct regmap *regmap; + unsigned int mask; + /* lsb */ + unsigned int shift; + unsigned int reg; +}; + #ifdef CONFIG_DEBUG_FS extern void regmap_debugfs_initcall(void); extern void regmap_debugfs_init(struct regmap *map, const char *name); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index a941dcfe7590..fef6f13b96bb 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -801,6 +801,95 @@ struct regmap *devm_regmap_init(struct device *dev, } EXPORT_SYMBOL_GPL(devm_regmap_init); +static void regmap_field_init(struct regmap_field *rm_field, + struct regmap *regmap, struct reg_field reg_field) +{ + int field_bits = reg_field.msb - reg_field.lsb + 1; + rm_field->regmap = regmap; + rm_field->reg = reg_field.reg; + rm_field->shift = reg_field.lsb; + rm_field->mask = ((BIT(field_bits) - 1) << reg_field.lsb); +} + +/** + * devm_regmap_field_alloc(): Allocate and initialise a register field + * in a register map. + * + * @dev: Device that will be interacted with + * @regmap: regmap bank in which this register field is located. + * @reg_field: Register field with in the bank. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap_field. The regmap_field will be automatically freed + * by the device management code. + */ +struct regmap_field *devm_regmap_field_alloc(struct device *dev, + struct regmap *regmap, struct reg_field reg_field) +{ + struct regmap_field *rm_field = devm_kzalloc(dev, + sizeof(*rm_field), GFP_KERNEL); + if (!rm_field) + return ERR_PTR(-ENOMEM); + + regmap_field_init(rm_field, regmap, reg_field); + + return rm_field; + +} +EXPORT_SYMBOL_GPL(devm_regmap_field_alloc); + +/** + * devm_regmap_field_free(): Free register field allocated using + * devm_regmap_field_alloc. Usally drivers need not call this function, + * as the memory allocated via devm will be freed as per device-driver + * life-cyle. + * + * @dev: Device that will be interacted with + * @field: regmap field which should be freed. + */ +void devm_regmap_field_free(struct device *dev, + struct regmap_field *field) +{ + devm_kfree(dev, field); +} +EXPORT_SYMBOL_GPL(devm_regmap_field_free); + +/** + * regmap_field_alloc(): Allocate and initialise a register field + * in a register map. + * + * @regmap: regmap bank in which this register field is located. + * @reg_field: Register field with in the bank. + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap_field. The regmap_field should be freed by the + * user once its finished working with it using regmap_field_free(). + */ +struct regmap_field *regmap_field_alloc(struct regmap *regmap, + struct reg_field reg_field) +{ + struct regmap_field *rm_field = kzalloc(sizeof(*rm_field), GFP_KERNEL); + + if (!rm_field) + return ERR_PTR(-ENOMEM); + + regmap_field_init(rm_field, regmap, reg_field); + + return rm_field; +} +EXPORT_SYMBOL_GPL(regmap_field_alloc); + +/** + * regmap_field_free(): Free register field allocated using regmap_field_alloc + * + * @field: regmap field which should be freed. + */ +void regmap_field_free(struct regmap_field *field) +{ + kfree(field); +} +EXPORT_SYMBOL_GPL(regmap_field_free); + /** * regmap_reinit_cache(): Reinitialise the current register cache * @@ -1249,6 +1338,22 @@ int regmap_raw_write(struct regmap *map, unsigned int reg, } EXPORT_SYMBOL_GPL(regmap_raw_write); +/** + * regmap_field_write(): Write a value to a single register field + * + * @field: Register field to write to + * @val: Value to be written + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_field_write(struct regmap_field *field, unsigned int val) +{ + return regmap_update_bits(field->regmap, field->reg, + field->mask, val << field->shift); +} +EXPORT_SYMBOL_GPL(regmap_field_write); + /* * regmap_bulk_write(): Write multiple registers to the device * @@ -1531,6 +1636,31 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, } EXPORT_SYMBOL_GPL(regmap_raw_read); +/** + * regmap_field_read(): Read a value to a single register field + * + * @field: Register field to read from + * @val: Pointer to store read value + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_field_read(struct regmap_field *field, unsigned int *val) +{ + int ret; + unsigned int reg_val; + ret = regmap_read(field->regmap, field->reg, ®_val); + if (ret != 0) + return ret; + + reg_val &= field->mask; + reg_val >>= field->shift; + *val = reg_val; + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_field_read); + /** * regmap_bulk_read(): Read multiple registers from the device * -- cgit v1.2.3 From d6814a7dafa590ec5fe0597922ea76354f9bec59 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 May 2013 15:54:54 +0100 Subject: 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(+) (limited to 'drivers/base') 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. -- cgit v1.2.3