From 78ba73eecd2256790926859849801c0446766c0a Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Fri, 24 Jan 2014 15:40:39 -0800 Subject: regmap: cache: Handle stride > 1 in sync_block_raw_flush regcache_sync_block_raw_flush takes the address of the base register and the address of one past the last register to write to. "count" is the number of registers in the range, not the number of bytes, it should be (end addr - start addr) / stride. Without accounting for strides greater than one, registers past the end might be synced or the writeable_reg callback at the beginning of _regmap_raw_write will fail and nothing will be written. Signed-off-by: Dylan Reid Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index d4dd77134814..dd56177b7010 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -636,10 +636,10 @@ static int regcache_sync_block_raw_flush(struct regmap *map, const void **data, if (*data == NULL) return 0; - count = cur - base; + count = (cur - base) / map->reg_stride; dev_dbg(map->dev, "Writing %zu bytes for %d registers from 0x%x-0x%x\n", - count * val_bytes, count, base, cur - 1); + count * val_bytes, count, base, cur - map->reg_stride); map->cache_bypass = 1; -- cgit v1.2.3 From 676970da5cf6fec096945a8bbc19749089af57aa Mon Sep 17 00:00:00 2001 From: Pawel Moll Date: Thu, 30 Jan 2014 13:26:32 +0000 Subject: regmap: debugfs: "registers" & "access" for single register maps When a map covers a single register, max_register is equal to 0, so the "registers" & "access" files were not created. Now they will be, as register 0 must be readable for such map to make sense. Signed-off-by: Pawel Moll 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 c5471cd6ebb7..45d812c0ea77 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -511,7 +511,7 @@ void regmap_debugfs_init(struct regmap *map, const char *name) debugfs_create_file("range", 0400, map->debugfs, map, ®map_reg_ranges_fops); - if (map->max_register) { + if (map->max_register || regmap_readable(map, 0)) { debugfs_create_file("registers", 0400, map->debugfs, map, ®map_map_fops); debugfs_create_file("access", 0400, map->debugfs, -- cgit v1.2.3 From b5ab3e5cae097642480b7983c13ff9d3ea21d0de Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Jan 2014 20:25:48 +0000 Subject: regmap: irq: Remove domain on exit irqdomain now supports removal of domains on exit so we can properly clean up on deletion of a regmap irqchip. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-irq.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 82692068d3cb..52ce0c17c68b 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -533,7 +533,7 @@ void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *d) return; free_irq(irq, d); - /* We should unmap the domain but... */ + irq_domain_remove(d->domain); kfree(d->wake_buf); kfree(d->mask_buf_def); kfree(d->mask_buf); -- cgit v1.2.3 From d4807ad2c4c0e17b6f00e3be9492c81de0804f40 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 10 Feb 2014 12:59:46 +0100 Subject: regmap: Check readable regs in _regmap_read Check if regs are readable. Signed-off-by: Michal Simek Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6a19515f8a45..b897c1a88b56 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1736,6 +1736,9 @@ static int _regmap_read(struct regmap *map, unsigned int reg, if (map->cache_only) return -EBUSY; + if (!regmap_readable(map, reg)) + return -EIO; + ret = map->reg_read(context, reg, val); if (ret == 0) { #ifdef LOG_DEVICE -- cgit v1.2.3 From 6cfec04bcc05a829179c02584bb55f28fee03795 Mon Sep 17 00:00:00 2001 From: Michal Simek Date: Mon, 10 Feb 2014 16:22:33 +0100 Subject: regmap: Separate regmap dev initialization Create special function regmap_attach_dev which can be called separately out of regmap_init. Signed-off-by: Michal Simek Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 41 ++++++++++++++++++++++++++++------------- include/linux/regmap.h | 2 ++ 2 files changed, 30 insertions(+), 13 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6a19515f8a45..43065ceff90f 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -380,6 +380,28 @@ static void regmap_range_exit(struct regmap *map) kfree(map->selector_work_buf); } +int regmap_attach_dev(struct device *dev, struct regmap *map, + const struct regmap_config *config) +{ + struct regmap **m; + + map->dev = dev; + + regmap_debugfs_init(map, config->name); + + /* Add a devres resource for dev_get_regmap() */ + m = devres_alloc(dev_get_regmap_release, sizeof(*m), GFP_KERNEL); + if (!m) { + regmap_debugfs_exit(map); + return -ENOMEM; + } + *m = map; + devres_add(dev, m); + + return 0; +} +EXPORT_SYMBOL_GPL(regmap_attach_dev); + /** * regmap_init(): Initialise register map * @@ -397,7 +419,7 @@ struct regmap *regmap_init(struct device *dev, void *bus_context, const struct regmap_config *config) { - struct regmap *map, **m; + struct regmap *map; int ret = -EINVAL; enum regmap_endian reg_endian, val_endian; int i, j; @@ -734,25 +756,18 @@ skip_format_initialization: } } - regmap_debugfs_init(map, config->name); - ret = regcache_init(map, config); if (ret != 0) goto err_range; - /* Add a devres resource for dev_get_regmap() */ - m = devres_alloc(dev_get_regmap_release, sizeof(*m), GFP_KERNEL); - if (!m) { - ret = -ENOMEM; - goto err_debugfs; - } - *m = map; - devres_add(dev, m); + if (dev) + ret = regmap_attach_dev(dev, map, config); + if (ret != 0) + goto err_regcache; return map; -err_debugfs: - regmap_debugfs_exit(map); +err_regcache: regcache_exit(map); err_range: regmap_range_exit(map); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 4149f1a9b003..fa4d079fa44c 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -317,6 +317,8 @@ struct regmap *regmap_init(struct device *dev, const struct regmap_bus *bus, void *bus_context, const struct regmap_config *config); +int regmap_attach_dev(struct device *dev, struct regmap *map, + const struct regmap_config *config); struct regmap *regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config); struct regmap *regmap_init_spi(struct spi_device *dev, -- cgit v1.2.3 From e2f74dc6739731a6b6900946a21ac235181aa60f Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 20 Feb 2014 08:50:10 +0800 Subject: regmap: Clean up _regmap_update_bits() Since sometimes the 'config' parameter has no use, it should be NULL. And make the code simplifier. Signed-off-by: Xiubo Li Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index b897c1a88b56..9a426c081484 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1969,9 +1969,11 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg, if (tmp != orig) { ret = _regmap_write(map, reg, tmp); - *change = true; + if (change) + *change = true; } else { - *change = false; + if (change) + *change = false; } return ret; @@ -1990,11 +1992,10 @@ static int _regmap_update_bits(struct regmap *map, unsigned int reg, int regmap_update_bits(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val) { - bool change; int ret; map->lock(map->lock_arg); - ret = _regmap_update_bits(map, reg, mask, val, &change); + ret = _regmap_update_bits(map, reg, mask, val, NULL); map->unlock(map->lock_arg); return ret; @@ -2019,14 +2020,13 @@ EXPORT_SYMBOL_GPL(regmap_update_bits); 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); + ret = _regmap_update_bits(map, reg, mask, val, NULL); map->async = false; -- cgit v1.2.3 From 7a7a70b49b51e90593ae072a9402d6615d05e895 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 21 Feb 2014 19:37:10 +0000 Subject: regmap: Check stride of register patch as we register it Currently, we check the registers in the patch are aligned to the register stride everytime we sync the cache and the first time the patch is written out is unchecked. This patch checks the register patch when we first register it so the first writes are no longer unchecked and then doesn't check on subsequent syncs as the patch will be unchanged. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 4 ---- drivers/base/regmap/regmap.c | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index d4dd77134814..16426aa1457c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -312,10 +312,6 @@ int regcache_sync(struct regmap *map) /* Apply any patch first */ map->cache_bypass = 1; for (i = 0; i < map->patch_regs; i++) { - if (map->patch[i].reg % map->reg_stride) { - ret = -EINVAL; - goto out; - } ret = _regmap_write(map, map->patch[i].reg, map->patch[i].def); if (ret != 0) { dev_err(map->dev, "Failed to write %x = %x: %d\n", diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6a19515f8a45..6e19560c7a5b 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2194,6 +2194,10 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, /* Write out first; it's useful to apply even if we fail later. */ for (i = 0; i < num_regs; i++) { + if (regs[i].reg % map->reg_stride) { + ret = -EINVAL; + goto out; + } ret = _regmap_write(map, regs[i].reg, regs[i].def); if (ret != 0) { dev_err(map->dev, "Failed to write %x = %x: %d\n", -- cgit v1.2.3 From 53e87f88b14c011692ab2cb8c8abc994bdd28ae9 Mon Sep 17 00:00:00 2001 From: Nenghua Cao Date: Fri, 21 Feb 2014 16:05:45 +0800 Subject: regmap: fix coccinelle warnings /drivers/base/regmap/regmap.c:717:6-33: WARNING: Comparison to bool. More information about semantic patching is available at http://coccinelle.lip6.fr/ Signed-off-by: Nenghua Cao Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 9a426c081484..92d9b79ff93e 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -718,7 +718,7 @@ skip_format_initialization: new->window_start = range_cfg->window_start; new->window_len = range_cfg->window_len; - if (_regmap_range_add(map, new) == false) { + if (!_regmap_range_add(map, new)) { dev_err(map->dev, "Failed to add range %d\n", i); kfree(new); goto err_range; -- cgit v1.2.3 From f7e2cec02b0e5bfe2180f09de9b0bc724774c51a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 25 Feb 2014 13:45:49 +0000 Subject: regmap: Mark reg_defaults in regmap_multi_reg_write as const There should be no need for the writes supplied to this function to be edited by it so mark them as const. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 4 ++-- include/linux/regmap.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6e19560c7a5b..1e3934aea56a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1592,8 +1592,8 @@ EXPORT_SYMBOL_GPL(regmap_bulk_write); * 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 regmap_multi_reg_write(struct regmap *map, const struct reg_default *regs, + int num_regs) { int ret = 0, i; diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 4149f1a9b003..e97ac6c8b7be 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -386,7 +386,7 @@ 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 regmap_multi_reg_write(struct regmap *map, const 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); -- cgit v1.2.3 From 1d5b40bccf04994248b39e8ce234a7c1f3235cf5 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 25 Feb 2014 13:45:50 +0000 Subject: regmap: Add bypassed version of regmap_multi_reg_write Devices with more complex boot proceedures may occasionally apply the register patch manual. regmap_multi_reg_write is a logical way to do so, however the patch must be applied with cache bypass on, such that it doesn't override any user settings. This patch adds a regmap_multi_reg_write_bypassed function that applies a set of writes with the bypass enabled. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 75 ++++++++++++++++++++++++++++++++++++-------- include/linux/regmap.h | 3 ++ 2 files changed, 65 insertions(+), 13 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 1e3934aea56a..e6a2c29c0be4 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1576,6 +1576,26 @@ out: } EXPORT_SYMBOL_GPL(regmap_bulk_write); +static int _regmap_multi_reg_write(struct regmap *map, + const struct reg_default *regs, + int num_regs) +{ + int i, ret; + + for (i = 0; i < num_regs; i++) { + if (regs[i].reg % map->reg_stride) + return -EINVAL; + ret = _regmap_write(map, regs[i].reg, regs[i].def); + if (ret != 0) { + dev_err(map->dev, "Failed to write %x = %x: %d\n", + regs[i].reg, regs[i].def, ret); + return ret; + } + } + + return 0; +} + /* * regmap_multi_reg_write(): Write multiple registers to the device * @@ -1595,28 +1615,57 @@ EXPORT_SYMBOL_GPL(regmap_bulk_write); int regmap_multi_reg_write(struct regmap *map, const 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; - } + int ret; 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: + ret = _regmap_multi_reg_write(map, regs, num_regs); + map->unlock(map->lock_arg); return ret; } EXPORT_SYMBOL_GPL(regmap_multi_reg_write); +/* + * regmap_multi_reg_write_bypassed(): Write multiple registers to the + * device but not the cache + * + * 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_bypassed(struct regmap *map, + const struct reg_default *regs, + int num_regs) +{ + int ret; + bool bypass; + + map->lock(map->lock_arg); + + bypass = map->cache_bypass; + map->cache_bypass = true; + + ret = _regmap_multi_reg_write(map, regs, num_regs); + + map->cache_bypass = bypass; + + map->unlock(map->lock_arg); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_multi_reg_write_bypassed); + /** * 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 e97ac6c8b7be..ca2272fbd014 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -388,6 +388,9 @@ 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, const struct reg_default *regs, int num_regs); +int regmap_multi_reg_write_bypassed(struct regmap *map, + const 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); -- cgit v1.2.3 From 6bf13103b2dbf09d32a0da2e732b6196522c1462 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 25 Feb 2014 13:45:51 +0000 Subject: regmap: Base regmap_register_patch on _regmap_multi_reg_write Since we now have an internal version of regmap_multi_reg_write use this to apply the register patch. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e6a2c29c0be4..35ab7baffcc5 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2227,7 +2227,7 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, int num_regs) { struct reg_default *p; - int i, ret; + int ret; bool bypass; if (WARN_ONCE(num_regs <= 0, "invalid registers number (%d)\n", @@ -2241,19 +2241,9 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, 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++) { - if (regs[i].reg % map->reg_stride) { - ret = -EINVAL; - goto out; - } - ret = _regmap_write(map, regs[i].reg, regs[i].def); - if (ret != 0) { - dev_err(map->dev, "Failed to write %x = %x: %d\n", - regs[i].reg, regs[i].def, ret); - goto out; - } - } + ret = _regmap_multi_reg_write(map, regs, num_regs); + if (ret != 0) + goto out; p = krealloc(map->patch, sizeof(struct reg_default) * (map->patch_regs + num_regs), -- cgit v1.2.3 From 13ff50c85846338bb9820abd3933227b678dc086 Mon Sep 17 00:00:00 2001 From: Nenghua Cao Date: Wed, 19 Feb 2014 18:44:13 +0800 Subject: regmap: add regmap_parse_val api In some cases, we need regmap's format parse_val function to do be/le translation according to the bus configuration. For example, snd_soc_bytes_put() uses regmap to write/read values, and use cpu_to_be() directly to covert MASK into big endian. This is a defect, and should use regmap's format function to do it according to bus configuration. Signed-off-by: Nenghua Cao Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 12 ++++++++++++ include/linux/regmap.h | 9 +++++++++ 2 files changed, 21 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6a19515f8a45..4b2ed0c9e80d 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2240,6 +2240,18 @@ int regmap_get_val_bytes(struct regmap *map) } EXPORT_SYMBOL_GPL(regmap_get_val_bytes); +int regmap_parse_val(struct regmap *map, const void *buf, + unsigned int *val) +{ + if (!map->format.parse_val) + return -EINVAL; + + *val = map->format.parse_val(buf); + + return 0; +} +EXPORT_SYMBOL_GPL(regmap_parse_val); + static int __init regmap_initcall(void) { regmap_debugfs_initcall(); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 4149f1a9b003..3e1a2e4a92ad 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -423,6 +423,8 @@ bool regmap_check_range_table(struct regmap *map, unsigned int reg, int regmap_register_patch(struct regmap *map, const struct reg_default *regs, int num_regs); +int regmap_parse_val(struct regmap *map, const void *buf, + unsigned int *val); static inline bool regmap_reg_in_range(unsigned int reg, const struct regmap_range *range) @@ -695,6 +697,13 @@ static inline int regmap_register_patch(struct regmap *map, return -EINVAL; } +static inline int regmap_parse_val(struct regmap *map, const void *buf, + unsigned int *val) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline struct regmap *dev_get_regmap(struct device *dev, const char *name) { -- cgit v1.2.3 From e894c3f46c302716d2f156b1f3339e2f96ceb65c Mon Sep 17 00:00:00 2001 From: "Opensource [Anthony Olech]" Date: Tue, 4 Mar 2014 13:54:02 +0000 Subject: regmap: Implementation for regmap_multi_reg_write This is the implementation of regmap_multi_reg_write() There is a new capability 'can_multi_write' that device drivers must set in order to use this multi reg write mode. This replaces the first definition, which just defined the API. Signed-off-by: Anthony Olech Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 2 + drivers/base/regmap/regmap.c | 188 +++++++++++++++++++++++++++++++++++++---- include/linux/regmap.h | 4 + 3 files changed, 178 insertions(+), 16 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 33414b1de201..7d1326985bee 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -134,6 +134,8 @@ struct regmap { /* if set, converts bulk rw to single rw */ bool use_single_rw; + /* if set, the device supports multi write mode */ + bool can_multi_write; struct rb_root range_tree; void *selector_work_buf; /* Scratch buffer used for selector */ diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 35ab7baffcc5..c69bbc06dfbb 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -439,6 +439,7 @@ struct regmap *regmap_init(struct device *dev, else map->reg_stride = 1; map->use_single_rw = config->use_single_rw; + map->can_multi_write = config->can_multi_write; map->dev = dev; map->bus = bus; map->bus_context = bus_context; @@ -1576,41 +1577,196 @@ out: } EXPORT_SYMBOL_GPL(regmap_bulk_write); +/* + * _regmap_raw_multi_reg_write() + * + * the (register,newvalue) pairs in regs have not been formatted, but + * they are all in the same page and have been changed to being page + * relative. The page register has been written if that was neccessary. + */ +static int _regmap_raw_multi_reg_write(struct regmap *map, + const struct reg_default *regs, + size_t num_regs) +{ + int ret; + void *buf; + int i; + u8 *u8; + size_t val_bytes = map->format.val_bytes; + size_t reg_bytes = map->format.reg_bytes; + size_t pad_bytes = map->format.pad_bytes; + size_t pair_size = reg_bytes + pad_bytes + val_bytes; + size_t len = pair_size * num_regs; + + buf = kzalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* We have to linearise by hand. */ + + u8 = buf; + + for (i = 0; i < num_regs; i++) { + int reg = regs[i].reg; + int val = regs[i].def; + trace_regmap_hw_write_start(map->dev, reg, 1); + map->format.format_reg(u8, reg, map->reg_shift); + u8 += reg_bytes + pad_bytes; + map->format.format_val(u8, val, 0); + u8 += val_bytes; + } + u8 = buf; + *u8 |= map->write_flag_mask; + + ret = map->bus->write(map->bus_context, buf, len); + + kfree(buf); + + for (i = 0; i < num_regs; i++) { + int reg = regs[i].reg; + trace_regmap_hw_write_done(map->dev, reg, 1); + } + return ret; +} + +static unsigned int _regmap_register_page(struct regmap *map, + unsigned int reg, + struct regmap_range_node *range) +{ + unsigned int win_page = (reg - range->range_min) / range->window_len; + + return win_page; +} + +static int _regmap_range_multi_paged_reg_write(struct regmap *map, + struct reg_default *regs, + size_t num_regs) +{ + int ret; + int i, n; + struct reg_default *base; + unsigned int this_page; + /* + * the set of registers are not neccessarily in order, but + * since the order of write must be preserved this algorithm + * chops the set each time the page changes + */ + base = regs; + for (i = 0, n = 0; i < num_regs; i++, n++) { + unsigned int reg = regs[i].reg; + struct regmap_range_node *range; + + range = _regmap_range_lookup(map, reg); + if (range) { + unsigned int win_page = _regmap_register_page(map, reg, + range); + + if (i == 0) + this_page = win_page; + if (win_page != this_page) { + this_page = win_page; + ret = _regmap_raw_multi_reg_write(map, base, n); + if (ret != 0) + return ret; + base += n; + n = 0; + } + ret = _regmap_select_page(map, &base[n].reg, range, 1); + if (ret != 0) + return ret; + } + } + if (n > 0) + return _regmap_raw_multi_reg_write(map, base, n); + return 0; +} + static int _regmap_multi_reg_write(struct regmap *map, const struct reg_default *regs, - int num_regs) + size_t num_regs) { - int i, ret; + int i; + int ret; + + if (!map->can_multi_write) { + for (i = 0; i < num_regs; i++) { + ret = _regmap_write(map, regs[i].reg, regs[i].def); + if (ret != 0) + return ret; + } + return 0; + } + + if (!map->format.parse_inplace) + return -EINVAL; + + if (map->writeable_reg) + for (i = 0; i < num_regs; i++) { + int reg = regs[i].reg; + if (!map->writeable_reg(map->dev, reg)) + return -EINVAL; + if (reg % map->reg_stride) + return -EINVAL; + } + + if (!map->cache_bypass) { + for (i = 0; i < num_regs; i++) { + unsigned int val = regs[i].def; + unsigned int reg = regs[i].reg; + ret = regcache_write(map, reg, val); + if (ret) { + dev_err(map->dev, + "Error in caching of register: %x ret: %d\n", + reg, ret); + return ret; + } + } + if (map->cache_only) { + map->cache_dirty = true; + return 0; + } + } + + WARN_ON(!map->bus); for (i = 0; i < num_regs; i++) { - if (regs[i].reg % map->reg_stride) - return -EINVAL; - ret = _regmap_write(map, regs[i].reg, regs[i].def); - if (ret != 0) { - dev_err(map->dev, "Failed to write %x = %x: %d\n", - regs[i].reg, regs[i].def, ret); + unsigned int reg = regs[i].reg; + struct regmap_range_node *range; + range = _regmap_range_lookup(map, reg); + if (range) { + size_t len = sizeof(struct reg_default)*num_regs; + struct reg_default *base = kmemdup(regs, len, + GFP_KERNEL); + if (!base) + return -ENOMEM; + ret = _regmap_range_multi_paged_reg_write(map, base, + num_regs); + kfree(base); + return ret; } } - - return 0; + return _regmap_raw_multi_reg_write(map, regs, num_regs); } /* * regmap_multi_reg_write(): Write multiple registers to the device * - * where the set of register are supplied in any order + * where the set of register,value pairs are supplied in any order, + * possibly not all in a single range. * * @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. + * The 'normal' block write mode will send ultimately send data on the + * target bus as R,V1,V2,V3,..,Vn where successively higer registers are + * addressed. However, this alternative block multi write mode will send + * the data as R1,V1,R2,V2,..,Rn,Vn on the target bus. The target device + * must of course support the mode. * - * A value of zero will be returned on success, a negative errno will - * be returned in error cases. + * 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, const struct reg_default *regs, int num_regs) diff --git a/include/linux/regmap.h b/include/linux/regmap.h index ca2272fbd014..220aec660064 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -164,6 +164,9 @@ typedef void (*regmap_unlock)(void *); * @use_single_rw: If set, converts the bulk read and write operations into * a series of single read and write operations. This is useful * for device that does not support bulk read and write. + * @can_multi_write: If set, the device supports the multi write mode of bulk + * write operations, if clear multi write requests will be + * split into individual write operations * * @cache_type: The actual cache type. * @reg_defaults_raw: Power on reset values for registers (for use with @@ -215,6 +218,7 @@ struct regmap_config { u8 write_flag_mask; bool use_single_rw; + bool can_multi_write; enum regmap_endian reg_format_endian; enum regmap_endian val_format_endian; -- cgit v1.2.3 From 72a6a5df2c6c06e1a2504a6272d722e435a93bcb Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Thu, 13 Mar 2014 09:06:01 +0100 Subject: regmap: irq: Set data pointer only on regmap_add_irq_chip success After setting the 'data' pointer (wchich is returned to the caller for freeing later) the regmap_add_irq_chip() could still fail for various reasons (ENOMEM, regmap_read or regmap_write failure). In such case the memory under 'data' was freed in error path and error value was returned but the 'data' variable was not changed. This could lead to errors if the caller passed such 'data' to regmap_del_irq_chip(). The 'data' pointer should be changed atomically from the caller perspective - set it only on regmap_add_irq_chip() success. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-irq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-irq.c b/drivers/base/regmap/regmap-irq.c index 52ce0c17c68b..edf88f20cbce 100644 --- a/drivers/base/regmap/regmap-irq.c +++ b/drivers/base/regmap/regmap-irq.c @@ -368,8 +368,6 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, if (!d) return -ENOMEM; - *data = d; - d->status_buf = kzalloc(sizeof(unsigned int) * chip->num_regs, GFP_KERNEL); if (!d->status_buf) @@ -506,6 +504,8 @@ int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags, goto err_domain; } + *data = d; + return 0; err_domain: -- cgit v1.2.3 From 56fb1c74f3bda1c0100fc3e9a7888c229174f9a4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 18 Mar 2014 10:53:26 +0000 Subject: regmap: Ensure regmap_register_patch() is compatible with fast_io With fast_io we use mutexes to lock the I/O operations so we would need to do GFP_ATOMIC allocations if we wanted to do allocations inside the lock as we do currently. Since it is unlikely that we will want to register a patch outside of init where concurrency shouldn't be an issue move the allocation of the patch data outside the lock. Reported-by: Takashi Iwai Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 6a19515f8a45..e5a5509160fe 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -2173,6 +2173,9 @@ EXPORT_SYMBOL_GPL(regmap_async_complete); * apply them immediately. Typically this is used to apply * corrections to be applied to the device defaults on startup, such * as the updates some vendors provide to undocumented registers. + * + * The caller must ensure that this function cannot be called + * concurrently with either itself or regcache_sync(). */ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, int num_regs) @@ -2185,6 +2188,17 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, num_regs)) return 0; + p = krealloc(map->patch, + sizeof(struct reg_default) * (map->patch_regs + num_regs), + GFP_KERNEL); + if (p) { + memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); + map->patch = p; + map->patch_regs += num_regs; + } else { + return -ENOMEM; + } + map->lock(map->lock_arg); bypass = map->cache_bypass; @@ -2202,17 +2216,6 @@ int regmap_register_patch(struct regmap *map, const struct reg_default *regs, } } - p = krealloc(map->patch, - sizeof(struct reg_default) * (map->patch_regs + num_regs), - GFP_KERNEL); - if (p) { - memcpy(p + map->patch_regs, regs, num_regs * sizeof(*regs)); - map->patch = p; - map->patch_regs += num_regs; - } else { - ret = -ENOMEM; - } - out: map->async = false; map->cache_bypass = bypass; -- cgit v1.2.3 From 4999e9621a58fa03fe18aa2ea55838bd2e755190 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 18 Mar 2014 12:58:33 +0100 Subject: regmap: Fix possible sleep-in-atomic in regmap_bulk_write() regmap deploys the spinlock for the protection when set up in fast_io mode. This may lead to sleep-in-atomic by memory allocation with GFP_KERNEL in regmap_bulk_write(). This patch fixes it by moving the allocation out of the lock. [Fix excessively large locked region -- broonie] Signed-off-by: Takashi Iwai Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e5a5509160fe..35077374f38b 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1520,12 +1520,12 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, if (reg % map->reg_stride) return -EINVAL; - map->lock(map->lock_arg); /* * Some devices don't support bulk write, for * them we have a series of single write operations. */ if (!map->bus || map->use_single_rw) { + map->lock(map->lock_arg); for (i = 0; i < val_count; i++) { unsigned int ival; @@ -1554,24 +1554,25 @@ int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, if (ret != 0) goto out; } +out: + map->unlock(map->lock_arg); } else { void *wval; wval = kmemdup(val, val_count * val_bytes, GFP_KERNEL); if (!wval) { - ret = -ENOMEM; dev_err(map->dev, "Error in memory allocation\n"); - goto out; + return -ENOMEM; } for (i = 0; i < val_count * val_bytes; i += val_bytes) map->format.parse_inplace(wval + i); + map->lock(map->lock_arg); ret = _regmap_raw_write(map, reg, wval, val_bytes * val_count); + map->unlock(map->lock_arg); kfree(wval); } -out: - map->unlock(map->lock_arg); return ret; } EXPORT_SYMBOL_GPL(regmap_bulk_write); -- cgit v1.2.3 From 756173285e87c792c6fa8eaaaf1217cfcf1416dd Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Tue, 18 Mar 2014 13:45:08 -0700 Subject: regmap: cache: Step by stride in default sync The default sync operation was still assuming a stride of one, fix it to respect the reg_stride set in the map. Signed-off-by: Dylan Reid Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index d4dd77134814..bb3ba42e0329 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -249,7 +249,7 @@ static int regcache_default_sync(struct regmap *map, unsigned int min, { unsigned int reg; - for (reg = min; reg <= max; reg++) { + for (reg = min; reg <= max; reg += map->reg_stride) { unsigned int val; int ret; -- cgit v1.2.3 From 83f8475ce99fa1c44b03059b6cc5dcaae69b4819 Mon Sep 17 00:00:00 2001 From: Dylan Reid Date: Tue, 18 Mar 2014 13:45:09 -0700 Subject: regmap: cache: Don't attempt to sync non-writeable registers In the regcache_default_sync, if a register isn't writeable, then _regmap_write will return an error and the rest of the sync will be aborted. Avoid this by checking if a register is writeable before trying to sync it. Signed-off-by: Dylan Reid Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index bb3ba42e0329..a9d8d7be6aa3 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -253,7 +253,8 @@ static int regcache_default_sync(struct regmap *map, unsigned int min, unsigned int val; int ret; - if (regmap_volatile(map, reg)) + if (regmap_volatile(map, reg) || + !regmap_writeable(map, reg)) continue; ret = regcache_read(map, reg, &val); -- cgit v1.2.3 From 41b0c2c976a8758a2b7f5b14cbc5d1a7436932cc Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 27 Mar 2014 12:42:42 +0800 Subject: regmap: mmio: add regmap_mmio_{regsize, count}_check. Signed-off-by: Xiubo Li Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-mmio.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index 81f977510775..4f1efce94034 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -30,6 +30,16 @@ struct regmap_mmio_context { struct clk *clk; }; +static inline void regmap_mmio_regsize_check(size_t reg_size) +{ + BUG_ON(reg_size != 4); +} + +static inline void regmap_mmio_count_check(size_t count) +{ + BUG_ON(count < 4); +} + static int regmap_mmio_gather_write(void *context, const void *reg, size_t reg_size, const void *val, size_t val_size) @@ -38,7 +48,7 @@ static int regmap_mmio_gather_write(void *context, u32 offset; int ret; - BUG_ON(reg_size != 4); + regmap_mmio_regsize_check(reg_size); if (!IS_ERR(ctx->clk)) { ret = clk_enable(ctx->clk); @@ -81,7 +91,7 @@ static int regmap_mmio_gather_write(void *context, static int regmap_mmio_write(void *context, const void *data, size_t count) { - BUG_ON(count < 4); + regmap_mmio_count_check(count); return regmap_mmio_gather_write(context, data, 4, data + 4, count - 4); } @@ -94,7 +104,7 @@ static int regmap_mmio_read(void *context, u32 offset; int ret; - BUG_ON(reg_size != 4); + regmap_mmio_regsize_check(reg_size); if (!IS_ERR(ctx->clk)) { ret = clk_enable(ctx->clk); -- cgit v1.2.3 From 932580409a9dacbf42215fa737bf06ae2c0aa624 Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Thu, 27 Mar 2014 12:42:43 +0800 Subject: regmap: mmio: Add support for 1/2/8 bytes wide register address. Since regmap core and mmio have already support for 1/2/8 bytes wide values, so adds support for 1/2/8 bytes wide registers address. Signed-off-by: Xiubo Li Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-mmio.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index 4f1efce94034..ed080a47b1f8 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -26,18 +26,30 @@ struct regmap_mmio_context { void __iomem *regs; + unsigned reg_bytes; unsigned val_bytes; + unsigned pad_bytes; struct clk *clk; }; static inline void regmap_mmio_regsize_check(size_t reg_size) { - BUG_ON(reg_size != 4); + switch (reg_size) { + case 1: + case 2: + case 4: +#ifdef CONFIG_64BIT + case 8: +#endif + break; + default: + BUG(); + } } static inline void regmap_mmio_count_check(size_t count) { - BUG_ON(count < 4); + BUG_ON(count % 2 != 0); } static int regmap_mmio_gather_write(void *context, @@ -91,9 +103,13 @@ static int regmap_mmio_gather_write(void *context, static int regmap_mmio_write(void *context, const void *data, size_t count) { + struct regmap_mmio_context *ctx = context; + u32 offset = ctx->reg_bytes + ctx->pad_bytes; + regmap_mmio_count_check(count); - return regmap_mmio_gather_write(context, data, 4, data + 4, count - 4); + return regmap_mmio_gather_write(context, data, ctx->reg_bytes, + data + offset, count - offset); } static int regmap_mmio_read(void *context, @@ -219,6 +235,8 @@ static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, ctx->regs = regs; ctx->val_bytes = config->val_bits / 8; + ctx->reg_bytes = config->reg_bits / 8; + ctx->pad_bytes = config->pad_bits / 8; ctx->clk = ERR_PTR(-ENODEV); if (clk_id == NULL) -- cgit v1.2.3 From 451485ba6bfbed36220b9e710fca0525f62e771d Mon Sep 17 00:00:00 2001 From: Xiubo Li Date: Fri, 28 Mar 2014 13:12:56 +0800 Subject: regmap: mmio: Add regmap_mmio_regbits_check. Fix the support for 1/2/8 bytes wide register address checking. Signed-off-by: Xiubo Li Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-mmio.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-mmio.c b/drivers/base/regmap/regmap-mmio.c index ed080a47b1f8..de45a1e1548f 100644 --- a/drivers/base/regmap/regmap-mmio.c +++ b/drivers/base/regmap/regmap-mmio.c @@ -47,6 +47,21 @@ static inline void regmap_mmio_regsize_check(size_t reg_size) } } +static int regmap_mmio_regbits_check(size_t reg_bits) +{ + switch (reg_bits) { + case 8: + case 16: + case 32: +#ifdef CONFIG_64BIT + case 64: +#endif + return 0; + default: + return -EINVAL; + } +} + static inline void regmap_mmio_count_check(size_t count) { BUG_ON(count % 2 != 0); @@ -191,8 +206,9 @@ static struct regmap_mmio_context *regmap_mmio_gen_context(struct device *dev, int min_stride; int ret; - if (config->reg_bits != 32) - return ERR_PTR(-EINVAL); + ret = regmap_mmio_regbits_check(config->reg_bits); + if (ret) + return ERR_PTR(ret); if (config->pad_bits) return ERR_PTR(-EINVAL); -- cgit v1.2.3