From 2e2ae66df37a14c9b33889b243b0ae1352ada1dd Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 20 Jul 2011 22:33:39 +0100 Subject: regmap: Allow devices to specify which registers are accessible This is currently unused but we need to know which registers exist and their properties in order to implement diagnostics like register map dumps and the cache features. We use callbacks partly because properties can vary at runtime (eg, through access locks on registers) and partly because big switch statements are a good compromise between readable code and small data size for providing information on big register maps. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index cf3565cae93d..2fa55c56897a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -37,6 +37,11 @@ struct regmap { void *work_buf; /* Scratch buffer used to format I/O */ struct regmap_format format; /* Buffer format */ const struct regmap_bus *bus; + + unsigned int max_register; + bool (*writeable_reg)(struct device *dev, unsigned int reg); + bool (*readable_reg)(struct device *dev, unsigned int reg); + bool (*volatile_reg)(struct device *dev, unsigned int reg); }; static void regmap_format_4_12_write(struct regmap *map, @@ -116,6 +121,10 @@ struct regmap *regmap_init(struct device *dev, map->format.val_bytes = config->val_bits / 8; map->dev = dev; map->bus = bus; + map->max_register = config->max_register; + map->writeable_reg = config->writeable_reg; + map->readable_reg = config->readable_reg; + map->volatile_reg = config->volatile_reg; switch (config->reg_bits) { case 4: -- cgit v1.2.1 From 2547e201b3693f91d643fc0d21ef86171894b59b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 20 Jul 2011 21:47:22 +0100 Subject: regmap: Just send the buffer directly for single register writes When doing a single register write we use work_buf for both the register and the value with the buffer formatted for sending directly to the device so we can just do a write() directly. This saves allocating a temporary buffer if we can't do gather writes and is likely to be faster than doing a gather write. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index cf3565cae93d..6aa2c4b9a65a 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -202,13 +202,19 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, map->format.format_reg(map->work_buf, reg); - /* Try to do a gather write if we can */ - if (map->bus->gather_write) + /* If we're doing a single register write we can probably just + * send the work_buf directly, otherwise try to do a gather + * write. + */ + if (val == map->work_buf + map->format.reg_bytes) + ret = map->bus->write(map->dev, map->work_buf, + map->format.reg_bytes + val_len); + else if (map->bus->gather_write) ret = map->bus->gather_write(map->dev, map->work_buf, map->format.reg_bytes, val, val_len); - /* Otherwise fall back on linearising by hand. */ + /* If that didn't work fall back on linearising by hand. */ if (ret == -ENOTSUPP) { len = map->format.reg_bytes + val_len; buf = kmalloc(len, GFP_KERNEL); -- cgit v1.2.1 From fb2736bbaee0e704a4f33912cf532597b2dc5b33 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 24 Jul 2011 21:30:55 +0100 Subject: regmap: Add basic tracepoints Trace single register reads and writes, plus start/stop tracepoints for the actual I/O to see where we're spending time. This makes it easy to have always on logging without overwhelming the logs and also lets us take advantage of all the context and time information that the trace subsystem collects for us. We don't currently trace register values for bulk operations as this would add complexity and overhead parsing the cooked data that's being worked with. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 02ed1546da21..7d4dc11ad86c 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -17,6 +17,9 @@ #include +#define CREATE_TRACE_POINTS +#include + struct regmap; struct regmap_format { @@ -211,6 +214,9 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, map->format.format_reg(map->work_buf, reg); + trace_regmap_hw_write_start(map->dev, reg, + val_len / map->format.val_bytes); + /* If we're doing a single register write we can probably just * send the work_buf directly, otherwise try to do a gather * write. @@ -237,19 +243,31 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, kfree(buf); } + trace_regmap_hw_write_done(map->dev, reg, + val_len / map->format.val_bytes); + return ret; } static int _regmap_write(struct regmap *map, unsigned int reg, unsigned int val) { + int ret; BUG_ON(!map->format.format_write && !map->format.format_val); + trace_regmap_reg_write(map->dev, reg, val); + if (map->format.format_write) { map->format.format_write(map, reg, val); - return map->bus->write(map->dev, map->work_buf, - map->format.buf_size); + trace_regmap_hw_write_start(map->dev, reg, 1); + + ret = map->bus->write(map->dev, map->work_buf, + map->format.buf_size); + + trace_regmap_hw_write_done(map->dev, reg, 1); + + return ret; } else { map->format.format_val(map->work_buf + map->format.reg_bytes, val); @@ -331,12 +349,16 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, if (map->bus->read_flag_mask) u8[0] |= map->bus->read_flag_mask; + trace_regmap_hw_read_start(map->dev, reg, + val_len / map->format.val_bytes); + ret = map->bus->read(map->dev, map->work_buf, map->format.reg_bytes, val, val_len); - if (ret != 0) - return ret; - return 0; + trace_regmap_hw_read_done(map->dev, reg, + val_len / map->format.val_bytes); + + return ret; } static int _regmap_read(struct regmap *map, unsigned int reg, @@ -348,8 +370,10 @@ static int _regmap_read(struct regmap *map, unsigned int reg, return -EINVAL; ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes); - if (ret == 0) + if (ret == 0) { *val = map->format.parse_val(map->work_buf); + trace_regmap_reg_read(map->dev, reg, *val); + } return ret; } -- cgit v1.2.1 From 73304781274200c341996f65220d36b3cda8e217 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 24 Jul 2011 11:46:20 +0100 Subject: regmap: Implement writable register checks This is mainly intended to be used by devices which can dynamically block register writes at runtime, for other devices there is usually limited value. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 7d4dc11ad86c..e57f10f485a1 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -211,6 +211,13 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, void *buf; int ret = -ENOTSUPP; size_t len; + int i; + + /* Check for unwritable registers before we start */ + if (map->writeable_reg) + for (i = 0; i < val_len / map->format.val_bytes; i++) + if (!map->writeable_reg(map->dev, reg + i)) + return -EINVAL; map->format.format_reg(map->work_buf, reg); -- cgit v1.2.1 From 93de91245b66f20dd387c2745744950a11a5c436 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 20 Jul 2011 22:35:37 +0100 Subject: regmap: Use a local header for API internals Allowing the implementation to be multi-file. Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 45 ++++++++++++++++++++++++++++++++++++++++++ drivers/base/regmap/regmap.c | 29 +-------------------------- 2 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 drivers/base/regmap/internal.h (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h new file mode 100644 index 000000000000..7e61504a7ac3 --- /dev/null +++ b/drivers/base/regmap/internal.h @@ -0,0 +1,45 @@ +/* + * Register map access API internal header + * + * 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 as + * published by the Free Software Foundation. + */ + +#ifndef _REGMAP_INTERNAL_H +#define _REGMAP_INTERNAL_H + +#include + +struct regmap; + +struct regmap_format { + size_t buf_size; + size_t reg_bytes; + size_t val_bytes; + void (*format_write)(struct regmap *map, + unsigned int reg, unsigned int val); + void (*format_reg)(void *buf, unsigned int reg); + void (*format_val)(void *buf, unsigned int val); + unsigned int (*parse_val)(void *buf); +}; + +struct regmap { + struct mutex lock; + + struct device *dev; /* Device we do I/O on */ + void *work_buf; /* Scratch buffer used to format I/O */ + struct regmap_format format; /* Buffer format */ + const struct regmap_bus *bus; + + unsigned int max_register; + bool (*writeable_reg)(struct device *dev, unsigned int reg); + bool (*readable_reg)(struct device *dev, unsigned int reg); + bool (*volatile_reg)(struct device *dev, unsigned int reg); +}; + +#endif diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e57f10f485a1..f51efeb091c5 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -15,37 +15,10 @@ #include #include -#include - #define CREATE_TRACE_POINTS #include -struct regmap; - -struct regmap_format { - size_t buf_size; - size_t reg_bytes; - size_t val_bytes; - void (*format_write)(struct regmap *map, - unsigned int reg, unsigned int val); - void (*format_reg)(void *buf, unsigned int reg); - void (*format_val)(void *buf, unsigned int val); - unsigned int (*parse_val)(void *buf); -}; - -struct regmap { - struct mutex lock; - - struct device *dev; /* Device we do I/O on */ - void *work_buf; /* Scratch buffer used to format I/O */ - struct regmap_format format; /* Buffer format */ - const struct regmap_bus *bus; - - unsigned int max_register; - bool (*writeable_reg)(struct device *dev, unsigned int reg); - bool (*readable_reg)(struct device *dev, unsigned int reg); - bool (*volatile_reg)(struct device *dev, unsigned int reg); -}; +#include "internal.h" static void regmap_format_4_12_write(struct regmap *map, unsigned int reg, unsigned int val) -- cgit v1.2.1 From 31244e396fa9e4854cfd6dfe305983e77802c156 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 20 Jul 2011 22:56:53 +0100 Subject: regmap: Provide register map dump via debugfs Copy over the read parts of the ASoC debugfs implementation into regmap, allowing users to see what the register values the device has are at runtime. The implementation, especially the support for seeking, is mostly due to Dimitris Papastamos' work in ASoC. Signed-off-by: Mark Brown --- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/internal.h | 15 ++++ drivers/base/regmap/regmap-debugfs.c | 131 +++++++++++++++++++++++++++++++++++ drivers/base/regmap/regmap.c | 11 +++ 4 files changed, 158 insertions(+) create mode 100644 drivers/base/regmap/regmap-debugfs.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index f476f4571295..057c13f66a67 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_REGMAP) += regmap.o +obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 7e61504a7ac3..78f87f316a1b 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -14,6 +14,7 @@ #define _REGMAP_INTERNAL_H #include +#include struct regmap; @@ -36,10 +37,24 @@ struct regmap { struct regmap_format format; /* Buffer format */ const struct regmap_bus *bus; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif + unsigned int max_register; bool (*writeable_reg)(struct device *dev, unsigned int reg); bool (*readable_reg)(struct device *dev, unsigned int reg); bool (*volatile_reg)(struct device *dev, unsigned int reg); }; +#ifdef CONFIG_DEBUG_FS +extern void regmap_debugfs_initcall(void); +extern void regmap_debugfs_init(struct regmap *map); +extern void regmap_debugfs_exit(struct regmap *map); +#else +void regmap_debugfs_initcall(void) { } +void regmap_debugfs_init(struct regmap *map) { } +void regmap_debugfs_exit(struct regmap *map) { } +#endif + #endif diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c new file mode 100644 index 000000000000..2be8bf86825b --- /dev/null +++ b/drivers/base/regmap/regmap-debugfs.c @@ -0,0 +1,131 @@ +/* + * Register map access API - debugfs + * + * 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 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include + +#include "internal.h" + +static struct dentry *regmap_debugfs_root; + +static int regmap_map_open_file(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + size_t reg_len, val_len, tot_len; + size_t buf_pos = 0; + loff_t p = 0; + ssize_t ret; + int i; + struct regmap *map = file->private_data; + char *buf; + unsigned int val; + + if (*ppos < 0 || !count) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Calculate the length of a fixed format */ + snprintf(buf, count, "%x", map->max_register); + reg_len = strlen(buf); + val_len = 2 * map->format.val_bytes; + tot_len = reg_len + val_len + 3; /* : \n */ + + for (i = 0; i < map->max_register; i++) { + if (map->readable_reg && + !map->readable_reg(map->dev, i)) + continue; + + /* If we're in the region the user is trying to read */ + if (p >= *ppos) { + /* ...but not beyond it */ + if (buf_pos >= count - 1 - tot_len) + break; + + /* Format the register */ + snprintf(buf + buf_pos, count - buf_pos, "%.*x: ", + reg_len, i); + buf_pos += reg_len + 2; + + /* Format the value, write all X if we can't read */ + ret = regmap_read(map, i, &val); + if (ret == 0) + snprintf(buf + buf_pos, count - buf_pos, + "%.*x", val_len, val); + else + memset(buf + buf_pos, 'X', val_len); + buf_pos += 2 * map->format.val_bytes; + + buf[buf_pos++] = '\n'; + } + p += tot_len; + } + + ret = buf_pos; + + if (copy_to_user(user_buf, buf, buf_pos)) { + ret = -EFAULT; + goto out; + } + + *ppos += buf_pos; + +out: + kfree(buf); + return ret; +} + +static const struct file_operations regmap_map_fops = { + .open = regmap_map_open_file, + .read = regmap_map_read_file, + .llseek = default_llseek, +}; + + +void regmap_debugfs_init(struct regmap *map) +{ + map->debugfs = debugfs_create_dir(dev_name(map->dev), + regmap_debugfs_root); + if (!map->debugfs) { + dev_warn(map->dev, "Failed to create debugfs directory\n"); + return; + } + + if (map->max_register) + debugfs_create_file("registers", 0400, map->debugfs, + map, ®map_map_fops); +} + +void regmap_debugfs_exit(struct regmap *map) +{ + debugfs_remove_recursive(map->debugfs); +} + +void regmap_debugfs_initcall(void) +{ + regmap_debugfs_root = debugfs_create_dir("regmap", NULL); + if (!regmap_debugfs_root) { + pr_warn("regmap: Failed to create debugfs root\n"); + return; + } +} diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index f51efeb091c5..a3eaef6552ce 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -156,6 +156,8 @@ struct regmap *regmap_init(struct device *dev, goto err_bus; } + regmap_debugfs_init(map); + return map; err_bus: @@ -172,6 +174,7 @@ EXPORT_SYMBOL_GPL(regmap_init); */ void regmap_exit(struct regmap *map) { + regmap_debugfs_exit(map); kfree(map->work_buf); module_put(map->bus->owner); kfree(map); @@ -472,3 +475,11 @@ out: return ret; } EXPORT_SYMBOL_GPL(regmap_update_bits); + +static int __init regmap_initcall(void) +{ + regmap_debugfs_initcall(); + + return 0; +} +postcore_initcall(regmap_initcall); -- cgit v1.2.1 From 2efe1642b73e74604498175de032b8a604868fb7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 8 Aug 2011 15:41:46 +0900 Subject: regmap: Skip precious registers when dumping registers via debugfs Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regmap-debugfs.c | 4 ++++ drivers/base/regmap/regmap.c | 1 + 3 files changed, 6 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 78f87f316a1b..a67dc68aba5e 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -45,6 +45,7 @@ struct regmap { bool (*writeable_reg)(struct device *dev, unsigned int reg); bool (*readable_reg)(struct device *dev, unsigned int reg); bool (*volatile_reg)(struct device *dev, unsigned int reg); + bool (*precious_reg)(struct device *dev, unsigned int reg); }; #ifdef CONFIG_DEBUG_FS diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 2be8bf86825b..184b618e318e 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -56,6 +56,10 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, !map->readable_reg(map->dev, i)) continue; + if (map->precious_reg && + map->precious_reg(map->dev, i)) + continue; + /* If we're in the region the user is trying to read */ if (p >= *ppos) { /* ...but not beyond it */ diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index a3eaef6552ce..d74d306a938b 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -101,6 +101,7 @@ struct regmap *regmap_init(struct device *dev, map->writeable_reg = config->writeable_reg; map->readable_reg = config->readable_reg; map->volatile_reg = config->volatile_reg; + map->precious_reg = config->precious_reg; switch (config->reg_bits) { case 4: -- cgit v1.2.1 From cb3c2dcfa34072b785cf292ca0b66494496572b9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 9 Aug 2011 16:47:42 +0900 Subject: regmap: Fix type of field width specifiers for x86_64 x86_64 size_t is not an int but the printf format specifier for size_t should be an int. Signed-off-by: Mark Brown Reported-by: Stephen Rothwell --- 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 184b618e318e..6e304a4e2706 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -29,7 +29,7 @@ static int regmap_map_open_file(struct inode *inode, struct file *file) static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { - size_t reg_len, val_len, tot_len; + int reg_len, val_len, tot_len; size_t buf_pos = 0; loff_t p = 0; ssize_t ret; -- cgit v1.2.1 From 790923e56be795e14eaaeb3305cb4e171cd0a72f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 9 Aug 2011 10:41:37 +0900 Subject: regmap: Remove unused type and list fields from bus interface We no longer enumerate the bus types, we rely on the driver telling us this on init. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-i2c.c | 1 - drivers/base/regmap/regmap-spi.c | 1 - 2 files changed, 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c index c2231ff06cbc..e6ce82d0ecd1 100644 --- a/drivers/base/regmap/regmap-i2c.c +++ b/drivers/base/regmap/regmap-i2c.c @@ -90,7 +90,6 @@ static int regmap_i2c_read(struct device *dev, } static struct regmap_bus regmap_i2c = { - .type = &i2c_bus_type, .write = regmap_i2c_write, .gather_write = regmap_i2c_gather_write, .read = regmap_i2c_read, diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c index 4deba0621bc7..07633bda0a04 100644 --- a/drivers/base/regmap/regmap-spi.c +++ b/drivers/base/regmap/regmap-spi.c @@ -47,7 +47,6 @@ static int regmap_spi_read(struct device *dev, } static struct regmap_bus regmap_spi = { - .type = &spi_bus_type, .write = regmap_spi_write, .gather_write = regmap_spi_gather_write, .read = regmap_spi_read, -- cgit v1.2.1 From 8de2f081ef8ee716663f916df9f2a7d015fa0dad Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 10 Aug 2011 17:14:41 +0900 Subject: regmap: Add functions to check for access on registers We're going to be using these in quite a few places so factor out the readable/writable/volatile/precious checks. Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 5 ++++ drivers/base/regmap/regmap-debugfs.c | 6 ++--- drivers/base/regmap/regmap.c | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a67dc68aba5e..5ab3fefa4b05 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -48,6 +48,11 @@ struct regmap { bool (*precious_reg)(struct device *dev, unsigned int reg); }; +bool regmap_writeable(struct regmap *map, unsigned int reg); +bool regmap_readable(struct regmap *map, unsigned int reg); +bool regmap_volatile(struct regmap *map, unsigned int reg); +bool regmap_precious(struct regmap *map, unsigned int reg); + #ifdef CONFIG_DEBUG_FS extern void regmap_debugfs_initcall(void); extern void regmap_debugfs_init(struct regmap *map); diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 6e304a4e2706..fff8e832a985 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -52,12 +52,10 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, tot_len = reg_len + val_len + 3; /* : \n */ for (i = 0; i < map->max_register; i++) { - if (map->readable_reg && - !map->readable_reg(map->dev, i)) + if (!regmap_readable(map, i)) continue; - if (map->precious_reg && - map->precious_reg(map->dev, i)) + if (regmap_precious(map, i)) continue; /* If we're in the region the user is trying to read */ diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index d74d306a938b..fa2bd896eb20 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -20,6 +20,50 @@ #include "internal.h" +bool regmap_writeable(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->writeable_reg) + return map->writeable_reg(map->dev, reg); + + return true; +} + +bool regmap_readable(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->readable_reg) + return map->readable_reg(map->dev, reg); + + return true; +} + +bool regmap_volatile(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->volatile_reg) + return map->volatile_reg(map->dev, reg); + + return true; +} + +bool regmap_precious(struct regmap *map, unsigned int reg) +{ + if (map->max_register && reg > map->max_register) + return false; + + if (map->precious_reg) + return map->precious_reg(map->dev, reg); + + return false; +} + static void regmap_format_4_12_write(struct regmap *map, unsigned int reg, unsigned int val) { -- cgit v1.2.1 From 21f555445676e5c7d30bb9b3487cb183d02e45e3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 10 Aug 2011 17:15:31 +0900 Subject: regmap: Share some of the debugfs infrastructure ready for more files Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index fff8e832a985..e541d7f4b4e0 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -20,7 +20,14 @@ static struct dentry *regmap_debugfs_root; -static int regmap_map_open_file(struct inode *inode, struct file *file) +/* Calculate the length of a fixed format */ +static size_t regmap_calc_reg_len(int max_val, char *buf, size_t buf_size) +{ + snprintf(buf, buf_size, "%x", max_val); + return strlen(buf); +} + +static int regmap_open_file(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; @@ -46,8 +53,7 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, return -ENOMEM; /* Calculate the length of a fixed format */ - snprintf(buf, count, "%x", map->max_register); - reg_len = strlen(buf); + reg_len = regmap_calc_reg_len(map->max_register, buf, count); val_len = 2 * map->format.val_bytes; tot_len = reg_len + val_len + 3; /* : \n */ @@ -98,7 +104,7 @@ out: } static const struct file_operations regmap_map_fops = { - .open = regmap_map_open_file, + .open = regmap_open_file, .read = regmap_map_read_file, .llseek = default_llseek, }; -- cgit v1.2.1 From 449e38427fe57a6120fecd1051981c89ee862b3d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 10 Aug 2011 17:28:04 +0900 Subject: regmap: Provide access information via debugfs Let userspace know what the access map for the device is. This is helpful for verifying that the access map is correctly configured and could also be useful for programs that try to work with the data. File format is: register: R W V P where R, W, V and P are 'y' or 'n' showing readable, writable, volatile and precious respectively. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 72 +++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index e541d7f4b4e0..7a8d67537d3f 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -109,6 +109,73 @@ static const struct file_operations regmap_map_fops = { .llseek = default_llseek, }; +static ssize_t regmap_access_read_file(struct file *file, + char __user *user_buf, size_t count, + loff_t *ppos) +{ + int reg_len, tot_len; + size_t buf_pos = 0; + loff_t p = 0; + ssize_t ret; + int i; + struct regmap *map = file->private_data; + char *buf; + + if (*ppos < 0 || !count) + return -EINVAL; + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + /* Calculate the length of a fixed format */ + reg_len = regmap_calc_reg_len(map->max_register, buf, count); + tot_len = reg_len + 10; /* ': R W V P\n' */ + + for (i = 0; i < map->max_register; i++) { + /* Ignore registers which are neither readable nor writable */ + if (!regmap_readable(map, i) && !regmap_writeable(map, i)) + continue; + + /* If we're in the region the user is trying to read */ + if (p >= *ppos) { + /* ...but not beyond it */ + if (buf_pos >= count - 1 - tot_len) + break; + + /* Format the register */ + snprintf(buf + buf_pos, count - buf_pos, + "%.*x: %c %c %c %c\n", + reg_len, i, + regmap_readable(map, i) ? 'y' : 'n', + regmap_writeable(map, i) ? 'y' : 'n', + regmap_volatile(map, i) ? 'y' : 'n', + regmap_precious(map, i) ? 'y' : 'n'); + + buf_pos += tot_len; + } + p += tot_len; + } + + ret = buf_pos; + + if (copy_to_user(user_buf, buf, buf_pos)) { + ret = -EFAULT; + goto out; + } + + *ppos += buf_pos; + +out: + kfree(buf); + return ret; +} + +static const struct file_operations regmap_access_fops = { + .open = regmap_open_file, + .read = regmap_access_read_file, + .llseek = default_llseek, +}; void regmap_debugfs_init(struct regmap *map) { @@ -119,9 +186,12 @@ void regmap_debugfs_init(struct regmap *map) return; } - if (map->max_register) + if (map->max_register) { debugfs_create_file("registers", 0400, map->debugfs, map, ®map_map_fops); + debugfs_create_file("access", 0400, map->debugfs, + map, ®map_access_fops); + } } void regmap_debugfs_exit(struct regmap *map) -- cgit v1.2.1 From 5b457e3910251ef28712ba5d14adac8194bd5b2c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 5 Sep 2011 08:12:05 -0700 Subject: regmap: Remove redundant owner field from the bus type struct No longer used as users link directly with the bus types so the core module infrastructure does refcounting for us. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-i2c.c | 1 - drivers/base/regmap/regmap-spi.c | 1 - 2 files changed, 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c index e7d916d1b3ea..38621ec87c05 100644 --- a/drivers/base/regmap/regmap-i2c.c +++ b/drivers/base/regmap/regmap-i2c.c @@ -93,7 +93,6 @@ static struct regmap_bus regmap_i2c = { .write = regmap_i2c_write, .gather_write = regmap_i2c_gather_write, .read = regmap_i2c_read, - .owner = THIS_MODULE, }; /** diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c index cc0f1164cf99..14132b829062 100644 --- a/drivers/base/regmap/regmap-spi.c +++ b/drivers/base/regmap/regmap-spi.c @@ -50,7 +50,6 @@ static struct regmap_bus regmap_spi = { .write = regmap_spi_write, .gather_write = regmap_spi_gather_write, .read = regmap_spi_read, - .owner = THIS_MODULE, .read_flag_mask = 0x80, }; -- cgit v1.2.1 From d813ae9a105219255c07d382059de831186c10d0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 5 Sep 2011 08:13:07 -0700 Subject: regmap: Include the last register in debugfs output Off by one in the array iteration. Reported-by: Jonathan Cameron Signed-off-by: Mark Brown --- drivers/base/regmap/regmap-debugfs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 7a8d67537d3f..6f397476e27c 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -57,7 +57,7 @@ static ssize_t regmap_map_read_file(struct file *file, char __user *user_buf, val_len = 2 * map->format.val_bytes; tot_len = reg_len + val_len + 3; /* : \n */ - for (i = 0; i < map->max_register; i++) { + for (i = 0; i < map->max_register + 1; i++) { if (!regmap_readable(map, i)) continue; @@ -132,7 +132,7 @@ static ssize_t regmap_access_read_file(struct file *file, reg_len = regmap_calc_reg_len(map->max_register, buf, count); tot_len = reg_len + 10; /* ': R W V P\n' */ - for (i = 0; i < map->max_register; i++) { + for (i = 0; i < map->max_register + 1; i++) { /* Ignore registers which are neither readable nor writable */ if (!regmap_readable(map, i) && !regmap_writeable(map, i)) continue; -- cgit v1.2.1 From 6f306441e97f8f9d27c43a536360fe221f675a71 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 5 Sep 2011 20:46:32 +0200 Subject: regmap: Add support for device specific write and read flag masks. Some buses like SPI have no standard notation of read or write operations. The general scheme here is to set or clear specific bits in the register address to indicate whether the operation is a read or write. We already support having a read flag mask per bus, but as there is no standard the bits which need to be set or cleared differ between devices and vendors, thus we need a mechanism to specify them per device. This patch adds two new entries to the regmap_config struct, read_flag_mask and write_flag_mask. These will be or'ed onto the top byte when doing a read or write operation. If both masks are empty the device will fallback to the regmap_bus masks. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 3 +++ drivers/base/regmap/regmap.c | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 5ab3fefa4b05..7e14d5a6f53e 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -46,6 +46,9 @@ struct regmap { bool (*readable_reg)(struct device *dev, unsigned int reg); bool (*volatile_reg)(struct device *dev, unsigned int reg); bool (*precious_reg)(struct device *dev, unsigned int reg); + + u8 read_flag_mask; + u8 write_flag_mask; }; bool regmap_writeable(struct regmap *map, unsigned int reg); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 86b184776199..e7adfe70e425 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -147,6 +147,13 @@ struct regmap *regmap_init(struct device *dev, map->volatile_reg = config->volatile_reg; map->precious_reg = config->precious_reg; + if (config->read_flag_mask || config->write_flag_mask) { + map->read_flag_mask = config->read_flag_mask; + map->write_flag_mask = config->write_flag_mask; + } else { + map->read_flag_mask = bus->read_flag_mask; + } + switch (config->reg_bits) { case 4: switch (config->val_bits) { @@ -226,6 +233,7 @@ EXPORT_SYMBOL_GPL(regmap_exit); static int _regmap_raw_write(struct regmap *map, unsigned int reg, const void *val, size_t val_len) { + u8 *u8 = map->work_buf; void *buf; int ret = -ENOTSUPP; size_t len; @@ -239,6 +247,8 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, map->format.format_reg(map->work_buf, reg); + u8[0] |= map->write_flag_mask; + trace_regmap_hw_write_start(map->dev, reg, val_len / map->format.val_bytes); @@ -366,13 +376,12 @@ static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, map->format.format_reg(map->work_buf, reg); /* - * Some buses flag reads by setting the high bits in the + * Some buses or devices flag reads by setting the high bits in the * register addresss; since it's always the high bits for all * current formats we can do this here rather than in * formatting. This may break if we get interesting formats. */ - if (map->bus->read_flag_mask) - u8[0] |= map->bus->read_flag_mask; + u8[0] |= map->read_flag_mask; trace_regmap_hw_read_start(map->dev, reg, val_len / map->format.val_bytes); -- cgit v1.2.1 From bbcf61ca8dcf093bd030a4dafb662b714676652d Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Mon, 5 Sep 2011 22:06:13 +0200 Subject: regmap: Make debugfs stubs static inline Make the debugfs stubs static inline to avoid future compilation issues due to duplicated symbols when CONFIG_DEBUG_FS=n once internal.h is included by multiple source files. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 7e14d5a6f53e..a98493cde5c3 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -61,9 +61,9 @@ extern void regmap_debugfs_initcall(void); extern void regmap_debugfs_init(struct regmap *map); extern void regmap_debugfs_exit(struct regmap *map); #else -void regmap_debugfs_initcall(void) { } -void regmap_debugfs_init(struct regmap *map) { } -void regmap_debugfs_exit(struct regmap *map) { } +static inline void regmap_debugfs_initcall(void) { } +static inline void regmap_debugfs_init(struct regmap *map) { } +static inline void regmap_debugfs_exit(struct regmap *map) { } #endif #endif -- cgit v1.2.1 From 9fabe24e9b1af84509b842731d2beaf85e66681e Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:00 +0100 Subject: regmap: Introduce caching support This patch introduces caching support for regmap. The regcache API has evolved essentially out of ASoC soc-cache so most of the actual caching types (except LZO) have been tested in the past. The purpose of regcache is to optimize in time and space the handling of register caches. Time optimization is achieved by not having to go over a slow bus like I2C to read the value of a register, instead it is cached locally in memory and can be retrieved faster. Regarding space optimization, some of the cache types are better at packing the caches, for e.g. the rbtree and the LZO caches. By doing this the sacrifice in time still wins over doing I2C transactions. Signed-off-by: Dimitris Papastamos Tested-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/Makefile | 2 +- drivers/base/regmap/internal.h | 52 +++++++ drivers/base/regmap/regcache.c | 304 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regcache.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 057c13f66a67..2e103ea9a3a0 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_REGMAP) += regmap.o +obj-$(CONFIG_REGMAP) += regmap.o regcache.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index a98493cde5c3..615f5581d5db 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -17,6 +17,7 @@ #include struct regmap; +struct regcache_ops; struct regmap_format { size_t buf_size; @@ -49,6 +50,40 @@ struct regmap { u8 read_flag_mask; u8 write_flag_mask; + + /* regcache specific members */ + const struct regcache_ops *cache_ops; + enum regcache_type cache_type; + + /* number of bytes in reg_defaults_raw */ + unsigned int cache_size_raw; + /* number of bytes per word in reg_defaults_raw */ + unsigned int cache_word_size; + /* number of entries in reg_defaults */ + unsigned int num_reg_defaults; + /* number of entries in reg_defaults_raw */ + unsigned int num_reg_defaults_raw; + + /* if set, only the cache is modified not the HW */ + unsigned int cache_only:1; + /* if set, only the HW is modified not the cache */ + unsigned int cache_bypass:1; + /* if set, remember to free reg_defaults_raw */ + unsigned int cache_free:1; + + struct reg_default *reg_defaults; + const void *reg_defaults_raw; + void *cache; +}; + +struct regcache_ops { + const char *name; + enum regcache_type type; + int (*init)(struct regmap *map); + int (*exit)(struct regmap *map); + 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); }; bool regmap_writeable(struct regmap *map, unsigned int reg); @@ -66,4 +101,21 @@ static inline void regmap_debugfs_init(struct regmap *map) { } static inline void regmap_debugfs_exit(struct regmap *map) { } #endif +/* regcache core declarations */ +int regcache_init(struct regmap *map); +void regcache_exit(struct regmap *map); +int regcache_read(struct regmap *map, + unsigned int reg, unsigned int *value); +int regcache_write(struct regmap *map, + unsigned int reg, unsigned int value); +int regcache_sync(struct regmap *map); + +unsigned int regcache_get_val(const void *base, unsigned int idx, + unsigned int word_size); +bool regcache_set_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size); +int regcache_lookup_reg(struct regmap *map, unsigned int reg); +int regcache_insert_reg(struct regmap *map, unsigned int reg, + unsigned int val); + #endif diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c new file mode 100644 index 000000000000..9575e4c5f34a --- /dev/null +++ b/drivers/base/regmap/regcache.c @@ -0,0 +1,304 @@ +/* + * Register cache access API + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include "internal.h" + +static const struct regcache_ops *cache_types[] = { +}; + +static int regcache_hw_init(struct regmap *map) +{ + int i, j; + int ret; + int count; + unsigned int val; + void *tmp_buf; + + if (!map->num_reg_defaults_raw) + return -EINVAL; + + if (!map->reg_defaults_raw) { + dev_warn(map->dev, "No cache defaults, reading back from HW\n"); + tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL); + if (!tmp_buf) + return -EINVAL; + ret = regmap_bulk_read(map, 0, tmp_buf, + map->num_reg_defaults_raw); + if (ret < 0) { + kfree(tmp_buf); + return ret; + } + map->reg_defaults_raw = tmp_buf; + map->cache_free = 1; + } + + /* calculate the size of reg_defaults */ + for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) { + val = regcache_get_val(map->reg_defaults_raw, + i, map->cache_word_size); + if (!val) + continue; + count++; + } + + map->reg_defaults = kmalloc(count * sizeof(struct reg_default), + GFP_KERNEL); + if (!map->reg_defaults) + return -ENOMEM; + + /* fill the reg_defaults */ + map->num_reg_defaults = count; + for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { + val = regcache_get_val(map->reg_defaults_raw, + i, map->cache_word_size); + if (!val) + continue; + map->reg_defaults[j].reg = i; + map->reg_defaults[j].def = val; + j++; + } + + return 0; +} + +int regcache_init(struct regmap *map) +{ + int ret; + int i; + void *tmp_buf; + + if (map->cache_type == REGCACHE_NONE) + return 0; + + for (i = 0; i < ARRAY_SIZE(cache_types); i++) + if (cache_types[i]->type == map->cache_type) + break; + + if (i == ARRAY_SIZE(cache_types)) { + dev_err(map->dev, "Could not match compress type: %d\n", + map->cache_type); + return -EINVAL; + } + + map->cache = NULL; + map->cache_ops = cache_types[i]; + + if (!map->cache_ops->read || + !map->cache_ops->write || + !map->cache_ops->name) + return -EINVAL; + + /* We still need to ensure that the reg_defaults + * won't vanish from under us. We'll need to make + * a copy of it. + */ + if (map->reg_defaults) { + if (!map->num_reg_defaults) + return -EINVAL; + tmp_buf = kmemdup(map->reg_defaults, map->num_reg_defaults * + sizeof(struct reg_default), GFP_KERNEL); + if (!tmp_buf) + return -ENOMEM; + map->reg_defaults = tmp_buf; + } else { + /* Some devices such as PMIC's don't have cache defaults, + * we cope with this by reading back the HW registers and + * crafting the cache defaults by hand. + */ + ret = regcache_hw_init(map); + if (ret < 0) + return ret; + } + + if (!map->max_register) + map->max_register = map->num_reg_defaults_raw; + + if (map->cache_ops->init) { + dev_dbg(map->dev, "Initializing %s cache\n", + map->cache_ops->name); + return map->cache_ops->init(map); + } + return 0; +} + +void regcache_exit(struct regmap *map) +{ + if (map->cache_type == REGCACHE_NONE) + return; + + BUG_ON(!map->cache_ops); + + kfree(map->reg_defaults); + if (map->cache_free) + kfree(map->reg_defaults_raw); + + if (map->cache_ops->exit) { + dev_dbg(map->dev, "Destroying %s cache\n", + map->cache_ops->name); + map->cache_ops->exit(map); + } +} + +/** + * regcache_read: Fetch the value of a given register from the cache. + * + * @map: map to configure. + * @reg: The register index. + * @value: The value to be returned. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + if (map->cache_type == REGCACHE_NONE) + return -ENOSYS; + + BUG_ON(!map->cache_ops); + + if (!regmap_readable(map, reg)) + return -EIO; + + if (!regmap_volatile(map, reg)) + return map->cache_ops->read(map, reg, value); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(regcache_read); + +/** + * regcache_write: Set the value of a given register in the cache. + * + * @map: map to configure. + * @reg: The register index. + * @value: The new register value. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_write(struct regmap *map, + unsigned int reg, unsigned int value) +{ + if (map->cache_type == REGCACHE_NONE) + return 0; + + BUG_ON(!map->cache_ops); + + if (!regmap_writeable(map, reg)) + return -EIO; + + if (!regmap_volatile(map, reg)) + return map->cache_ops->write(map, reg, value); + + return 0; +} +EXPORT_SYMBOL_GPL(regcache_write); + +/** + * regcache_sync: Sync the register cache with the hardware. + * + * @map: map to configure. + * + * Any registers that should not be synced should be marked as + * volatile. In general drivers can choose not to use the provided + * syncing functionality if they so require. + * + * Return a negative value on failure, 0 on success. + */ +int regcache_sync(struct regmap *map) +{ + BUG_ON(!map->cache_ops); + + if (map->cache_ops->sync) { + dev_dbg(map->dev, "Syncing %s cache\n", + map->cache_ops->name); + return map->cache_ops->sync(map); + } + return 0; +} +EXPORT_SYMBOL_GPL(regcache_sync); + +bool regcache_set_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size) +{ + switch (word_size) { + case 1: { + u8 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + case 2: { + u16 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + default: + BUG(); + } + /* unreachable */ + return false; +} + +unsigned int regcache_get_val(const void *base, unsigned int idx, + unsigned int word_size) +{ + if (!base) + return -EINVAL; + + switch (word_size) { + case 1: { + const u8 *cache = base; + return cache[idx]; + } + case 2: { + const u16 *cache = base; + return cache[idx]; + } + default: + BUG(); + } + /* unreachable */ + return -1; +} + +int regcache_lookup_reg(struct regmap *map, unsigned int reg) +{ + unsigned int i; + + for (i = 0; i < map->num_reg_defaults; i++) + if (map->reg_defaults[i].reg == reg) + return i; + return -1; +} + +int regcache_insert_reg(struct regmap *map, unsigned int reg, + unsigned int val) +{ + void *tmp; + + tmp = krealloc(map->reg_defaults, + (map->num_reg_defaults + 1) * sizeof(struct reg_default), + GFP_KERNEL); + if (!tmp) + return -ENOMEM; + map->reg_defaults = tmp; + map->num_reg_defaults++; + map->reg_defaults[map->num_reg_defaults - 1].reg = reg; + map->reg_defaults[map->num_reg_defaults - 1].def = val; + return 0; +} -- cgit v1.2.1 From 195af65ca92179ac2b524d35d732dc6fecec2744 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:01 +0100 Subject: regmap: Add the indexed cache support This is the simplest form of a cache available in regcache. Any registers whose default value is 0 are ignored. If any of those registers are modified in the future, they will be placed in the cache on demand. The cache layout is essentially using the provided register defaults by the regcache core directly and does not re-map it to another representation. Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/Makefile | 2 +- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regcache-indexed.c | 65 ++++++++++++++++++++++++++++++++++ drivers/base/regmap/regcache.c | 1 + 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regcache-indexed.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 2e103ea9a3a0..418d151eb30b 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_REGMAP) += regmap.o regcache.o +obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 615f5581d5db..5bd5759efd5c 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -118,4 +118,5 @@ int regcache_lookup_reg(struct regmap *map, unsigned int reg); int regcache_insert_reg(struct regmap *map, unsigned int reg, unsigned int val); +extern struct regcache_ops regcache_indexed_ops; #endif diff --git a/drivers/base/regmap/regcache-indexed.c b/drivers/base/regmap/regcache-indexed.c new file mode 100644 index 000000000000..ff8b44ce044b --- /dev/null +++ b/drivers/base/regmap/regcache-indexed.c @@ -0,0 +1,65 @@ +/* + * Register cache access API - indexed caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include + +#include "internal.h" + +static int regcache_indexed_read(struct regmap *map, unsigned int reg, + unsigned int *value) +{ + int ret; + + ret = regcache_lookup_reg(map, reg); + if (ret < 0) + *value = 0; + else + *value = map->reg_defaults[ret].def; + return 0; +} + +static int regcache_indexed_write(struct regmap *map, unsigned int reg, + unsigned int value) +{ + int ret; + + ret = regcache_lookup_reg(map, reg); + if (ret < 0) + return regcache_insert_reg(map, reg, value); + map->reg_defaults[ret].def = value; + return 0; +} + +static int regcache_indexed_sync(struct regmap *map) +{ + int i; + int ret; + + for (i = 0; i < map->num_reg_defaults; i++) { + ret = regmap_write(map, map->reg_defaults[i].reg, + map->reg_defaults[i].def); + if (ret < 0) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + } + return 0; +} + +struct regcache_ops regcache_indexed_ops = { + .type = REGCACHE_INDEXED, + .name = "indexed", + .read = regcache_indexed_read, + .write = regcache_indexed_write, + .sync = regcache_indexed_sync +}; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 9575e4c5f34a..22b73ec12fd5 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -16,6 +16,7 @@ #include "internal.h" static const struct regcache_ops *cache_types[] = { + ®cache_indexed_ops, }; static int regcache_hw_init(struct regmap *map) -- cgit v1.2.1 From 28644c809f44498b8cd91d00b4cdb09e63b99843 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:02 +0100 Subject: regmap: Add the rbtree cache support This patch adds support for the rbtree cache compression type. Each rbnode manages a variable length block of registers. There can be no two nodes with overlapping blocks. Each block has a base register and a currently top register, all the other registers, if any, lie in between these two and in ascending order. The reasoning behind the construction of this rbtree is simple. In the snd_soc_rbtree_cache_init() function, we iterate over the register defaults provided by the regcache core. For each register value that is non-zero we insert it in the rbtree. In order to determine in which rbnode we need to add the register, we first look if there is another register already added that is adjacent to the one we are about to add. If that is the case we append it in that rbnode block, otherwise we create a new rbnode with a single register in its block and add it to the tree. There are various optimizations across the implementation to speed up lookups by caching the most recently used rbnode. Signed-off-by: Dimitris Papastamos Tested-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/Makefile | 2 +- drivers/base/regmap/internal.h | 1 + drivers/base/regmap/regcache-rbtree.c | 399 ++++++++++++++++++++++++++++++++++ drivers/base/regmap/regcache.c | 1 + 4 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regcache-rbtree.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 418d151eb30b..20cd65035fc7 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o +obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 5bd5759efd5c..7ef8afc77e7c 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -119,4 +119,5 @@ int regcache_insert_reg(struct regmap *map, unsigned int reg, unsigned int val); extern struct regcache_ops regcache_indexed_ops; +extern struct regcache_ops regcache_rbtree_ops; #endif diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c new file mode 100644 index 000000000000..4d7ba4511755 --- /dev/null +++ b/drivers/base/regmap/regcache-rbtree.c @@ -0,0 +1,399 @@ +/* + * Register cache access API - rbtree caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include "internal.h" + +static int regcache_rbtree_write(struct regmap *map, unsigned int reg, + unsigned int value); + +struct regcache_rbtree_node { + /* the actual rbtree node holding this block */ + struct rb_node node; + /* base register handled by this block */ + unsigned int base_reg; + /* number of bytes needed to represent the register index */ + unsigned int word_size; + /* block of adjacent registers */ + void *block; + /* number of registers available in the block */ + unsigned int blklen; +} __attribute__ ((packed)); + +struct regcache_rbtree_ctx { + struct rb_root root; + struct regcache_rbtree_node *cached_rbnode; +}; + +static inline void regcache_rbtree_get_base_top_reg( + struct regcache_rbtree_node *rbnode, + unsigned int *base, unsigned int *top) +{ + *base = rbnode->base_reg; + *top = rbnode->base_reg + rbnode->blklen - 1; +} + +static unsigned int regcache_rbtree_get_register( + struct regcache_rbtree_node *rbnode, unsigned int idx) +{ + unsigned int val; + + switch (rbnode->word_size) { + case 1: { + u8 *p = rbnode->block; + val = p[idx]; + return val; + } + case 2: { + u16 *p = rbnode->block; + val = p[idx]; + return val; + } + default: + BUG(); + break; + } + return -1; +} + +static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode, + unsigned int idx, unsigned int val) +{ + switch (rbnode->word_size) { + case 1: { + u8 *p = rbnode->block; + p[idx] = val; + break; + } + case 2: { + u16 *p = rbnode->block; + p[idx] = val; + break; + } + default: + BUG(); + break; + } +} + +static struct regcache_rbtree_node *regcache_rbtree_lookup( + struct rb_root *root, unsigned int reg) +{ + struct rb_node *node; + struct regcache_rbtree_node *rbnode; + unsigned int base_reg, top_reg; + + node = root->rb_node; + while (node) { + rbnode = container_of(node, struct regcache_rbtree_node, node); + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) + return rbnode; + else if (reg > top_reg) + node = node->rb_right; + else if (reg < base_reg) + node = node->rb_left; + } + + return NULL; +} + +static int regcache_rbtree_insert(struct rb_root *root, + struct regcache_rbtree_node *rbnode) +{ + struct rb_node **new, *parent; + struct regcache_rbtree_node *rbnode_tmp; + unsigned int base_reg_tmp, top_reg_tmp; + unsigned int base_reg; + + parent = NULL; + new = &root->rb_node; + while (*new) { + rbnode_tmp = container_of(*new, struct regcache_rbtree_node, + node); + /* base and top registers of the current rbnode */ + regcache_rbtree_get_base_top_reg(rbnode_tmp, &base_reg_tmp, + &top_reg_tmp); + /* base register of the rbnode to be added */ + base_reg = rbnode->base_reg; + parent = *new; + /* if this register has already been inserted, just return */ + if (base_reg >= base_reg_tmp && + base_reg <= top_reg_tmp) + return 0; + else if (base_reg > top_reg_tmp) + new = &((*new)->rb_right); + else if (base_reg < base_reg_tmp) + new = &((*new)->rb_left); + } + + /* insert the node into the rbtree */ + rb_link_node(&rbnode->node, parent, new); + rb_insert_color(&rbnode->node, root); + + return 1; +} + +static int regcache_rbtree_init(struct regmap *map) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + int i; + int ret; + + map->cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); + if (!map->cache) + return -ENOMEM; + + rbtree_ctx = map->cache; + rbtree_ctx->root = RB_ROOT; + rbtree_ctx->cached_rbnode = NULL; + + for (i = 0; i < map->num_reg_defaults; i++) { + ret = regcache_rbtree_write(map, + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + if (ret) + goto err; + } + + return 0; + +err: + regcache_exit(map); + return ret; +} + +static int regcache_rbtree_exit(struct regmap *map) +{ + struct rb_node *next; + struct regcache_rbtree_ctx *rbtree_ctx; + struct regcache_rbtree_node *rbtree_node; + + /* if we've already been called then just return */ + rbtree_ctx = map->cache; + if (!rbtree_ctx) + return 0; + + /* free up the rbtree */ + next = rb_first(&rbtree_ctx->root); + while (next) { + rbtree_node = rb_entry(next, struct regcache_rbtree_node, node); + next = rb_next(&rbtree_node->node); + rb_erase(&rbtree_node->node, &rbtree_ctx->root); + kfree(rbtree_node->block); + kfree(rbtree_node); + } + + /* release the resources */ + kfree(map->cache); + map->cache = NULL; + + return 0; +} + +static int regcache_rbtree_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + struct regcache_rbtree_node *rbnode; + unsigned int base_reg, top_reg; + unsigned int reg_tmp; + + rbtree_ctx = map->cache; + /* look up the required register in the cached rbnode */ + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) { + reg_tmp = reg - base_reg; + *value = regcache_rbtree_get_register(rbnode, reg_tmp); + return 0; + } + } + /* if we can't locate it in the cached rbnode we'll have + * to traverse the rbtree looking for it. + */ + rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); + if (rbnode) { + reg_tmp = reg - rbnode->base_reg; + *value = regcache_rbtree_get_register(rbnode, reg_tmp); + rbtree_ctx->cached_rbnode = rbnode; + } else { + /* uninitialized registers default to 0 */ + *value = 0; + } + + return 0; +} + + +static int regcache_rbtree_insert_to_block(struct regcache_rbtree_node *rbnode, + unsigned int pos, unsigned int reg, + unsigned int value) +{ + u8 *blk; + + blk = krealloc(rbnode->block, + (rbnode->blklen + 1) * rbnode->word_size, GFP_KERNEL); + if (!blk) + return -ENOMEM; + + /* insert the register value in the correct place in the rbnode block */ + memmove(blk + (pos + 1) * rbnode->word_size, + blk + pos * rbnode->word_size, + (rbnode->blklen - pos) * rbnode->word_size); + + /* update the rbnode block, its size and the base register */ + rbnode->block = blk; + rbnode->blklen++; + if (!pos) + rbnode->base_reg = reg; + + regcache_rbtree_set_register(rbnode, pos, value); + return 0; +} + +static int regcache_rbtree_write(struct regmap *map, unsigned int reg, + unsigned int value) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + struct regcache_rbtree_node *rbnode, *rbnode_tmp; + struct rb_node *node; + unsigned int val; + unsigned int reg_tmp; + unsigned int base_reg, top_reg; + unsigned int pos; + int i; + int ret; + + rbtree_ctx = map->cache; + /* look up the required register in the cached rbnode */ + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) { + reg_tmp = reg - base_reg; + val = regcache_rbtree_get_register(rbnode, reg_tmp); + if (val == value) + return 0; + regcache_rbtree_set_register(rbnode, reg_tmp, value); + return 0; + } + } + /* if we can't locate it in the cached rbnode we'll have + * to traverse the rbtree looking for it. + */ + rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); + if (rbnode) { + reg_tmp = reg - rbnode->base_reg; + val = regcache_rbtree_get_register(rbnode, reg_tmp); + if (val == value) + return 0; + regcache_rbtree_set_register(rbnode, reg_tmp, value); + rbtree_ctx->cached_rbnode = rbnode; + } else { + /* bail out early, no need to create the rbnode yet */ + if (!value) + return 0; + /* look for an adjacent register to the one we are about to add */ + for (node = rb_first(&rbtree_ctx->root); node; + node = rb_next(node)) { + rbnode_tmp = rb_entry(node, struct regcache_rbtree_node, node); + for (i = 0; i < rbnode_tmp->blklen; i++) { + reg_tmp = rbnode_tmp->base_reg + i; + if (abs(reg_tmp - reg) != 1) + continue; + /* decide where in the block to place our register */ + if (reg_tmp + 1 == reg) + pos = i + 1; + else + pos = i; + ret = regcache_rbtree_insert_to_block(rbnode_tmp, pos, + reg, value); + if (ret) + return ret; + rbtree_ctx->cached_rbnode = rbnode_tmp; + 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. + */ + rbnode = kzalloc(sizeof *rbnode, GFP_KERNEL); + if (!rbnode) + return -ENOMEM; + rbnode->blklen = 1; + rbnode->base_reg = reg; + rbnode->word_size = map->cache_word_size; + rbnode->block = kmalloc(rbnode->blklen * rbnode->word_size, + GFP_KERNEL); + if (!rbnode->block) { + kfree(rbnode); + return -ENOMEM; + } + regcache_rbtree_set_register(rbnode, 0, value); + regcache_rbtree_insert(&rbtree_ctx->root, rbnode); + rbtree_ctx->cached_rbnode = rbnode; + } + + return 0; +} + +static int regcache_rbtree_sync(struct regmap *map) +{ + struct regcache_rbtree_ctx *rbtree_ctx; + struct rb_node *node; + struct regcache_rbtree_node *rbnode; + unsigned int regtmp; + unsigned int val, def; + int ret; + int i; + + rbtree_ctx = map->cache; + for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) { + rbnode = rb_entry(node, struct regcache_rbtree_node, node); + for (i = 0; i < rbnode->blklen; i++) { + regtmp = rbnode->base_reg + i; + val = regcache_rbtree_get_register(rbnode, i); + ret = regcache_lookup_reg(map, i); + if (ret < 0) + def = 0; + else + def = map->reg_defaults[ret].def; + if (val == def) + continue; + map->cache_bypass = 1; + ret = regmap_write(map, regtmp, val); + map->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + regtmp, val); + } + } + + return 0; +} + +struct regcache_ops regcache_rbtree_ops = { + .type = REGCACHE_RBTREE, + .name = "rbtree", + .init = regcache_rbtree_init, + .exit = regcache_rbtree_exit, + .read = regcache_rbtree_read, + .write = regcache_rbtree_write, + .sync = regcache_rbtree_sync +}; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 22b73ec12fd5..b870a668b771 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -17,6 +17,7 @@ static const struct regcache_ops *cache_types[] = { ®cache_indexed_ops, + ®cache_rbtree_ops, }; static int regcache_hw_init(struct regmap *map) -- cgit v1.2.1 From 2cbbb579bcbe3e11baf1c59920dcd5a780b80447 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:03 +0100 Subject: regmap: Add the LZO cache support This patch adds support for LZO compression when storing the register cache. For a typical device whose register map would normally occupy 25kB or 50kB by using the LZO compression technique, one can get down to ~5-7kB. There might be a performance penalty associated with each individual read/write due to decompressing/compressing the underlying cache, however that should not be noticeable. These memory benefits depend on whether the target architecture can get rid of the memory occupied by the original register defaults cache which is marked as __devinitconst. Nevertheless there will be some memory gain even if the target architecture can't get rid of the original register map, this should be around ~30-32kB instead of 50kB. Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/Kconfig | 2 + drivers/base/regmap/Makefile | 2 +- drivers/base/regmap/internal.h | 2 + drivers/base/regmap/regcache-lzo.c | 361 +++++++++++++++++++++++++++++++++++++ drivers/base/regmap/regcache.c | 1 + 5 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regcache-lzo.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index fabbf6cc5367..2fc6a66f39a4 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -4,6 +4,8 @@ config REGMAP default y if (REGMAP_I2C || REGMAP_SPI) + select LZO_COMPRESS + select LZO_DECOMPRESS bool config REGMAP_I2C diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 20cd65035fc7..0573c8a9dacb 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,4 +1,4 @@ -obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o +obj-$(CONFIG_REGMAP) += regmap.o regcache.o regcache-indexed.o regcache-rbtree.o regcache-lzo.o obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 7ef8afc77e7c..2d51b1b099f7 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -120,4 +120,6 @@ int regcache_insert_reg(struct regmap *map, unsigned int reg, extern struct regcache_ops regcache_indexed_ops; extern struct regcache_ops regcache_rbtree_ops; +extern struct regcache_ops regcache_lzo_ops; + #endif diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c new file mode 100644 index 000000000000..9079cb50b0b9 --- /dev/null +++ b/drivers/base/regmap/regcache-lzo.c @@ -0,0 +1,361 @@ +/* + * Register cache access API - LZO caching support + * + * Copyright 2011 Wolfson Microelectronics plc + * + * Author: Dimitris Papastamos + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include + +#include "internal.h" + +struct regcache_lzo_ctx { + void *wmem; + void *dst; + const void *src; + size_t src_len; + size_t dst_len; + size_t decompressed_size; + unsigned long *sync_bmp; + int sync_bmp_nbits; +}; + +#define LZO_BLOCK_NUM 8 +static int regcache_lzo_block_count(void) +{ + return LZO_BLOCK_NUM; +} + +static int regcache_lzo_prepare(struct regcache_lzo_ctx *lzo_ctx) +{ + lzo_ctx->wmem = kmalloc(LZO1X_MEM_COMPRESS, GFP_KERNEL); + if (!lzo_ctx->wmem) + return -ENOMEM; + return 0; +} + +static int regcache_lzo_compress(struct regcache_lzo_ctx *lzo_ctx) +{ + size_t compress_size; + int ret; + + ret = lzo1x_1_compress(lzo_ctx->src, lzo_ctx->src_len, + lzo_ctx->dst, &compress_size, lzo_ctx->wmem); + if (ret != LZO_E_OK || compress_size > lzo_ctx->dst_len) + return -EINVAL; + lzo_ctx->dst_len = compress_size; + return 0; +} + +static int regcache_lzo_decompress(struct regcache_lzo_ctx *lzo_ctx) +{ + size_t dst_len; + int ret; + + dst_len = lzo_ctx->dst_len; + ret = lzo1x_decompress_safe(lzo_ctx->src, lzo_ctx->src_len, + lzo_ctx->dst, &dst_len); + if (ret != LZO_E_OK || dst_len != lzo_ctx->dst_len) + return -EINVAL; + return 0; +} + +static int regcache_lzo_compress_cache_block(struct regmap *map, + struct regcache_lzo_ctx *lzo_ctx) +{ + int ret; + + lzo_ctx->dst_len = lzo1x_worst_compress(PAGE_SIZE); + lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); + if (!lzo_ctx->dst) { + lzo_ctx->dst_len = 0; + return -ENOMEM; + } + + ret = regcache_lzo_compress(lzo_ctx); + if (ret < 0) + return ret; + return 0; +} + +static int regcache_lzo_decompress_cache_block(struct regmap *map, + struct regcache_lzo_ctx *lzo_ctx) +{ + int ret; + + lzo_ctx->dst_len = lzo_ctx->decompressed_size; + lzo_ctx->dst = kmalloc(lzo_ctx->dst_len, GFP_KERNEL); + if (!lzo_ctx->dst) { + lzo_ctx->dst_len = 0; + return -ENOMEM; + } + + ret = regcache_lzo_decompress(lzo_ctx); + if (ret < 0) + return ret; + return 0; +} + +static inline int regcache_lzo_get_blkindex(struct regmap *map, + unsigned int reg) +{ + return (reg * map->cache_word_size) / + DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()); +} + +static inline int regcache_lzo_get_blkpos(struct regmap *map, + unsigned int reg) +{ + return reg % (DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()) / + map->cache_word_size); +} + +static inline int regcache_lzo_get_blksize(struct regmap *map) +{ + return DIV_ROUND_UP(map->cache_size_raw, regcache_lzo_block_count()); +} + +static int regcache_lzo_init(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + size_t bmp_size; + int ret, i, blksize, blkcount; + const char *p, *end; + unsigned long *sync_bmp; + + ret = 0; + + blkcount = regcache_lzo_block_count(); + map->cache = kzalloc(blkcount * sizeof *lzo_blocks, + GFP_KERNEL); + if (!map->cache) + return -ENOMEM; + lzo_blocks = map->cache; + + /* + * allocate a bitmap to be used when syncing the cache with + * the hardware. Each time a register is modified, the corresponding + * bit is set in the bitmap, so we know that we have to sync + * that register. + */ + bmp_size = map->num_reg_defaults_raw; + sync_bmp = kmalloc(BITS_TO_LONGS(bmp_size) * sizeof(long), + GFP_KERNEL); + if (!sync_bmp) { + ret = -ENOMEM; + goto err; + } + bitmap_zero(sync_bmp, bmp_size); + + /* allocate the lzo blocks and initialize them */ + for (i = 0; i < blkcount; i++) { + lzo_blocks[i] = kzalloc(sizeof **lzo_blocks, + GFP_KERNEL); + if (!lzo_blocks[i]) { + kfree(sync_bmp); + ret = -ENOMEM; + goto err; + } + lzo_blocks[i]->sync_bmp = sync_bmp; + lzo_blocks[i]->sync_bmp_nbits = bmp_size; + /* alloc the working space for the compressed block */ + ret = regcache_lzo_prepare(lzo_blocks[i]); + if (ret < 0) + goto err; + } + + blksize = regcache_lzo_get_blksize(map); + p = map->reg_defaults_raw; + end = map->reg_defaults_raw + map->cache_size_raw; + /* compress the register map and fill the lzo blocks */ + for (i = 0; i < blkcount; i++, p += blksize) { + lzo_blocks[i]->src = p; + if (p + blksize > end) + lzo_blocks[i]->src_len = end - p; + else + lzo_blocks[i]->src_len = blksize; + ret = regcache_lzo_compress_cache_block(map, + lzo_blocks[i]); + if (ret < 0) + goto err; + lzo_blocks[i]->decompressed_size = + lzo_blocks[i]->src_len; + } + + return 0; +err: + regcache_exit(map); + return ret; +} + +static int regcache_lzo_exit(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + int i, blkcount; + + lzo_blocks = map->cache; + if (!lzo_blocks) + return 0; + + blkcount = regcache_lzo_block_count(); + /* + * the pointer to the bitmap used for syncing the cache + * is shared amongst all lzo_blocks. Ensure it is freed + * only once. + */ + if (lzo_blocks[0]) + kfree(lzo_blocks[0]->sync_bmp); + for (i = 0; i < blkcount; i++) { + if (lzo_blocks[i]) { + kfree(lzo_blocks[i]->wmem); + kfree(lzo_blocks[i]->dst); + } + /* each lzo_block is a pointer returned by kmalloc or NULL */ + kfree(lzo_blocks[i]); + } + kfree(lzo_blocks); + map->cache = NULL; + return 0; +} + +static int regcache_lzo_read(struct regmap *map, + unsigned int reg, unsigned int *value) +{ + struct regcache_lzo_ctx *lzo_block, **lzo_blocks; + int ret, blkindex, blkpos; + size_t blksize, tmp_dst_len; + void *tmp_dst; + + *value = 0; + /* index of the compressed lzo block */ + blkindex = regcache_lzo_get_blkindex(map, reg); + /* register index within the decompressed block */ + blkpos = regcache_lzo_get_blkpos(map, reg); + /* size of the compressed block */ + blksize = regcache_lzo_get_blksize(map); + lzo_blocks = map->cache; + lzo_block = lzo_blocks[blkindex]; + + /* save the pointer and length of the compressed block */ + tmp_dst = lzo_block->dst; + tmp_dst_len = lzo_block->dst_len; + + /* prepare the source to be the compressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* decompress the block */ + ret = regcache_lzo_decompress_cache_block(map, lzo_block); + if (ret >= 0) + /* fetch the value from the cache */ + *value = regcache_get_val(lzo_block->dst, blkpos, + map->cache_word_size); + + kfree(lzo_block->dst); + /* restore the pointer and length of the compressed block */ + lzo_block->dst = tmp_dst; + lzo_block->dst_len = tmp_dst_len; + return 0; +} + +static int regcache_lzo_write(struct regmap *map, + unsigned int reg, unsigned int value) +{ + struct regcache_lzo_ctx *lzo_block, **lzo_blocks; + int ret, blkindex, blkpos; + size_t blksize, tmp_dst_len; + void *tmp_dst; + + /* index of the compressed lzo block */ + blkindex = regcache_lzo_get_blkindex(map, reg); + /* register index within the decompressed block */ + blkpos = regcache_lzo_get_blkpos(map, reg); + /* size of the compressed block */ + blksize = regcache_lzo_get_blksize(map); + lzo_blocks = map->cache; + lzo_block = lzo_blocks[blkindex]; + + /* save the pointer and length of the compressed block */ + tmp_dst = lzo_block->dst; + tmp_dst_len = lzo_block->dst_len; + + /* prepare the source to be the compressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* decompress the block */ + ret = regcache_lzo_decompress_cache_block(map, lzo_block); + if (ret < 0) { + kfree(lzo_block->dst); + goto out; + } + + /* write the new value to the cache */ + if (regcache_set_val(lzo_block->dst, blkpos, value, + map->cache_word_size)) { + kfree(lzo_block->dst); + goto out; + } + + /* prepare the source to be the decompressed block */ + lzo_block->src = lzo_block->dst; + lzo_block->src_len = lzo_block->dst_len; + + /* compress the block */ + ret = regcache_lzo_compress_cache_block(map, lzo_block); + if (ret < 0) { + kfree(lzo_block->dst); + kfree(lzo_block->src); + goto out; + } + + /* set the bit so we know we have to sync this register */ + set_bit(reg, lzo_block->sync_bmp); + kfree(tmp_dst); + kfree(lzo_block->src); + return 0; +out: + lzo_block->dst = tmp_dst; + lzo_block->dst_len = tmp_dst_len; + return ret; +} + +static int regcache_lzo_sync(struct regmap *map) +{ + struct regcache_lzo_ctx **lzo_blocks; + unsigned int val; + int i; + int ret; + + lzo_blocks = map->cache; + for_each_set_bit(i, lzo_blocks[0]->sync_bmp, lzo_blocks[0]->sync_bmp_nbits) { + ret = regcache_read(map, i, &val); + if (ret) + return ret; + map->cache_bypass = 1; + ret = regmap_write(map, i, val); + map->cache_bypass = 0; + if (ret) + return ret; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + i, val); + } + + return 0; +} + +struct regcache_ops regcache_lzo_ops = { + .type = REGCACHE_LZO, + .name = "lzo", + .init = regcache_lzo_init, + .exit = regcache_lzo_exit, + .read = regcache_lzo_read, + .write = regcache_lzo_write, + .sync = regcache_lzo_sync +}; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index b870a668b771..142d9cdfef3a 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -18,6 +18,7 @@ static const struct regcache_ops *cache_types[] = { ®cache_indexed_ops, ®cache_rbtree_ops, + ®cache_lzo_ops, }; static int regcache_hw_init(struct regmap *map) -- cgit v1.2.1 From 593600890110c02eb471cf844649dee213870416 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:04 +0100 Subject: regmap: Add the regcache_sync trace event Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 142d9cdfef3a..00609bf785fb 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -221,12 +221,18 @@ EXPORT_SYMBOL_GPL(regcache_write); */ int regcache_sync(struct regmap *map) { + int ret; + const char *name; + BUG_ON(!map->cache_ops); if (map->cache_ops->sync) { dev_dbg(map->dev, "Syncing %s cache\n", map->cache_ops->name); - return map->cache_ops->sync(map); + name = map->cache_ops->name; + trace_regcache_sync(map->dev, name, "start"); + ret = map->cache_ops->sync(map); + trace_regcache_sync(map->dev, name, "stop"); } return 0; } -- cgit v1.2.1 From 5d1729e7f02f050c73b68ce0198f8e5c48e9608a Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 19 Sep 2011 14:34:05 +0100 Subject: regmap: Incorporate the regcache core into regmap This patch incorporates the regcache core code into regmap. All previous patches have been no-ops essentially up to this point. The bulk read operation is not supported by regcache at the moment. This will be implemented incrementally. Signed-off-by: Dimitris Papastamos Tested-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e7adfe70e425..35964659a81d 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -146,6 +146,13 @@ struct regmap *regmap_init(struct device *dev, map->readable_reg = config->readable_reg; map->volatile_reg = config->volatile_reg; map->precious_reg = config->precious_reg; + map->cache_type = config->cache_type; + map->reg_defaults = config->reg_defaults; + map->num_reg_defaults = config->num_reg_defaults; + map->num_reg_defaults_raw = config->num_reg_defaults_raw; + map->reg_defaults_raw = config->reg_defaults_raw; + map->cache_size_raw = (config->val_bits / 8) * config->num_reg_defaults_raw; + map->cache_word_size = config->val_bits / 8; if (config->read_flag_mask || config->write_flag_mask) { map->read_flag_mask = config->read_flag_mask; @@ -208,6 +215,10 @@ struct regmap *regmap_init(struct device *dev, goto err_map; } + ret = regcache_init(map); + if (ret < 0) + goto err_map; + regmap_debugfs_init(map); return map; @@ -224,6 +235,7 @@ EXPORT_SYMBOL_GPL(regmap_init); */ void regmap_exit(struct regmap *map) { + regcache_exit(map); regmap_debugfs_exit(map); kfree(map->work_buf); kfree(map); @@ -290,6 +302,14 @@ static int _regmap_write(struct regmap *map, unsigned int reg, int ret; BUG_ON(!map->format.format_write && !map->format.format_val); + if (!map->cache_bypass) { + ret = regcache_write(map, reg, val); + if (ret != 0) + return ret; + if (map->cache_only) + return 0; + } + trace_regmap_reg_write(map->dev, reg, val); if (map->format.format_write) { @@ -403,6 +423,15 @@ static int _regmap_read(struct regmap *map, unsigned int reg, if (!map->format.parse_val) return -EINVAL; + if (!map->cache_bypass) { + ret = regcache_read(map, reg, val); + if (ret == 0) + return 0; + } + + if (map->cache_only) + return -EBUSY; + ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes); if (ret == 0) { *val = map->format.parse_val(map->work_buf); @@ -479,6 +508,8 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, int ret, i; size_t val_bytes = map->format.val_bytes; + WARN_ON(map->cache_type != REGCACHE_NONE); + if (!map->format.parse_val) return -EINVAL; -- cgit v1.2.1 From e7a6db30df42234bc0f7b9a0af402838e0f146b1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 19 Sep 2011 16:08:03 +0100 Subject: regmap: A cache type of _NONE behaves like a bypassed cache Avoid extra special casing by setting the cache_bypass flag when we're not caching. Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 00609bf785fb..179f222d76c3 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -82,8 +82,10 @@ int regcache_init(struct regmap *map) int i; void *tmp_buf; - if (map->cache_type == REGCACHE_NONE) + if (map->cache_type == REGCACHE_NONE) { + map->cache_bypass = true; return 0; + } for (i = 0; i < ARRAY_SIZE(cache_types); i++) if (cache_types[i]->type == map->cache_type) -- cgit v1.2.1 From 92afb286d744511f51a05f8acb6c111d05737617 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 19 Sep 2011 18:22:14 +0100 Subject: regmap: Allow drivers to control cache_only flag Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 179f222d76c3..e2b172b93dba 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -240,6 +240,24 @@ int regcache_sync(struct regmap *map) } EXPORT_SYMBOL_GPL(regcache_sync); +/** + * regcache_cache_only: Put a register map into cache only mode + * + * @map: map to configure + * @cache_only: flag if changes should be written to the hardware + * + * When a register map is marked as cache only writes to the register + * map API will only update the register cache, they will not cause + * any hardware changes. This is useful for allowing portions of + * drivers to act as though the device were functioning as normal when + * it is disabled for power saving reasons. + */ +void regcache_cache_only(struct regmap *map, bool enable) +{ + map->cache_only = enable; +} +EXPORT_SYMBOL_GPL(regcache_cache_only); + bool regcache_set_val(void *base, unsigned int idx, unsigned int val, unsigned int word_size) { -- cgit v1.2.1 From 25ed1156ddf99f6d8feb87d0992b2ecb1fef667a Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Tue, 27 Sep 2011 11:25:07 +0100 Subject: regmap: Remove redundant member `word_size' from regcache_rbtree_node Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 53 ++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 23 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 4d7ba4511755..dd1b937a0d84 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -23,8 +23,6 @@ struct regcache_rbtree_node { struct rb_node node; /* base register handled by this block */ unsigned int base_reg; - /* number of bytes needed to represent the register index */ - unsigned int word_size; /* block of adjacent registers */ void *block; /* number of registers available in the block */ @@ -45,11 +43,12 @@ static inline void regcache_rbtree_get_base_top_reg( } static unsigned int regcache_rbtree_get_register( - struct regcache_rbtree_node *rbnode, unsigned int idx) + struct regcache_rbtree_node *rbnode, unsigned int idx, + unsigned int word_size) { unsigned int val; - switch (rbnode->word_size) { + switch (word_size) { case 1: { u8 *p = rbnode->block; val = p[idx]; @@ -68,9 +67,10 @@ static unsigned int regcache_rbtree_get_register( } static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode, - unsigned int idx, unsigned int val) + unsigned int idx, unsigned int val, + unsigned int word_size) { - switch (rbnode->word_size) { + switch (word_size) { case 1: { u8 *p = rbnode->block; p[idx] = val; @@ -217,7 +217,8 @@ static int regcache_rbtree_read(struct regmap *map, regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); if (reg >= base_reg && reg <= top_reg) { reg_tmp = reg - base_reg; - *value = regcache_rbtree_get_register(rbnode, reg_tmp); + *value = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); return 0; } } @@ -227,7 +228,8 @@ static int regcache_rbtree_read(struct regmap *map, rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { reg_tmp = reg - rbnode->base_reg; - *value = regcache_rbtree_get_register(rbnode, reg_tmp); + *value = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); rbtree_ctx->cached_rbnode = rbnode; } else { /* uninitialized registers default to 0 */ @@ -240,19 +242,19 @@ static int regcache_rbtree_read(struct regmap *map, static int regcache_rbtree_insert_to_block(struct regcache_rbtree_node *rbnode, unsigned int pos, unsigned int reg, - unsigned int value) + unsigned int value, unsigned int word_size) { u8 *blk; blk = krealloc(rbnode->block, - (rbnode->blklen + 1) * rbnode->word_size, GFP_KERNEL); + (rbnode->blklen + 1) * word_size, GFP_KERNEL); if (!blk) return -ENOMEM; /* insert the register value in the correct place in the rbnode block */ - memmove(blk + (pos + 1) * rbnode->word_size, - blk + pos * rbnode->word_size, - (rbnode->blklen - pos) * rbnode->word_size); + memmove(blk + (pos + 1) * word_size, + blk + pos * word_size, + (rbnode->blklen - pos) * word_size); /* update the rbnode block, its size and the base register */ rbnode->block = blk; @@ -260,7 +262,7 @@ static int regcache_rbtree_insert_to_block(struct regcache_rbtree_node *rbnode, if (!pos) rbnode->base_reg = reg; - regcache_rbtree_set_register(rbnode, pos, value); + regcache_rbtree_set_register(rbnode, pos, value, word_size); return 0; } @@ -284,10 +286,12 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); if (reg >= base_reg && reg <= top_reg) { reg_tmp = reg - base_reg; - val = regcache_rbtree_get_register(rbnode, reg_tmp); + val = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); if (val == value) return 0; - regcache_rbtree_set_register(rbnode, reg_tmp, value); + regcache_rbtree_set_register(rbnode, reg_tmp, value, + map->cache_word_size); return 0; } } @@ -297,10 +301,12 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); if (rbnode) { reg_tmp = reg - rbnode->base_reg; - val = regcache_rbtree_get_register(rbnode, reg_tmp); + val = regcache_rbtree_get_register(rbnode, reg_tmp, + map->cache_word_size); if (val == value) return 0; - regcache_rbtree_set_register(rbnode, reg_tmp, value); + regcache_rbtree_set_register(rbnode, reg_tmp, value, + map->cache_word_size); rbtree_ctx->cached_rbnode = rbnode; } else { /* bail out early, no need to create the rbnode yet */ @@ -320,7 +326,8 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, else pos = i; ret = regcache_rbtree_insert_to_block(rbnode_tmp, pos, - reg, value); + reg, value, + map->cache_word_size); if (ret) return ret; rbtree_ctx->cached_rbnode = rbnode_tmp; @@ -337,14 +344,13 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, return -ENOMEM; rbnode->blklen = 1; rbnode->base_reg = reg; - rbnode->word_size = map->cache_word_size; - rbnode->block = kmalloc(rbnode->blklen * rbnode->word_size, + rbnode->block = kmalloc(rbnode->blklen * map->cache_word_size, GFP_KERNEL); if (!rbnode->block) { kfree(rbnode); return -ENOMEM; } - regcache_rbtree_set_register(rbnode, 0, value); + regcache_rbtree_set_register(rbnode, 0, value, map->cache_word_size); regcache_rbtree_insert(&rbtree_ctx->root, rbnode); rbtree_ctx->cached_rbnode = rbnode; } @@ -367,7 +373,8 @@ static int regcache_rbtree_sync(struct regmap *map) rbnode = rb_entry(node, struct regcache_rbtree_node, node); for (i = 0; i < rbnode->blklen; i++) { regtmp = rbnode->base_reg + i; - val = regcache_rbtree_get_register(rbnode, i); + val = regcache_rbtree_get_register(rbnode, i, + map->cache_word_size); ret = regcache_lookup_reg(map, i); if (ret < 0) def = 0; -- cgit v1.2.1 From dfdc4448e078d06bdba0da52db7176437877788f Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Tue, 27 Sep 2011 11:25:04 +0100 Subject: regmap: Fix signed/unsigned comparison Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-indexed.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-indexed.c b/drivers/base/regmap/regcache-indexed.c index ff8b44ce044b..268497aee46d 100644 --- a/drivers/base/regmap/regcache-indexed.c +++ b/drivers/base/regmap/regcache-indexed.c @@ -41,7 +41,7 @@ static int regcache_indexed_write(struct regmap *map, unsigned int reg, static int regcache_indexed_sync(struct regmap *map) { - int i; + unsigned int i; int ret; for (i = 0; i < map->num_reg_defaults; i++) { -- cgit v1.2.1 From 954757d767a78bc4b863fa9ea703bd7f814c8a55 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Tue, 27 Sep 2011 11:25:06 +0100 Subject: regmap: Implement generic syncing functionality In the absence of a sync callback, do it manually. This of course can't take advantange of the specific optimizations of each cache type but it will do well enough in most cases. Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index e2b172b93dba..6b9efd938dca 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -223,20 +223,39 @@ EXPORT_SYMBOL_GPL(regcache_write); */ int regcache_sync(struct regmap *map) { - int ret; + int ret = 0; + unsigned int val; + unsigned int i; const char *name; BUG_ON(!map->cache_ops); + dev_dbg(map->dev, "Syncing %s cache\n", + map->cache_ops->name); + name = map->cache_ops->name; + trace_regcache_sync(map->dev, name, "start"); if (map->cache_ops->sync) { - dev_dbg(map->dev, "Syncing %s cache\n", - map->cache_ops->name); - name = map->cache_ops->name; - trace_regcache_sync(map->dev, name, "start"); ret = map->cache_ops->sync(map); - trace_regcache_sync(map->dev, name, "stop"); + } else { + for (i = 0; i < map->num_reg_defaults; i++) { + ret = regcache_read(map, i, &val); + if (ret < 0) + goto out; + regcache_cache_bypass(map, true); + ret = regcache_write(map, i, val); + regcache_cache_bypass(map, false); + if (ret < 0) + goto out; + dev_dbg(map->dev, "Synced register %#x, value %#x\n", + map->reg_defaults[i].reg, + map->reg_defaults[i].def); + } + } - return 0; +out: + trace_regcache_sync(map->dev, name, "stop"); + + return ret; } EXPORT_SYMBOL_GPL(regcache_sync); -- cgit v1.2.1 From c5713004b304e89c8c5117d8f226d5a1603571dc Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 27 Sep 2011 20:15:37 +0200 Subject: regmap: regcache_rbtree_{set,get}_register: Use regcache_{set,get}_val Use regcache_{set,get}_val in regcache_rbtree_{set,get}_register instead of re-implementing its functionality. Signed-off-by: Lars-Peter Clausen Acked-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 35 ++--------------------------------- 1 file changed, 2 insertions(+), 33 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index dd1b937a0d84..52669dec73b3 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -46,45 +46,14 @@ static unsigned int regcache_rbtree_get_register( struct regcache_rbtree_node *rbnode, unsigned int idx, unsigned int word_size) { - unsigned int val; - - switch (word_size) { - case 1: { - u8 *p = rbnode->block; - val = p[idx]; - return val; - } - case 2: { - u16 *p = rbnode->block; - val = p[idx]; - return val; - } - default: - BUG(); - break; - } - return -1; + return regcache_get_val(rbnode->block, idx, word_size); } static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode, unsigned int idx, unsigned int val, unsigned int word_size) { - switch (word_size) { - case 1: { - u8 *p = rbnode->block; - p[idx] = val; - break; - } - case 2: { - u16 *p = rbnode->block; - p[idx] = val; - break; - } - default: - BUG(); - break; - } + regcache_set_val(rbnode->block, idx, val, word_size); } static struct regcache_rbtree_node *regcache_rbtree_lookup( -- cgit v1.2.1 From 3405addd220a0cf2e3a8ffb9051afe766e5f52e8 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Tue, 27 Sep 2011 20:15:38 +0200 Subject: regmap: rbtree-cache: Move cached rbnode handling into lookup function Move the handling of the cached rbnode into regcache_rbtree_lookup. This allows us to remove of some duplicated code sections in regcache_rbtree_read and regcache_rbtree_write. Signed-off-by: Lars-Peter Clausen Acked-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-rbtree.c | 61 +++++++++++------------------------ 1 file changed, 18 insertions(+), 43 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 52669dec73b3..de32ced1917a 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -56,23 +56,33 @@ static void regcache_rbtree_set_register(struct regcache_rbtree_node *rbnode, regcache_set_val(rbnode->block, idx, val, word_size); } -static struct regcache_rbtree_node *regcache_rbtree_lookup( - struct rb_root *root, unsigned int reg) +static struct regcache_rbtree_node *regcache_rbtree_lookup(struct regmap *map, + unsigned int reg) { + struct regcache_rbtree_ctx *rbtree_ctx = map->cache; struct rb_node *node; struct regcache_rbtree_node *rbnode; unsigned int base_reg, top_reg; - node = root->rb_node; + rbnode = rbtree_ctx->cached_rbnode; + if (rbnode) { + regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); + if (reg >= base_reg && reg <= top_reg) + return rbnode; + } + + node = rbtree_ctx->root.rb_node; while (node) { rbnode = container_of(node, struct regcache_rbtree_node, node); regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); - if (reg >= base_reg && reg <= top_reg) + if (reg >= base_reg && reg <= top_reg) { + rbtree_ctx->cached_rbnode = rbnode; return rbnode; - else if (reg > top_reg) + } else if (reg > top_reg) { node = node->rb_right; - else if (reg < base_reg) + } else if (reg < base_reg) { node = node->rb_left; + } } return NULL; @@ -174,32 +184,14 @@ static int regcache_rbtree_exit(struct regmap *map) static int regcache_rbtree_read(struct regmap *map, unsigned int reg, unsigned int *value) { - struct regcache_rbtree_ctx *rbtree_ctx; struct regcache_rbtree_node *rbnode; - unsigned int base_reg, top_reg; unsigned int reg_tmp; - rbtree_ctx = map->cache; - /* look up the required register in the cached rbnode */ - rbnode = rbtree_ctx->cached_rbnode; - if (rbnode) { - regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); - if (reg >= base_reg && reg <= top_reg) { - reg_tmp = reg - base_reg; - *value = regcache_rbtree_get_register(rbnode, reg_tmp, - map->cache_word_size); - return 0; - } - } - /* if we can't locate it in the cached rbnode we'll have - * to traverse the rbtree looking for it. - */ - rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); + rbnode = regcache_rbtree_lookup(map, reg); if (rbnode) { reg_tmp = reg - rbnode->base_reg; *value = regcache_rbtree_get_register(rbnode, reg_tmp, map->cache_word_size); - rbtree_ctx->cached_rbnode = rbnode; } else { /* uninitialized registers default to 0 */ *value = 0; @@ -243,31 +235,15 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, struct rb_node *node; unsigned int val; unsigned int reg_tmp; - unsigned int base_reg, top_reg; unsigned int pos; int i; int ret; rbtree_ctx = map->cache; - /* look up the required register in the cached rbnode */ - rbnode = rbtree_ctx->cached_rbnode; - if (rbnode) { - regcache_rbtree_get_base_top_reg(rbnode, &base_reg, &top_reg); - if (reg >= base_reg && reg <= top_reg) { - reg_tmp = reg - base_reg; - val = regcache_rbtree_get_register(rbnode, reg_tmp, - map->cache_word_size); - if (val == value) - return 0; - regcache_rbtree_set_register(rbnode, reg_tmp, value, - map->cache_word_size); - return 0; - } - } /* if we can't locate it in the cached rbnode we'll have * to traverse the rbtree looking for it. */ - rbnode = regcache_rbtree_lookup(&rbtree_ctx->root, reg); + rbnode = regcache_rbtree_lookup(map, reg); if (rbnode) { reg_tmp = reg - rbnode->base_reg; val = regcache_rbtree_get_register(rbnode, reg_tmp, @@ -276,7 +252,6 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, return 0; regcache_rbtree_set_register(rbnode, reg_tmp, value, map->cache_word_size); - rbtree_ctx->cached_rbnode = rbnode; } else { /* bail out early, no need to create the rbnode yet */ if (!value) -- cgit v1.2.1 From a40c282362419b8bccb75cea081992f535841085 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Wed, 28 Sep 2011 11:43:41 +0100 Subject: regmap: Fix regcache_sync generic implementation We want to use regmap_write() to actually write anything to the HW. Signed-off-by: Dimitris Papastamos 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 6b9efd938dca..5364dde2ecd0 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -242,7 +242,7 @@ int regcache_sync(struct regmap *map) if (ret < 0) goto out; regcache_cache_bypass(map, true); - ret = regcache_write(map, i, val); + ret = regmap_write(map, i, val); regcache_cache_bypass(map, false); if (ret < 0) goto out; -- cgit v1.2.1 From ec8a365fe62c78a16268bd1d12dfbebc2b775991 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Wed, 28 Sep 2011 11:43:42 +0100 Subject: regmap: Modify map->cache_bypass directly In preperation for the upcoming patches, modify map->cache_bypass directly. The helper functions will grab an exclusive lock. Because we'll have acquired the same lock we need to avoid a deadlock. Signed-off-by: Dimitris Papastamos 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 5364dde2ecd0..f46e247912cb 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -241,9 +241,9 @@ int regcache_sync(struct regmap *map) ret = regcache_read(map, i, &val); if (ret < 0) goto out; - regcache_cache_bypass(map, true); + map->cache_bypass = 1; ret = regmap_write(map, i, val); - regcache_cache_bypass(map, false); + map->cache_bypass = 0; if (ret < 0) goto out; dev_dbg(map->dev, "Synced register %#x, value %#x\n", -- cgit v1.2.1 From 38f6916976f9e964748f097be3688e334fb60f3d Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Wed, 28 Sep 2011 11:43:45 +0100 Subject: regmap: Grab the lock in regcache_cache_only() Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index f46e247912cb..744ed145bfa4 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -273,7 +273,9 @@ EXPORT_SYMBOL_GPL(regcache_sync); */ void regcache_cache_only(struct regmap *map, bool enable) { + mutex_lock(&map->sync_lock); map->cache_only = enable; + mutex_unlock(&map->sync_lock); } EXPORT_SYMBOL_GPL(regcache_cache_only); -- cgit v1.2.1 From 2cd148f1599a425f0f3ac6753da96a1a1aa3ce76 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 29 Sep 2011 10:40:55 +0100 Subject: regmap: Fix lock used for regcache_cache_only() Reported-by: Stephen Rothwell 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 744ed145bfa4..4dfab4107bfe 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -273,9 +273,9 @@ EXPORT_SYMBOL_GPL(regcache_sync); */ void regcache_cache_only(struct regmap *map, bool enable) { - mutex_lock(&map->sync_lock); + mutex_lock(&map->lock); map->cache_only = enable; - mutex_unlock(&map->sync_lock); + mutex_unlock(&map->lock); } EXPORT_SYMBOL_GPL(regcache_cache_only); -- cgit v1.2.1 From 4d2dc09538561eb8823c3c0072e6f5b868a5abe1 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 29 Sep 2011 10:39:07 +0100 Subject: regmap: Make _regmap_write() global Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/internal.h | 3 +++ drivers/base/regmap/regmap.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 2d51b1b099f7..348ff02eb93e 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -91,6 +91,9 @@ bool regmap_readable(struct regmap *map, unsigned int reg); bool regmap_volatile(struct regmap *map, unsigned int reg); bool regmap_precious(struct regmap *map, unsigned int reg); +int _regmap_write(struct regmap *map, unsigned int reg, + unsigned int val); + #ifdef CONFIG_DEBUG_FS extern void regmap_debugfs_initcall(void); extern void regmap_debugfs_init(struct regmap *map); diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 35964659a81d..d786ddcaf117 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -296,8 +296,8 @@ static int _regmap_raw_write(struct regmap *map, unsigned int reg, return ret; } -static int _regmap_write(struct regmap *map, unsigned int reg, - unsigned int val) +int _regmap_write(struct regmap *map, unsigned int reg, + unsigned int val) { int ret; BUG_ON(!map->format.format_write && !map->format.format_val); -- cgit v1.2.1 From 5fcd2560767cead8f0c741340e132c5417d9f73b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 29 Sep 2011 15:24:54 +0100 Subject: regmap: Fix apostrophe usage An apostrophe does not mean "look out, here comes an s!". 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 4dfab4107bfe..2caf6e49c389 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -118,7 +118,7 @@ int regcache_init(struct regmap *map) return -ENOMEM; map->reg_defaults = tmp_buf; } else { - /* Some devices such as PMIC's don't have cache defaults, + /* Some devices such as PMICs don't have cache defaults, * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. */ -- cgit v1.2.1 From 13753a9088af23c61e2f5c10a8f3ea136d8ebab5 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 29 Sep 2011 14:36:25 +0100 Subject: regmap: Lock the sync path, ensure we use the lockless _regmap_write() Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache-indexed.c | 4 ++-- drivers/base/regmap/regcache-lzo.c | 2 +- drivers/base/regmap/regcache-rbtree.c | 2 +- drivers/base/regmap/regcache.c | 4 +++- 4 files changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-indexed.c b/drivers/base/regmap/regcache-indexed.c index 268497aee46d..2e10bb13bfc4 100644 --- a/drivers/base/regmap/regcache-indexed.c +++ b/drivers/base/regmap/regcache-indexed.c @@ -45,8 +45,8 @@ static int regcache_indexed_sync(struct regmap *map) int ret; for (i = 0; i < map->num_reg_defaults; i++) { - ret = regmap_write(map, map->reg_defaults[i].reg, - map->reg_defaults[i].def); + ret = _regmap_write(map, map->reg_defaults[i].reg, + map->reg_defaults[i].def); if (ret < 0) return ret; dev_dbg(map->dev, "Synced register %#x, value %#x\n", diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c index 9079cb50b0b9..ad6af925f56c 100644 --- a/drivers/base/regmap/regcache-lzo.c +++ b/drivers/base/regmap/regcache-lzo.c @@ -339,7 +339,7 @@ static int regcache_lzo_sync(struct regmap *map) if (ret) return ret; map->cache_bypass = 1; - ret = regmap_write(map, i, val); + ret = _regmap_write(map, i, val); map->cache_bypass = 0; if (ret) return ret; diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index de32ced1917a..40f23dd8478c 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -327,7 +327,7 @@ static int regcache_rbtree_sync(struct regmap *map) if (val == def) continue; map->cache_bypass = 1; - ret = regmap_write(map, regtmp, val); + ret = _regmap_write(map, regtmp, val); map->cache_bypass = 0; if (ret) return ret; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 2caf6e49c389..59e432c0163d 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -230,6 +230,7 @@ int regcache_sync(struct regmap *map) BUG_ON(!map->cache_ops); + mutex_lock(&map->lock); dev_dbg(map->dev, "Syncing %s cache\n", map->cache_ops->name); name = map->cache_ops->name; @@ -242,7 +243,7 @@ int regcache_sync(struct regmap *map) if (ret < 0) goto out; map->cache_bypass = 1; - ret = regmap_write(map, i, val); + ret = _regmap_write(map, i, val); map->cache_bypass = 0; if (ret < 0) goto out; @@ -254,6 +255,7 @@ int regcache_sync(struct regmap *map) } out: trace_regcache_sync(map->dev, name, "stop"); + mutex_unlock(&map->lock); return ret; } -- cgit v1.2.1 From beb1a10f219ce720c13168203bd5ebe4ce7879e0 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 29 Sep 2011 14:36:26 +0100 Subject: regmap: Save/restore the bypass state upon syncing Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 59e432c0163d..5dbc5076267e 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -227,10 +227,13 @@ int regcache_sync(struct regmap *map) unsigned int val; unsigned int i; const char *name; + unsigned int bypass; BUG_ON(!map->cache_ops); mutex_lock(&map->lock); + /* Remember the initial bypass state */ + bypass = map->cache_bypass; dev_dbg(map->dev, "Syncing %s cache\n", map->cache_ops->name); name = map->cache_ops->name; @@ -255,6 +258,8 @@ int regcache_sync(struct regmap *map) } out: trace_regcache_sync(map->dev, name, "stop"); + /* Restore the bypass state */ + map->cache_bypass = bypass; mutex_unlock(&map->lock); return ret; -- cgit v1.2.1 From 6eb0f5e0154facfe4f0acdb9f474cde773319efc Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 29 Sep 2011 14:36:27 +0100 Subject: regmap: Implement regcache_cache_bypass helper function Ensure we've got a function so users can enable/disable the cache bypass option. Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 5dbc5076267e..876622453cd8 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -286,6 +286,25 @@ void regcache_cache_only(struct regmap *map, bool enable) } EXPORT_SYMBOL_GPL(regcache_cache_only); +/** + * regcache_cache_bypass: Put a register map into cache bypass mode + * + * @map: map to configure + * @cache_only: flag if changes should not be written to the hardware + * + * When a register map is marked with the cache bypass option, writes + * to the register map API will only update the hardware and not the + * the cache directly. This is useful when syncing the cache back to + * the hardware. + */ +void regcache_cache_bypass(struct regmap *map, bool enable) +{ + mutex_lock(&map->lock); + map->cache_bypass = enable; + mutex_unlock(&map->lock); +} +EXPORT_SYMBOL_GPL(regcache_cache_bypass); + bool regcache_set_val(void *base, unsigned int idx, unsigned int val, unsigned int word_size) { -- cgit v1.2.1 From ac77a765cb6e3b5aa41c186ad9f37db7fdad7dbe Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 29 Sep 2011 14:36:28 +0100 Subject: regmap: Ensure we scream if we enable cache bypass/only at the same time Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 876622453cd8..2d55b261f1c5 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -281,6 +281,7 @@ EXPORT_SYMBOL_GPL(regcache_sync); void regcache_cache_only(struct regmap *map, bool enable) { mutex_lock(&map->lock); + WARN_ON(map->cache_bypass && enable); map->cache_only = enable; mutex_unlock(&map->lock); } @@ -300,6 +301,7 @@ EXPORT_SYMBOL_GPL(regcache_cache_only); void regcache_cache_bypass(struct regmap *map, bool enable) { mutex_lock(&map->lock); + WARN_ON(map->cache_only && enable); map->cache_bypass = enable; mutex_unlock(&map->lock); } -- cgit v1.2.1 From c08604b8ae72b4fa1843a76fc7b403ddec49f8f4 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 3 Oct 2011 10:50:14 +0100 Subject: regmap: Optimize the lookup path to use binary search Since there are more lookups than insertions in a typical scenario, optimize the linear search into a binary search. For this to work, we need to keep reg_defaults sorted upon insertions, for now be lazy and use sort(). Signed-off-by: Dimitris Papastamos Signed-off-by: Mark Brown --- drivers/base/regmap/regcache.c | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index 2d55b261f1c5..e1846eb2f171 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -12,6 +12,7 @@ #include #include +#include #include "internal.h" @@ -356,14 +357,30 @@ unsigned int regcache_get_val(const void *base, unsigned int idx, int regcache_lookup_reg(struct regmap *map, unsigned int reg) { - unsigned int i; - - for (i = 0; i < map->num_reg_defaults; i++) - if (map->reg_defaults[i].reg == reg) - return i; + unsigned int min, max, index; + + min = 0; + max = map->num_reg_defaults - 1; + do { + index = (min + max) / 2; + if (map->reg_defaults[index].reg == reg) + return index; + if (map->reg_defaults[index].reg < reg) + min = index + 1; + else + max = index; + } while (min <= max); return -1; } +static int regcache_insert_cmp(const void *a, const void *b) +{ + const struct reg_default *_a = a; + const struct reg_default *_b = b; + + return _a->reg - _b->reg; +} + int regcache_insert_reg(struct regmap *map, unsigned int reg, unsigned int val) { @@ -378,5 +395,7 @@ int regcache_insert_reg(struct regmap *map, unsigned int reg, map->num_reg_defaults++; map->reg_defaults[map->num_reg_defaults - 1].reg = reg; map->reg_defaults[map->num_reg_defaults - 1].def = val; + sort(map->reg_defaults, map->num_reg_defaults, + sizeof(struct reg_default), regcache_insert_cmp, NULL); return 0; } -- cgit v1.2.1 From 0eef6b0415f58ed16aff95af8c92514ce5c01258 Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Mon, 3 Oct 2011 06:54:16 +0100 Subject: regmap: Fix doc comment Signed-off-by: Dimitris Papastamos 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 e1846eb2f171..b10e38fa0e3f 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -292,7 +292,7 @@ EXPORT_SYMBOL_GPL(regcache_cache_only); * regcache_cache_bypass: Put a register map into cache bypass mode * * @map: map to configure - * @cache_only: flag if changes should not be written to the hardware + * @cache_bypass: flag if changes should not be written to the hardware * * When a register map is marked with the cache bypass option, writes * to the register map API will only update the hardware and not the -- cgit v1.2.1 From f094fea68f0575286c55c06141cc89ffd0049024 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 4 Oct 2011 22:05:47 +0100 Subject: regmap: Use bsearch() to search the register defaults Rather than open coding a binary search use the standard bsearch() using the comparison function we're already using for sort() on insert. This fixes a lockup I was observing due to iterating on min <= max rather than min < max when we fail to look up. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- drivers/base/regmap/regcache.c | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index b10e38fa0e3f..c5379c86de88 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -12,6 +12,7 @@ #include #include +#include #include #include "internal.h" @@ -355,25 +356,7 @@ unsigned int regcache_get_val(const void *base, unsigned int idx, return -1; } -int regcache_lookup_reg(struct regmap *map, unsigned int reg) -{ - unsigned int min, max, index; - - min = 0; - max = map->num_reg_defaults - 1; - do { - index = (min + max) / 2; - if (map->reg_defaults[index].reg == reg) - return index; - if (map->reg_defaults[index].reg < reg) - min = index + 1; - else - max = index; - } while (min <= max); - return -1; -} - -static int regcache_insert_cmp(const void *a, const void *b) +static int regcache_default_cmp(const void *a, const void *b) { const struct reg_default *_a = a; const struct reg_default *_b = b; @@ -381,6 +364,23 @@ static int regcache_insert_cmp(const void *a, const void *b) return _a->reg - _b->reg; } +int regcache_lookup_reg(struct regmap *map, unsigned int reg) +{ + struct reg_default key; + struct reg_default *r; + + key.reg = reg; + key.def = 0; + + r = bsearch(&key, map->reg_defaults, map->num_reg_defaults, + sizeof(struct reg_default), regcache_default_cmp); + + if (r) + return r - map->reg_defaults; + else + return -1; +} + int regcache_insert_reg(struct regmap *map, unsigned int reg, unsigned int val) { @@ -396,6 +396,6 @@ int regcache_insert_reg(struct regmap *map, unsigned int reg, map->reg_defaults[map->num_reg_defaults - 1].reg = reg; map->reg_defaults[map->num_reg_defaults - 1].def = val; sort(map->reg_defaults, map->num_reg_defaults, - sizeof(struct reg_default), regcache_insert_cmp, NULL); + sizeof(struct reg_default), regcache_default_cmp, NULL); return 0; } -- cgit v1.2.1 From 6e6ace00a045251bd172b9b9c2379857bbff3dc7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 Oct 2011 13:23:31 +0100 Subject: regmap: Return a sensible error code if we fail to read the cache If a register isn't cached then let callers know that so they can fall back or error handle appropriately. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- drivers/base/regmap/regcache-indexed.c | 7 +++---- drivers/base/regmap/regcache-lzo.c | 4 ++-- drivers/base/regmap/regcache-rbtree.c | 3 +-- drivers/base/regmap/regcache.c | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-indexed.c b/drivers/base/regmap/regcache-indexed.c index 2e10bb13bfc4..507731ad8ec1 100644 --- a/drivers/base/regmap/regcache-indexed.c +++ b/drivers/base/regmap/regcache-indexed.c @@ -20,11 +20,10 @@ static int regcache_indexed_read(struct regmap *map, unsigned int reg, int ret; ret = regcache_lookup_reg(map, reg); - if (ret < 0) - *value = 0; - else + if (ret >= 0) *value = map->reg_defaults[ret].def; - return 0; + + return ret; } static int regcache_indexed_write(struct regmap *map, unsigned int reg, diff --git a/drivers/base/regmap/regcache-lzo.c b/drivers/base/regmap/regcache-lzo.c index ad6af925f56c..066aeece3626 100644 --- a/drivers/base/regmap/regcache-lzo.c +++ b/drivers/base/regmap/regcache-lzo.c @@ -232,7 +232,6 @@ static int regcache_lzo_read(struct regmap *map, size_t blksize, tmp_dst_len; void *tmp_dst; - *value = 0; /* index of the compressed lzo block */ blkindex = regcache_lzo_get_blkindex(map, reg); /* register index within the decompressed block */ @@ -261,7 +260,8 @@ static int regcache_lzo_read(struct regmap *map, /* restore the pointer and length of the compressed block */ lzo_block->dst = tmp_dst; lzo_block->dst_len = tmp_dst_len; - return 0; + + return ret; } static int regcache_lzo_write(struct regmap *map, diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 40f23dd8478c..887dbce63aff 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -193,8 +193,7 @@ static int regcache_rbtree_read(struct regmap *map, *value = regcache_rbtree_get_register(rbnode, reg_tmp, map->cache_word_size); } else { - /* uninitialized registers default to 0 */ - *value = 0; + return -ENOENT; } return 0; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index c5379c86de88..409abd282c6c 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -378,7 +378,7 @@ int regcache_lookup_reg(struct regmap *map, unsigned int reg) if (r) return r - map->reg_defaults; else - return -1; + return -ENOENT; } int regcache_insert_reg(struct regmap *map, unsigned int reg, -- cgit v1.2.1 From 04e016adcae28b65ddc9e756947fa1526a51c0b5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 Oct 2011 13:35:43 +0100 Subject: regmap: Warn on raw I/O as well as bulk reads that bypass cache As with the bulk reads we really should be able to make these play nicely with the cache but warn for now. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- drivers/base/regmap/regmap.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index d786ddcaf117..85bffddda530 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -377,6 +377,8 @@ int regmap_raw_write(struct regmap *map, unsigned int reg, { int ret; + WARN_ON(map->cache_type != REGCACHE_NONE); + mutex_lock(&map->lock); ret = _regmap_raw_write(map, reg, val, val_len); @@ -481,6 +483,8 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, { int ret; + WARN_ON(map->cache_type != REGCACHE_NONE); + mutex_lock(&map->lock); ret = _regmap_raw_read(map, reg, val, val_len); -- cgit v1.2.1 From e42c5a9a4230c38ceba0a890b30a2d0dd9314bff Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 Oct 2011 14:30:02 +0100 Subject: regmap: Allow rbtree to cache zero default values Ensure that when we start up in cache only mode we can store defaults of zero, otherwise if the hardware is unavailable we won't be able to read. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- drivers/base/regmap/regcache-rbtree.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 887dbce63aff..52511f95857a 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -252,9 +252,6 @@ static int regcache_rbtree_write(struct regmap *map, unsigned int reg, regcache_rbtree_set_register(rbnode, reg_tmp, value, map->cache_word_size); } else { - /* bail out early, no need to create the rbnode yet */ - if (!value) - return 0; /* look for an adjacent register to the one we are about to add */ for (node = rb_first(&rbtree_ctx->root); node; node = rb_next(node)) { -- cgit v1.2.1 From b03622a80d2206c4179d6a41a0dc5cfbdfc853ee Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 Oct 2011 12:54:25 +0100 Subject: regmap: Ensure rbtree syncs registers set to zero properly Simplify the check for registers set at their default value by avoiding picking a default value in the case where we don't have one. Instead we only compare the current value to the current value when we looked one up. This fixes the case where we don't have a default stored but the value was set to zero when that isn't the chip default. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- drivers/base/regmap/regcache-rbtree.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regcache-rbtree.c b/drivers/base/regmap/regcache-rbtree.c index 52511f95857a..e31498499b0f 100644 --- a/drivers/base/regmap/regcache-rbtree.c +++ b/drivers/base/regmap/regcache-rbtree.c @@ -304,7 +304,7 @@ static int regcache_rbtree_sync(struct regmap *map) struct rb_node *node; struct regcache_rbtree_node *rbnode; unsigned int regtmp; - unsigned int val, def; + unsigned int val; int ret; int i; @@ -315,13 +315,12 @@ static int regcache_rbtree_sync(struct regmap *map) regtmp = rbnode->base_reg + i; val = regcache_rbtree_get_register(rbnode, i, map->cache_word_size); + + /* Is this the hardware default? If so skip. */ ret = regcache_lookup_reg(map, i); - if (ret < 0) - def = 0; - else - def = map->reg_defaults[ret].def; - if (val == def) + if (ret > 0 && val == map->reg_defaults[ret].def) continue; + map->cache_bypass = 1; ret = _regmap_write(map, regtmp, val); map->cache_bypass = 0; -- cgit v1.2.1 From 8528bdd450d34687b380c0f87992d105bdf54ca3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 9 Oct 2011 13:13:58 +0100 Subject: regmap: Allow caches for devices with no defaults We only really need the defaults in order to cut down the number of registers we sync and to satisfy reads while the device is powered off but not all devices are going to need to do that (always on devices like PMICs being the prime example) so don't require those devices to supply a default. Instead only try to fall back to hardware defaults if the driver told us to. Devices using LZO won't be able to instantiate with this, that will require some updates in the LZO code to handle this case. Signed-off-by: Mark Brown Acked-by: Dimitris Papastamos --- 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 409abd282c6c..afcfef838263 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -119,7 +119,7 @@ int regcache_init(struct regmap *map) if (!tmp_buf) return -ENOMEM; map->reg_defaults = tmp_buf; - } else { + } else if (map->num_reg_defaults_raw) { /* Some devices such as PMICs don't have cache defaults, * we cope with this by reading back the HW registers and * crafting the cache defaults by hand. -- cgit v1.2.1 From de2d808f4de091321978d05a85ef0819e8f3561a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 Oct 2011 13:24:52 +0100 Subject: regmap: Support some block operations on cached devices Support raw reads if all the registers being read are volatile, the cache will have no impact for tem. Support bulk reads either directly (if all the registers are volatile) or by falling back to iterating over single register reads otherwise. Signed-off-by: Mark Brown --- drivers/base/regmap/regmap.c | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) (limited to 'drivers/base') diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 85bffddda530..bf441db1ee90 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -482,8 +482,14 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, size_t val_len) { int ret; + int i; + bool vol = true; - WARN_ON(map->cache_type != REGCACHE_NONE); + for (i = 0; i < val_len / map->format.val_bytes; i++) + if (!regmap_volatile(map, reg + i)) + vol = false; + + WARN_ON(!vol && map->cache_type != REGCACHE_NONE); mutex_lock(&map->lock); @@ -511,18 +517,30 @@ int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, { int ret, i; size_t val_bytes = map->format.val_bytes; - - WARN_ON(map->cache_type != REGCACHE_NONE); + bool vol = true; if (!map->format.parse_val) return -EINVAL; - ret = regmap_raw_read(map, reg, val, val_bytes * val_count); - if (ret != 0) - return ret; + /* Is this a block of volatile registers? */ + for (i = 0; i < val_count; i++) + if (!regmap_volatile(map, reg + i)) + vol = false; - for (i = 0; i < val_count * val_bytes; i += val_bytes) - map->format.parse_val(val + i); + if (vol || map->cache_type == REGCACHE_NONE) { + ret = regmap_raw_read(map, reg, val, val_bytes * val_count); + if (ret != 0) + return ret; + + for (i = 0; i < val_count * val_bytes; i += val_bytes) + map->format.parse_val(val + i); + } else { + for (i = 0; i < val_count; i++) { + ret = regmap_read(map, reg + i, val + (i * val_bytes)); + if (ret != 0) + return ret; + } + } return 0; } -- cgit v1.2.1