From fdf200290581150f7b69148abf6ca860684cbfbb Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Sun, 1 Sep 2013 20:24:50 -0700 Subject: [PATCH 01/16] regmap: add regmap_field_update_bits() Current regmap_field is supporting read/write functions. This patch adds new update_bits function for it. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 20 ++++++++++++++++++++ include/linux/regmap.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7d689a15c500..285afa7470c0 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1369,6 +1369,26 @@ int regmap_field_write(struct regmap_field *field, unsigned int val) } EXPORT_SYMBOL_GPL(regmap_field_write); +/** + * regmap_field_update_bits(): Perform a read/modify/write cycle + * on the register field + * + * @field: Register field to write to + * @mask: Bitmask to change + * @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_update_bits(struct regmap_field *field, unsigned int mask, unsigned int val) +{ + mask = (mask << field->shift) & field->mask; + + return regmap_update_bits(field->regmap, field->reg, + mask, val << field->shift); +} +EXPORT_SYMBOL_GPL(regmap_field_update_bits); + /* * regmap_bulk_write(): Write multiple registers to the device * diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a10380bfbeac..4c8c20a7a75d 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -448,6 +448,8 @@ void devm_regmap_field_free(struct device *dev, struct regmap_field *field); int regmap_field_read(struct regmap_field *field, unsigned int *val); int regmap_field_write(struct regmap_field *field, unsigned int val); +int regmap_field_update_bits(struct regmap_field *field, + unsigned int mask, unsigned int val); /** * Description of an IRQ for the generic regmap irq_chip. From a0102375ee82db1e08324b1a21484854cf2c1677 Mon Sep 17 00:00:00 2001 From: Kuninori Morimoto Date: Sun, 1 Sep 2013 20:30:50 -0700 Subject: [PATCH 02/16] regmap: Add regmap_fields APIs Current Linux kernel is supporting regmap_field method and it is very useful feature. It needs one regmap_filed for one register access. OTOH, there is multi port device which has many same registers in the market. The difference for each register access is only its address offset. Current API needs many regmap_field for such device, but it is not good. This patch adds new regmap_fileds API which can care about multi port/offset access via regmap. Signed-off-by: Kuninori Morimoto Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 3 ++ drivers/base/regmap/regmap.c | 83 ++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 11 +++++ 3 files changed, 97 insertions(+) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 57f777835d97..9010614f7793 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -179,6 +179,9 @@ struct regmap_field { /* lsb */ unsigned int shift; unsigned int reg; + + unsigned int id_size; + unsigned int id_offset; }; #ifdef CONFIG_DEBUG_FS diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 285afa7470c0..00152bf7ab12 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -821,6 +821,8 @@ static void regmap_field_init(struct regmap_field *rm_field, rm_field->reg = reg_field.reg; rm_field->shift = reg_field.lsb; rm_field->mask = ((BIT(field_bits) - 1) << reg_field.lsb); + rm_field->id_size = reg_field.id_size; + rm_field->id_offset = reg_field.id_offset; } /** @@ -1389,6 +1391,54 @@ int regmap_field_update_bits(struct regmap_field *field, unsigned int mask, unsi } EXPORT_SYMBOL_GPL(regmap_field_update_bits); +/** + * regmap_fields_write(): Write a value to a single register field with port ID + * + * @field: Register field to write to + * @id: port ID + * @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_fields_write(struct regmap_field *field, unsigned int id, + unsigned int val) +{ + if (id >= field->id_size) + return -EINVAL; + + return regmap_update_bits(field->regmap, + field->reg + (field->id_offset * id), + field->mask, val << field->shift); +} +EXPORT_SYMBOL_GPL(regmap_fields_write); + +/** + * regmap_fields_update_bits(): Perform a read/modify/write cycle + * on the register field + * + * @field: Register field to write to + * @id: port ID + * @mask: Bitmask to change + * @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_fields_update_bits(struct regmap_field *field, unsigned int id, + unsigned int mask, unsigned int val) +{ + if (id >= field->id_size) + return -EINVAL; + + mask = (mask << field->shift) & field->mask; + + return regmap_update_bits(field->regmap, + field->reg + (field->id_offset * id), + mask, val << field->shift); +} +EXPORT_SYMBOL_GPL(regmap_fields_update_bits); + /* * regmap_bulk_write(): Write multiple registers to the device * @@ -1696,6 +1746,39 @@ int regmap_field_read(struct regmap_field *field, unsigned int *val) } EXPORT_SYMBOL_GPL(regmap_field_read); +/** + * regmap_fields_read(): Read a value to a single register field with port ID + * + * @field: Register field to read from + * @id: port ID + * @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_fields_read(struct regmap_field *field, unsigned int id, + unsigned int *val) +{ + int ret; + unsigned int reg_val; + + if (id >= field->id_size) + return -EINVAL; + + ret = regmap_read(field->regmap, + field->reg + (field->id_offset * id), + ®_val); + if (ret != 0) + return ret; + + reg_val &= field->mask; + reg_val >>= field->shift; + *val = reg_val; + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_fields_read); + /** * regmap_bulk_read(): Read multiple registers from the device * diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 4c8c20a7a75d..a12bea07f79e 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -425,11 +425,15 @@ bool regmap_reg_in_ranges(unsigned int reg, * @reg: Offset of the register within the regmap bank * @lsb: lsb of the register field. * @reg: msb of the register field. + * @id_size: port size if it has some ports + * @id_offset: address offset for each ports */ struct reg_field { unsigned int reg; unsigned int lsb; unsigned int msb; + unsigned int id_size; + unsigned int id_offset; }; #define REG_FIELD(_reg, _lsb, _msb) { \ @@ -451,6 +455,13 @@ int regmap_field_write(struct regmap_field *field, unsigned int val); int regmap_field_update_bits(struct regmap_field *field, unsigned int mask, unsigned int val); +int regmap_fields_write(struct regmap_field *field, unsigned int id, + unsigned int val); +int regmap_fields_read(struct regmap_field *field, unsigned int id, + unsigned int *val); +int regmap_fields_update_bits(struct regmap_field *field, unsigned int id, + unsigned int mask, unsigned int val); + /** * Description of an IRQ for the generic regmap irq_chip. * From 6112fe60ac1bd1e68da8cc4248289d6e48015f9b Mon Sep 17 00:00:00 2001 From: Laxman Dewangan Date: Fri, 20 Sep 2013 18:00:10 +0530 Subject: [PATCH 03/16] regmap: add helper macro to set min/max range of register Add helper macro to set the min and max value of the register range. This is useful when initialising the register ranges of the device like static const struct regmap_range readable_ranges[] = { regmap_reg_range(DEVICE_REG0, DEVICE_REG10), }; Signed-off-by: Laxman Dewangan Signed-off-by: Mark Brown --- include/linux/regmap.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a10380bfbeac..7d3ae2be6869 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -70,6 +70,8 @@ struct regmap_range { unsigned int range_max; }; +#define regmap_reg_range(low, high) { .range_min = low, .range_max = high, } + /* * A table of ranges including some yes ranges and some no ranges. * If a register belongs to a no_range, the corresponding check function From 7e09a979404ed07b8f05d09a0e87a87c7891f472 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 7 Oct 2013 23:00:24 +0100 Subject: [PATCH 04/16] regmap: Cache async work structures Rather than allocating and deallocating the structures used to manage async transfers each time we do one keep the structures around as long as the regmap is around. This should provide a small performance improvement. Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 2 +- drivers/base/regmap/regmap.c | 59 +++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 57f777835d97..793ebe207c8a 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -44,7 +44,6 @@ struct regmap_format { struct regmap_async { struct list_head list; - struct work_struct cleanup; struct regmap *map; void *work_buf; }; @@ -67,6 +66,7 @@ struct regmap { spinlock_t async_lock; wait_queue_head_t async_waitq; struct list_head async_list; + struct list_head async_free; int async_ret; #ifdef CONFIG_DEBUG_FS diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7d689a15c500..742f300ca48a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -42,15 +42,6 @@ static int _regmap_bus_formatted_write(void *context, unsigned int reg, static int _regmap_bus_raw_write(void *context, unsigned int reg, unsigned int val); -static void async_cleanup(struct work_struct *work) -{ - struct regmap_async *async = container_of(work, struct regmap_async, - cleanup); - - kfree(async->work_buf); - kfree(async); -} - bool regmap_reg_in_ranges(unsigned int reg, const struct regmap_range *ranges, unsigned int nranges) @@ -465,6 +456,7 @@ struct regmap *regmap_init(struct device *dev, spin_lock_init(&map->async_lock); INIT_LIST_HEAD(&map->async_list); + INIT_LIST_HEAD(&map->async_free); init_waitqueue_head(&map->async_waitq); if (config->read_flag_mask || config->write_flag_mask) { @@ -942,12 +934,22 @@ EXPORT_SYMBOL_GPL(regmap_reinit_cache); */ void regmap_exit(struct regmap *map) { + struct regmap_async *async; + regcache_exit(map); regmap_debugfs_exit(map); regmap_range_exit(map); if (map->bus && map->bus->free_context) map->bus->free_context(map->bus_context); kfree(map->work_buf); + while (!list_empty(&map->async_free)) { + async = list_first_entry_or_null(&map->async_free, + struct regmap_async, + list); + list_del(&async->list); + kfree(async->work_buf); + kfree(async); + } kfree(map); } EXPORT_SYMBOL_GPL(regmap_exit); @@ -1115,20 +1117,31 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, u8[0] |= map->write_flag_mask; if (async && map->bus->async_write) { - struct regmap_async *async = map->bus->async_alloc(); - if (!async) - return -ENOMEM; + struct regmap_async *async; trace_regmap_async_write_start(map->dev, reg, val_len); - async->work_buf = kzalloc(map->format.buf_size, - GFP_KERNEL | GFP_DMA); - if (!async->work_buf) { - kfree(async); - return -ENOMEM; + spin_lock_irqsave(&map->async_lock, flags); + async = list_first_entry_or_null(&map->async_free, + struct regmap_async, + list); + if (async) + list_del(&async->list); + spin_unlock_irqrestore(&map->async_lock, flags); + + if (!async) { + async = map->bus->async_alloc(); + if (!async) + return -ENOMEM; + + async->work_buf = kzalloc(map->format.buf_size, + GFP_KERNEL | GFP_DMA); + if (!async->work_buf) { + kfree(async); + return -ENOMEM; + } } - INIT_WORK(&async->cleanup, async_cleanup); async->map = map; /* If the caller supplied the value we can use it safely. */ @@ -1152,11 +1165,8 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, ret); spin_lock_irqsave(&map->async_lock, flags); - list_del(&async->list); + list_move(&async->list, &map->async_free); spin_unlock_irqrestore(&map->async_lock, flags); - - kfree(async->work_buf); - kfree(async); } return ret; @@ -1820,8 +1830,7 @@ void regmap_async_complete_cb(struct regmap_async *async, int ret) trace_regmap_async_io_complete(map->dev); spin_lock(&map->async_lock); - - list_del(&async->list); + list_move(&async->list, &map->async_free); wake = list_empty(&map->async_list); if (ret != 0) @@ -1829,8 +1838,6 @@ void regmap_async_complete_cb(struct regmap_async *async, int ret) spin_unlock(&map->async_lock); - schedule_work(&async->cleanup); - if (wake) wake_up(&map->async_waitq); } From 651e013e3ce6c0646c39a07e22bebad75a207209 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 8 Oct 2013 18:37:36 +0100 Subject: [PATCH 05/16] regmap: Don't generate gather writes for single register raw writes Since it is quite common for single register raw or async writes to be generated by rbtree cache syncs or firmware downloads and essentially all hardware will be faster with only a single transfer optimise this case by copying single values into the internal scratch buffer before sending. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 742f300ca48a..d27758c337af 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1116,6 +1116,16 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, u8[0] |= map->write_flag_mask; + /* + * Essentially all I/O mechanisms will be faster with a single + * buffer to write. Since register syncs often generate raw + * writes of single registers optimise that case. + */ + if (val != work_val && val_len == map->format.val_bytes) { + memcpy(work_val, val, map->format.val_bytes); + val = work_val; + } + if (async && map->bus->async_write) { struct regmap_async *async; From 0a8198094da895c8d5db95812fe9de7027d808e4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 9 Oct 2013 12:28:52 +0100 Subject: [PATCH 06/16] regmap: Simplify the initiation of async I/O Rather than passing a flag around through the entire call stack store it in the regmap struct and read it when required. This minimises the visibility of the feature through the API, minimising the code updates needed to use it more widely. Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 3 ++- drivers/base/regmap/regcache.c | 3 +-- drivers/base/regmap/regmap.c | 19 +++++++++++-------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 793ebe207c8a..6873b4ce03f9 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -63,6 +63,7 @@ struct regmap { void *bus_context; const char *name; + bool async; spinlock_t async_lock; wait_queue_head_t async_waitq; struct list_head async_list; @@ -218,7 +219,7 @@ bool regcache_set_val(struct regmap *map, void *base, unsigned int idx, int regcache_lookup_reg(struct regmap *map, unsigned int reg); int _regmap_raw_write(struct regmap *map, unsigned int reg, - const void *val, size_t val_len, bool async); + const void *val, size_t val_len); void regmap_async_complete_cb(struct regmap_async *async, int ret); diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index d6c2d691b6e8..a36112af494c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -631,8 +631,7 @@ static int regcache_sync_block_raw_flush(struct regmap *map, const void **data, map->cache_bypass = 1; - ret = _regmap_raw_write(map, base, *data, count * val_bytes, - false); + ret = _regmap_raw_write(map, base, *data, count * val_bytes); map->cache_bypass = 0; diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index d27758c337af..268fb71891ee 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1041,7 +1041,7 @@ static int _regmap_select_page(struct regmap *map, unsigned int *reg, } int _regmap_raw_write(struct regmap *map, unsigned int reg, - const void *val, size_t val_len, bool async) + const void *val, size_t val_len) { struct regmap_range_node *range; unsigned long flags; @@ -1093,7 +1093,7 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, dev_dbg(map->dev, "Writing window %d/%zu\n", win_residue, val_len / map->format.val_bytes); ret = _regmap_raw_write(map, reg, val, win_residue * - map->format.val_bytes, async); + map->format.val_bytes); if (ret != 0) return ret; @@ -1126,7 +1126,7 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, val = work_val; } - if (async && map->bus->async_write) { + if (map->async && map->bus->async_write) { struct regmap_async *async; trace_regmap_async_write_start(map->dev, reg, val_len); @@ -1273,7 +1273,7 @@ static int _regmap_bus_raw_write(void *context, unsigned int reg, map->work_buf + map->format.reg_bytes + map->format.pad_bytes, - map->format.val_bytes, false); + map->format.val_bytes); } static inline void *_regmap_map_get_context(struct regmap *map) @@ -1365,7 +1365,7 @@ int regmap_raw_write(struct regmap *map, unsigned int reg, map->lock(map->lock_arg); - ret = _regmap_raw_write(map, reg, val, val_len, false); + ret = _regmap_raw_write(map, reg, val, val_len); map->unlock(map->lock_arg); @@ -1446,8 +1446,7 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, return ret; } } else { - ret = _regmap_raw_write(map, reg, wval, val_bytes * val_count, - false); + ret = _regmap_raw_write(map, reg, wval, val_bytes * val_count); } if (val_bytes != 1) @@ -1493,7 +1492,11 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, map->lock(map->lock_arg); - ret = _regmap_raw_write(map, reg, val, val_len, true); + map->async = true; + + ret = _regmap_raw_write(map, reg, val, val_len); + + map->async = false; map->unlock(map->lock_arg); From 915f441b6f31b1a8ee01e9263a4e2d44c434d832 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 9 Oct 2013 13:30:10 +0100 Subject: [PATCH 07/16] regmap: Provide asynchronous write and update bits operations Make it easier for drivers to include single register writes in asynchronous sequences by providing async versions of the write and update bits operations. The update bits operations are only likely to be effective when used with devices that have caches but this is common enough to be useful. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 103 +++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 31 +++++++++++ 2 files changed, 134 insertions(+) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 268fb71891ee..0503d868ff8c 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1337,6 +1337,37 @@ int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) } EXPORT_SYMBOL_GPL(regmap_write); +/** + * regmap_write_async(): Write a value to a single register asynchronously + * + * @map: Register map to write to + * @reg: Register 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_write_async(struct regmap *map, unsigned int reg, unsigned int val) +{ + int ret; + + if (reg % map->reg_stride) + return -EINVAL; + + map->lock(map->lock_arg); + + map->async = true; + + ret = _regmap_write(map, reg, val); + + map->async = false; + + map->unlock(map->lock_arg); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_write_async); + /** * regmap_raw_write(): Write raw values to one or more registers * @@ -1810,6 +1841,41 @@ int regmap_update_bits(struct regmap *map, unsigned int reg, } EXPORT_SYMBOL_GPL(regmap_update_bits); +/** + * regmap_update_bits_async: Perform a read/modify/write cycle on the register + * map asynchronously + * + * @map: Register map to update + * @reg: Register to update + * @mask: Bitmask to change + * @val: New value for bitmask + * + * With most buses the read must be done synchronously so this is most + * useful for devices with a cache which do not need to interact with + * the hardware to determine the current register value. + * + * Returns zero for success, a negative number on error. + */ +int regmap_update_bits_async(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val) +{ + bool change; + int ret; + + map->lock(map->lock_arg); + + map->async = true; + + ret = _regmap_update_bits(map, reg, mask, val, &change); + + map->async = false; + + map->unlock(map->lock_arg); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_update_bits_async); + /** * regmap_update_bits_check: Perform a read/modify/write cycle on the * register map and report if updated @@ -1835,6 +1901,43 @@ int regmap_update_bits_check(struct regmap *map, unsigned int reg, } EXPORT_SYMBOL_GPL(regmap_update_bits_check); +/** + * regmap_update_bits_check_async: Perform a read/modify/write cycle on the + * register map asynchronously and report if + * updated + * + * @map: Register map to update + * @reg: Register to update + * @mask: Bitmask to change + * @val: New value for bitmask + * @change: Boolean indicating if a write was done + * + * With most buses the read must be done synchronously so this is most + * useful for devices with a cache which do not need to interact with + * the hardware to determine the current register value. + * + * Returns zero for success, a negative number on error. + */ +int regmap_update_bits_check_async(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val, + bool *change) +{ + int ret; + + map->lock(map->lock_arg); + + map->async = true; + + ret = _regmap_update_bits(map, reg, mask, val, change); + + map->async = false; + + map->unlock(map->lock_arg); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_update_bits_check_async); + void regmap_async_complete_cb(struct regmap_async *async, int ret) { struct regmap *map = async->map; diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a10380bfbeac..114565befbd2 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -374,6 +374,7 @@ int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config); struct regmap *dev_get_regmap(struct device *dev, const char *name); int regmap_write(struct regmap *map, unsigned int reg, unsigned int val); +int regmap_write_async(struct regmap *map, unsigned int reg, unsigned int val); int regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len); int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, @@ -387,9 +388,14 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count); int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val); +int regmap_update_bits_async(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val); int regmap_update_bits_check(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val, bool *change); +int regmap_update_bits_check_async(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val, + bool *change); int regmap_get_val_bytes(struct regmap *map); int regmap_async_complete(struct regmap *map); bool regmap_can_raw_write(struct regmap *map); @@ -527,6 +533,13 @@ static inline int regmap_write(struct regmap *map, unsigned int reg, return -EINVAL; } +static inline int regmap_write_async(struct regmap *map, unsigned int reg, + unsigned int val) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline int regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len) { @@ -576,6 +589,14 @@ static inline int regmap_update_bits(struct regmap *map, unsigned int reg, return -EINVAL; } +static inline int regmap_update_bits_async(struct regmap *map, + unsigned int reg, + unsigned int mask, unsigned int val) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline int regmap_update_bits_check(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val, @@ -585,6 +606,16 @@ static inline int regmap_update_bits_check(struct regmap *map, return -EINVAL; } +static inline int regmap_update_bits_check_async(struct regmap *map, + unsigned int reg, + unsigned int mask, + unsigned int val, + bool *change) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline int regmap_get_val_bytes(struct regmap *map) { WARN_ONCE(1, "regmap API is disabled"); From 4174a7a4f763ed51809a4dbca06de054d31c1d38 Mon Sep 17 00:00:00 2001 From: Anthony Olech Date: Wed, 9 Oct 2013 17:44:38 +0100 Subject: [PATCH 08/16] regmap: Fix regmap_bulk_write single-rw mutex deadlock When regmap_bulk_write() is called with the map->use_single_rw flag set an immediate mutex deadlock happens because regmap_raw_write() is called after obtaining the mutex and regmap_raw_write() itself then tries to obtain the mutex as well. It is obvious that no one other than myself tried it with a real device. I did, but only for the purposes of an experiment and demonstration. But even if this situation will never ever happen with a real device, it is a bug and therefore should be fixed. Signed-off-by: Anthony Olech Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7d689a15c500..c1245cae0f43 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1418,10 +1418,11 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, */ if (map->use_single_rw) { for (i = 0; i < val_count; i++) { - ret = regmap_raw_write(map, - reg + (i * map->reg_stride), - val + (i * val_bytes), - val_bytes); + ret = _regmap_raw_write(map, + reg + (i * map->reg_stride), + val + (i * val_bytes), + val_bytes, + false); if (ret != 0) return ret; } From 1a25f26138cde2b83fd74ead6da0bbd4b6c42b60 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Oct 2013 20:55:03 +0100 Subject: [PATCH 09/16] regmap: Use async I/O for patch application Try to speed up patch application a little using async I/O. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 0503d868ff8c..71282e12fd29 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2029,6 +2029,7 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, bypass = map->cache_bypass; map->cache_bypass = true; + map->async = true; /* Write out first; it's useful to apply even if we fail later. */ for (i = 0; i < num_regs; i++) { @@ -2052,10 +2053,13 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, } out: + map->async = false; map->cache_bypass = bypass; map->unlock(map->lock_arg); + regmap_async_complete(map); + return ret; } EXPORT_SYMBOL_GPL(regmap_register_patch); From affbe886e712437c25b575eac5fde5886bb42aec Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Oct 2013 21:06:32 +0100 Subject: [PATCH 10/16] regmap: Use async I/O during cache sync Try to speed up I/O a little by not synchronising until we are finished scheduling writes. A brief survey of existing users suggests we have none that would currently benefit from an async cache sync. Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index a36112af494c..d4dd77134814 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -307,6 +307,8 @@ int regcache_sync(struct regmap *map) if (!map->cache_dirty) goto out; + map->async = true; + /* Apply any patch first */ map->cache_bypass = 1; for (i = 0; i < map->patch_regs; i++) { @@ -332,11 +334,15 @@ int regcache_sync(struct regmap *map) map->cache_dirty = false; out: - trace_regcache_sync(map->dev, name, "stop"); /* Restore the bypass state */ + map->async = false; map->cache_bypass = bypass; map->unlock(map->lock_arg); + regmap_async_complete(map); + + trace_regcache_sync(map->dev, name, "stop"); + return ret; } EXPORT_SYMBOL_GPL(regcache_sync); @@ -375,17 +381,23 @@ int regcache_sync_region(struct regmap *map, unsigned int min, if (!map->cache_dirty) goto out; + map->async = true; + 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"); /* Restore the bypass state */ map->cache_bypass = bypass; + map->async = false; map->unlock(map->lock_arg); + regmap_async_complete(map); + + trace_regcache_sync(map->dev, name, "stop region"); + return ret; } EXPORT_SYMBOL_GPL(regcache_sync_region); From e33fabd365596178e72f62bb4b89f0aaad0509ad Mon Sep 17 00:00:00 2001 From: Anthony Olech Date: Fri, 11 Oct 2013 15:31:11 +0100 Subject: [PATCH 11/16] regmap: new API regmap_multi_reg_write() definition New API regmap_multi_reg_write() is defined that allows a set of reg,val pairs to be written to a I2C client device as one block transfer from the point of view of a single I2C master system. A simple demonstration implementation is included that just splits the block write request into a sequence of single register writes. The implementation will be modified later to support those I2C clients that implement the alternative non-standard MULTIWRITE block write mode so to achieve a single I2C transfer that will be atomic even in multiple I2C master systems. Signed-off-by: Anthony Olech Signed-off-by: David Dajun Chen Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 41 ++++++++++++++++++++++++++++++++++++ include/linux/regmap.h | 2 ++ 2 files changed, 43 insertions(+) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7d689a15c500..c42ad69e527f 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1439,6 +1439,47 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, } EXPORT_SYMBOL_GPL(regmap_bulk_write); +/* + * regmap_multi_reg_write(): Write multiple registers to the device + * + * where the set of register are supplied in any order + * + * @map: Register map to write to + * @regs: Array of structures containing register,value to be written + * @num_regs: Number of registers to write + * + * This function is intended to be used for writing a large block of data + * atomically to the device in single transfer for those I2C client devices + * that implement this alternative block write mode. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_multi_reg_write(struct regmap *map, struct reg_default *regs, + int num_regs) +{ + int ret = 0, i; + + for (i = 0; i < num_regs; i++) { + int reg = regs[i].reg; + if (reg % map->reg_stride) + return -EINVAL; + } + + map->lock(map->lock_arg); + + for (i = 0; i < num_regs; i++) { + ret = _regmap_write(map, regs[i].reg, regs[i].def); + if (ret != 0) + goto out; + } +out: + map->unlock(map->lock_arg); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_multi_reg_write); + /** * regmap_raw_write_async(): Write raw values to one or more registers * asynchronously diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a10380bfbeac..4b933a31f84f 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -378,6 +378,8 @@ int regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len); int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count); +int regmap_multi_reg_write(struct regmap *map, struct reg_default *regs, + int num_regs); int regmap_raw_write_async(struct regmap *map, unsigned int reg, const void *val, size_t val_len); int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val); From cd1b9dd0220d3c126b3b61c1f96f0832fc21fc61 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Oct 2013 22:25:23 +0100 Subject: [PATCH 12/16] regmap: spi: Handle async writes of only one buffer If the value is zero then assume it has been included in the register data and don't send anything, minimising the number of interactions with the hardware. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-spi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c index 4c506bd940f3..37f12ae7aada 100644 --- a/drivers/base/regmap/regmap-spi.c +++ b/drivers/base/regmap/regmap-spi.c @@ -73,7 +73,8 @@ static int regmap_spi_async_write(void *context, spi_message_init(&async->m); spi_message_add_tail(&async->t[0], &async->m); - spi_message_add_tail(&async->t[1], &async->m); + if (val) + spi_message_add_tail(&async->t[1], &async->m); async->m.complete = regmap_spi_complete; async->m.context = async; From 04c50ccf0dab02923ef888a4839bfbd00de03181 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 10 Oct 2013 22:38:29 +0100 Subject: [PATCH 13/16] regmap: Only send a single buffer for async I/O if writing one register Extend the interface for async I/O by allowing the value buffer to be omitted and sending the value as part of the register buffer, minimising the number of separate hardware operations required. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index a975a601f718..d0ce2fef43a3 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1157,18 +1157,23 @@ int _regmap_raw_write(struct regmap *map, unsigned int reg, /* If the caller supplied the value we can use it safely. */ memcpy(async->work_buf, map->work_buf, map->format.pad_bytes + map->format.reg_bytes + map->format.val_bytes); - if (val == work_val) - val = async->work_buf + map->format.pad_bytes + - map->format.reg_bytes; spin_lock_irqsave(&map->async_lock, flags); list_add_tail(&async->list, &map->async_list); spin_unlock_irqrestore(&map->async_lock, flags); - ret = map->bus->async_write(map->bus_context, async->work_buf, - map->format.reg_bytes + - map->format.pad_bytes, - val, val_len, async); + if (val != work_val) + ret = map->bus->async_write(map->bus_context, + async->work_buf, + map->format.reg_bytes + + map->format.pad_bytes, + val, val_len, async); + else + ret = map->bus->async_write(map->bus_context, + async->work_buf, + map->format.reg_bytes + + map->format.pad_bytes + + val_len, NULL, 0, async); if (ret != 0) { dev_err(map->dev, "Failed to schedule write: %d\n", From 4bd7145b194af7cd96fc56d2ebee583b3edf03d3 Mon Sep 17 00:00:00 2001 From: Yi Zhang Date: Tue, 22 Oct 2013 18:44:32 +0800 Subject: [PATCH 14/16] regmap: irq: clear status when disable irq clear the status bit if the mask register doesn't prevent the chip level irq from being asserted OR in the following sequence, there will be irq storm happens: 1) interrupt is triggered; 2) another thread disables it(the mask bit is set); 3) _Then_ the interrupt thread is not ACKed(the status bit is not cleared), and it's ignored; 4) if the irq is still asserted because of the uncleared status bit, the irq storm happens; Signed-off-by: Yi Zhang Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-irq.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index d10456ffd811..763c60d3d277 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -105,6 +105,22 @@ static void regmap_irq_sync_unlock(struct irq_data *data) "Failed to sync wakes in %x: %d\n", reg, ret); } + + if (!d->chip->init_ack_masked) + continue; + /* + * Ack all the masked interrupts uncondictionly, + * OR if there is masked interrupt which hasn't been Acked, + * it'll be ignored in irq handler, then may introduce irq storm + */ + if (d->mask_buf[i] && d->chip->ack_base) { + reg = d->chip->ack_base + + (i * map->reg_stride * d->irq_reg_stride); + ret = regmap_write(map, reg, d->mask_buf[i]); + if (ret != 0) + dev_err(d->map->dev, "Failed to ack 0x%x: %d\n", + reg, ret); + } } if (d->chip->runtime_pm) From a52eaeb1898bc0589888ee62de291aa278379004 Mon Sep 17 00:00:00 2001 From: Tero Kristo Date: Thu, 24 Oct 2013 15:03:41 +0300 Subject: [PATCH 15/16] regmap: debugfs: Fix a boot time crash with early regmap init If called early enough, regmap_debugfs_init causes a crash, if the fs subsystem does not have its mount cache created yet. Even if this would work, the root node for the regmap debugfs is still missing, thus postpone the regmap_debugfs_init in this case until the root node is created. A special regmap_debugfs_early list is created for this purpose which is parsed later in the boot. Signed-off-by: Tero Kristo Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 57 +++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index de11ecaf3833..c5471cd6ebb7 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -15,10 +15,19 @@ #include #include #include +#include #include "internal.h" +struct regmap_debugfs_node { + struct regmap *map; + const char *name; + struct list_head link; +}; + static struct dentry *regmap_debugfs_root; +static LIST_HEAD(regmap_debugfs_early_list); +static DEFINE_MUTEX(regmap_debugfs_early_lock); /* Calculate the length of a fixed format */ static size_t regmap_calc_reg_len(int max_val, char *buf, size_t buf_size) @@ -465,6 +474,20 @@ void regmap_debugfs_init(struct regmap *map, const char *name) struct rb_node *next; struct regmap_range_node *range_node; + /* If we don't have the debugfs root yet, postpone init */ + if (!regmap_debugfs_root) { + struct regmap_debugfs_node *node; + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) + return; + node->map = map; + node->name = name; + mutex_lock(®map_debugfs_early_lock); + list_add(&node->link, ®map_debugfs_early_list); + mutex_unlock(®map_debugfs_early_lock); + return; + } + INIT_LIST_HEAD(&map->debugfs_off_cache); mutex_init(&map->cache_lock); @@ -519,18 +542,42 @@ void regmap_debugfs_init(struct regmap *map, const char *name) void regmap_debugfs_exit(struct regmap *map) { - debugfs_remove_recursive(map->debugfs); - mutex_lock(&map->cache_lock); - regmap_debugfs_free_dump_cache(map); - mutex_unlock(&map->cache_lock); - kfree(map->debugfs_name); + if (map->debugfs) { + debugfs_remove_recursive(map->debugfs); + mutex_lock(&map->cache_lock); + regmap_debugfs_free_dump_cache(map); + mutex_unlock(&map->cache_lock); + kfree(map->debugfs_name); + } else { + struct regmap_debugfs_node *node, *tmp; + + mutex_lock(®map_debugfs_early_lock); + list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, + link) { + if (node->map == map) { + list_del(&node->link); + kfree(node); + } + } + mutex_unlock(®map_debugfs_early_lock); + } } void regmap_debugfs_initcall(void) { + struct regmap_debugfs_node *node, *tmp; + regmap_debugfs_root = debugfs_create_dir("regmap", NULL); if (!regmap_debugfs_root) { pr_warn("regmap: Failed to create debugfs root\n"); return; } + + mutex_lock(®map_debugfs_early_lock); + list_for_each_entry_safe(node, tmp, ®map_debugfs_early_list, link) { + regmap_debugfs_init(node->map, node->name); + list_del(&node->link); + kfree(node); + } + mutex_unlock(®map_debugfs_early_lock); } From a01779f89fc8a2225cb82dca0fc7b8451851cb7b Mon Sep 17 00:00:00 2001 From: Josh Cartwright Date: Mon, 28 Oct 2013 13:12:35 -0500 Subject: [PATCH 16/16] regmap: add SPMI support Add basic support for the System Power Management Interface (SPMI) bus. This is a simple implementation which only implements register accesses via the Extended Register Read/Write Long commands. Signed-off-by: Josh Cartwright Signed-off-by: Mark Brown --- drivers/base/regmap/Kconfig | 5 +- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-spmi.c | 90 +++++++++++++++++++++++++++++++ include/linux/regmap.h | 5 ++ 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regmap-spmi.c diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index f0d30543fcce..4251570610c9 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -3,7 +3,7 @@ # subsystems should select the appropriate symbols. config REGMAP - default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_MMIO || REGMAP_IRQ) + default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_MMIO || REGMAP_IRQ) select LZO_COMPRESS select LZO_DECOMPRESS select IRQ_DOMAIN if REGMAP_IRQ @@ -15,6 +15,9 @@ config REGMAP_I2C config REGMAP_SPI tristate +config REGMAP_SPMI + tristate + config REGMAP_MMIO tristate diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index cf129980abd0..a7c670b4123a 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -3,5 +3,6 @@ obj-$(CONFIG_REGMAP) += regcache-rbtree.o regcache-lzo.o regcache-flat.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o +obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o diff --git a/drivers/base/regmap/regmap-spmi.c b/drivers/base/regmap/regmap-spmi.c new file mode 100644 index 000000000000..ac2391013db1 --- /dev/null +++ b/drivers/base/regmap/regmap-spmi.c @@ -0,0 +1,90 @@ +/* + * Register map access API - SPMI support + * + * Copyright (c) 2012-2013, The Linux Foundation. All rights reserved. + * + * Based on regmap-i2c.c: + * Copyright 2011 Wolfson Microelectronics plc + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include +#include +#include +#include + +static int regmap_spmi_read(void *context, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + BUG_ON(reg_size != 2); + return spmi_ext_register_readl(context, *(u16 *)reg, + val, val_size); +} + +static int regmap_spmi_gather_write(void *context, + const void *reg, size_t reg_size, + const void *val, size_t val_size) +{ + BUG_ON(reg_size != 2); + return spmi_ext_register_writel(context, *(u16 *)reg, val, val_size); +} + +static int regmap_spmi_write(void *context, const void *data, + size_t count) +{ + BUG_ON(count < 2); + return regmap_spmi_gather_write(context, data, 2, data + 2, count - 2); +} + +static struct regmap_bus regmap_spmi = { + .read = regmap_spmi_read, + .write = regmap_spmi_write, + .gather_write = regmap_spmi_gather_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +/** + * regmap_init_spmi(): Initialize register map + * + * @sdev: 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. + */ +struct regmap *regmap_init_spmi(struct spmi_device *sdev, + const struct regmap_config *config) +{ + return regmap_init(&sdev->dev, ®map_spmi, sdev, config); +} +EXPORT_SYMBOL_GPL(regmap_init_spmi); + +/** + * devm_regmap_init_spmi(): Initialise managed register map + * + * @sdev: 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. The regmap will be automatically freed by the + * device management code. + */ +struct regmap *devm_regmap_init_spmi(struct spmi_device *sdev, + const struct regmap_config *config) +{ + return devm_regmap_init(&sdev->dev, ®map_spmi, sdev, config); +} +EXPORT_SYMBOL_GPL(devm_regmap_init_spmi); + +MODULE_LICENSE("GPL"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index a10380bfbeac..3f5abc86b6b5 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -23,6 +23,7 @@ struct device; struct i2c_client; struct irq_domain; struct spi_device; +struct spmi_device; struct regmap; struct regmap_range_cfg; struct regmap_field; @@ -318,6 +319,8 @@ struct regmap *regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config); struct regmap *regmap_init_spi(struct spi_device *dev, const struct regmap_config *config); +struct regmap *regmap_init_spmi(struct spmi_device *dev, + const struct regmap_config *config); struct regmap *regmap_init_mmio_clk(struct device *dev, const char *clk_id, void __iomem *regs, const struct regmap_config *config); @@ -330,6 +333,8 @@ struct regmap *devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config); struct regmap *devm_regmap_init_spi(struct spi_device *dev, const struct regmap_config *config); +struct regmap *devm_regmap_init_spmi(struct spmi_device *dev, + const struct regmap_config *config); struct regmap *devm_regmap_init_mmio_clk(struct device *dev, const char *clk_id, void __iomem *regs, const struct regmap_config *config);