From b83a313bf2520183641cf485d68cc273323597d2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 11 May 2011 19:59:58 +0200 Subject: regmap: Add generic non-memory mapped register access API There are many places in the tree where we implement register access for devices on non-memory mapped buses, especially I2C and SPI. Since hardware designers seem to have settled on a relatively consistent set of register interfaces this can be effectively factored out into shared code. There are a standard set of formats for marshalling data for exchange with the device, with the actual I/O mechanisms generally being simple byte streams. We create an abstraction for marshaling data into formats which can be sent on the control interfaces, and create a standard method for plugging in actual transport underneath that. This is mostly a refactoring and renaming of the bottom level of the existing code for sharing register I/O which we have in ASoC. A subsequent patch in this series converts ASoC to use this. The main difference in interface is that reads return values by writing to a location provided by a pointer rather than in the return value, ensuring we can use the full range of the type for register data. We also use unsigned types rather than ints for the same reason. As some of the devices can have very large register maps the existing ASoC code also contains infrastructure for managing register caches. This cache work will be moved over in a future stage to allow for separate review, the current patch only deals with the physical I/O. Signed-off-by: Mark Brown Acked-by: Liam Girdwood Acked-by: Greg Kroah-Hartman Acked-by: Wolfram Sang Acked-by: Grant Likely --- drivers/base/Kconfig | 2 + drivers/base/Makefile | 1 + drivers/base/regmap/Kconfig | 6 + drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap.c | 455 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 465 insertions(+) create mode 100644 drivers/base/regmap/Kconfig create mode 100644 drivers/base/regmap/Makefile create mode 100644 drivers/base/regmap/regmap.c (limited to 'drivers/base') diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index d57e8d0fb823..b605d01f5d45 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -168,4 +168,6 @@ config SYS_HYPERVISOR bool default n +source "drivers/base/regmap/Kconfig" + endmenu diff --git a/drivers/base/Makefile b/drivers/base/Makefile index 4c5701c15f53..dd4f9b245fc1 100644 --- a/drivers/base/Makefile +++ b/drivers/base/Makefile @@ -18,6 +18,7 @@ ifeq ($(CONFIG_SYSFS),y) obj-$(CONFIG_MODULES) += module.o endif obj-$(CONFIG_SYS_HYPERVISOR) += hypervisor.o +obj-$(CONFIG_REGMAP) += regmap/ ccflags-$(CONFIG_DEBUG_DRIVER) := -DDEBUG diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig new file mode 100644 index 000000000000..fc0eb1d484dd --- /dev/null +++ b/drivers/base/regmap/Kconfig @@ -0,0 +1,6 @@ +# Generic register map support. There are no user servicable options here, +# this is an API intended to be used by other kernel subsystems. These +# subsystems should select the appropriate symbols. + +config REGMAP + bool diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile new file mode 100644 index 000000000000..7532e13e0f00 --- /dev/null +++ b/drivers/base/regmap/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_REGMAP) += regmap.o diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c new file mode 100644 index 000000000000..cf3565cae93d --- /dev/null +++ b/drivers/base/regmap/regmap.c @@ -0,0 +1,455 @@ +/* + * Register map access API + * + * 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 + +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; +}; + +static void regmap_format_4_12_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 12) | val); +} + +static void regmap_format_7_9_write(struct regmap *map, + unsigned int reg, unsigned int val) +{ + __be16 *out = map->work_buf; + *out = cpu_to_be16((reg << 9) | val); +} + +static void regmap_format_8(void *buf, unsigned int val) +{ + u8 *b = buf; + + b[0] = val; +} + +static void regmap_format_16(void *buf, unsigned int val) +{ + __be16 *b = buf; + + b[0] = cpu_to_be16(val); +} + +static unsigned int regmap_parse_8(void *buf) +{ + u8 *b = buf; + + return b[0]; +} + +static unsigned int regmap_parse_16(void *buf) +{ + __be16 *b = buf; + + b[0] = be16_to_cpu(b[0]); + + return b[0]; +} + +/** + * regmap_init(): Initialise register map + * + * @dev: Device that will be interacted with + * @bus: Bus-specific callbacks to use with device + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. This function should generally not be called + * directly, it should be called by bus-specific init functions. + */ +struct regmap *regmap_init(struct device *dev, + const struct regmap_bus *bus, + const struct regmap_config *config) +{ + struct regmap *map; + int ret = -EINVAL; + + if (!bus || !config) + return NULL; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (map == NULL) { + ret = -ENOMEM; + goto err; + } + + mutex_init(&map->lock); + map->format.buf_size = (config->reg_bits + config->val_bits) / 8; + map->format.reg_bytes = config->reg_bits / 8; + map->format.val_bytes = config->val_bits / 8; + map->dev = dev; + map->bus = bus; + + switch (config->reg_bits) { + case 4: + switch (config->val_bits) { + case 12: + map->format.format_write = regmap_format_4_12_write; + break; + default: + goto err_map; + } + break; + + case 7: + switch (config->val_bits) { + case 9: + map->format.format_write = regmap_format_7_9_write; + break; + default: + goto err_map; + } + break; + + case 8: + map->format.format_reg = regmap_format_8; + break; + + case 16: + map->format.format_reg = regmap_format_16; + break; + + default: + goto err_map; + } + + switch (config->val_bits) { + case 8: + map->format.format_val = regmap_format_8; + map->format.parse_val = regmap_parse_8; + break; + case 16: + map->format.format_val = regmap_format_16; + map->format.parse_val = regmap_parse_16; + break; + } + + if (!map->format.format_write && + !(map->format.format_reg && map->format.format_val)) + goto err_map; + + map->work_buf = kmalloc(map->format.buf_size, GFP_KERNEL); + if (map->work_buf == NULL) { + ret = -ENOMEM; + goto err_bus; + } + + return map; + +err_bus: + module_put(map->bus->owner); +err_map: + kfree(map); +err: + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(regmap_init); + +/** + * regmap_exit(): Free a previously allocated register map + */ +void regmap_exit(struct regmap *map) +{ + kfree(map->work_buf); + module_put(map->bus->owner); + kfree(map); +} +EXPORT_SYMBOL_GPL(regmap_exit); + +static int _regmap_raw_write(struct regmap *map, unsigned int reg, + const void *val, size_t val_len) +{ + void *buf; + int ret = -ENOTSUPP; + size_t len; + + map->format.format_reg(map->work_buf, reg); + + /* Try to do a gather write if we can */ + 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 (ret == -ENOTSUPP) { + len = map->format.reg_bytes + val_len; + buf = kmalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy(buf, map->work_buf, map->format.reg_bytes); + memcpy(buf + map->format.reg_bytes, val, val_len); + ret = map->bus->write(map->dev, buf, len); + + kfree(buf); + } + + return ret; +} + +static int _regmap_write(struct regmap *map, unsigned int reg, + unsigned int val) +{ + BUG_ON(!map->format.format_write && !map->format.format_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); + } else { + map->format.format_val(map->work_buf + map->format.reg_bytes, + val); + return _regmap_raw_write(map, reg, + map->work_buf + map->format.reg_bytes, + map->format.val_bytes); + } +} + +/** + * regmap_write(): Write a value to a single register + * + * @map: Register map to write to + * @reg: Register to write to + * @val: Value to be written + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_write(struct regmap *map, unsigned int reg, unsigned int val) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_write(map, reg, val); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_write); + +/** + * regmap_raw_write(): Write raw values to one or more registers + * + * @map: Register map to write to + * @reg: Initial register to write to + * @val: Block of data to be written, laid out for direct transmission to the + * device + * @val_len: Length of data pointed to by val. + * + * This function is intended to be used for things like firmware + * download where a large block of data needs to be transferred to the + * device. No formatting will be done on the data provided. + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_raw_write(struct regmap *map, unsigned int reg, + const void *val, size_t val_len) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_raw_write(map, reg, val, val_len); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_raw_write); + +static int _regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + unsigned int val_len) +{ + u8 *u8 = map->work_buf; + int ret; + + map->format.format_reg(map->work_buf, reg); + + /* + * Some buses 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; + + ret = map->bus->read(map->dev, map->work_buf, map->format.reg_bytes, + val, map->format.val_bytes); + if (ret != 0) + return ret; + + return 0; +} + +static int _regmap_read(struct regmap *map, unsigned int reg, + unsigned int *val) +{ + int ret; + + if (!map->format.parse_val) + return -EINVAL; + + ret = _regmap_raw_read(map, reg, map->work_buf, map->format.val_bytes); + if (ret == 0) + *val = map->format.parse_val(map->work_buf); + + return ret; +} + +/** + * regmap_read(): Read a value from a single register + * + * @map: Register map to write to + * @reg: Register to be read from + * @val: Pointer to store read value + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_read(map, reg, val); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_read); + +/** + * regmap_raw_read(): Read raw data from the device + * + * @map: Register map to write to + * @reg: First register to be read from + * @val: Pointer to store read value + * @val_len: Size of data to read + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, + size_t val_len) +{ + int ret; + + mutex_lock(&map->lock); + + ret = _regmap_raw_read(map, reg, val, val_len); + + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_raw_read); + +/** + * regmap_bulk_read(): Read multiple registers from the device + * + * @map: Register map to write to + * @reg: First register to be read from + * @val: Pointer to store read value, in native register size for device + * @val_count: Number of registers to read + * + * A value of zero will be returned on success, a negative errno will + * be returned in error cases. + */ +int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, + size_t val_count) +{ + int ret, i; + size_t val_bytes = map->format.val_bytes; + + if (!map->format.parse_val) + return -EINVAL; + + 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); + + return 0; +} +EXPORT_SYMBOL_GPL(regmap_bulk_read); + +/** + * remap_update_bits: Perform a read/modify/write cycle on the register map + * + * @map: Register map to update + * @reg: Register to update + * @mask: Bitmask to change + * @val: New value for bitmask + * + * Returns zero for success, a negative number on error. + */ +int regmap_update_bits(struct regmap *map, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int ret; + unsigned int tmp; + + mutex_lock(&map->lock); + + ret = _regmap_read(map, reg, &tmp); + if (ret != 0) + goto out; + + tmp &= ~mask; + tmp |= val & mask; + + ret = _regmap_write(map, reg, tmp); + +out: + mutex_unlock(&map->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(regmap_update_bits); -- cgit v1.2.3 From 9943fa300a5dcd4536e9a4aa5c09cf94e5e7b28c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 20 Jun 2011 19:02:29 +0100 Subject: regmap: Add I2C bus support Signed-off-by: Mark Brown Acked-by: Liam Girdwood Acked-by: Wolfram Sang Acked-by: Grant Likely --- drivers/base/regmap/Kconfig | 4 ++ drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-i2c.c | 115 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 drivers/base/regmap/regmap-i2c.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index fc0eb1d484dd..03cf1abed401 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -3,4 +3,8 @@ # subsystems should select the appropriate symbols. config REGMAP + default y if REGMAP_I2C bool + +config REGMAP_I2C + tristate diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 7532e13e0f00..ee5109d20f4e 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_REGMAP) += regmap.o +obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o diff --git a/drivers/base/regmap/regmap-i2c.c b/drivers/base/regmap/regmap-i2c.c new file mode 100644 index 000000000000..c2231ff06cbc --- /dev/null +++ b/drivers/base/regmap/regmap-i2c.c @@ -0,0 +1,115 @@ +/* + * Register map access API - I2C support + * + * 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 + +static int regmap_i2c_write(struct device *dev, const void *data, size_t count) +{ + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + + ret = i2c_master_send(i2c, data, count); + if (ret == count) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int regmap_i2c_gather_write(struct device *dev, + const void *reg, size_t reg_size, + const void *val, size_t val_size) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; + int ret; + + /* If the I2C controller can't do a gather tell the core, it + * will substitute in a linear write for us. + */ + if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_PROTOCOL_MANGLING)) + return -ENOTSUPP; + + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; + + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_NOSTART; + xfer[1].len = val_size; + xfer[1].buf = (void *)val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + +static int regmap_i2c_read(struct device *dev, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct i2c_client *i2c = to_i2c_client(dev); + struct i2c_msg xfer[2]; + int ret; + + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = reg_size; + xfer[0].buf = (void *)reg; + + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = val_size; + xfer[1].buf = val; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret == 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static struct regmap_bus regmap_i2c = { + .type = &i2c_bus_type, + .write = regmap_i2c_write, + .gather_write = regmap_i2c_gather_write, + .read = regmap_i2c_read, + .owner = THIS_MODULE, +}; + +/** + * regmap_init_i2c(): Initialise register map + * + * @i2c: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +struct regmap *regmap_init_i2c(struct i2c_client *i2c, + const struct regmap_config *config) +{ + return regmap_init(&i2c->dev, ®map_i2c, config); +} +EXPORT_SYMBOL_GPL(regmap_init_i2c); + -- cgit v1.2.3 From a676f083068b08e676c557279effbd7f4d590812 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 12 May 2011 11:42:10 +0200 Subject: regmap: Add SPI bus support Signed-off-by: Mark Brown Acked-by: Liam Girdwood Acked-by: Wolfram Sang Acked-by: Grant Likely --- drivers/base/regmap/Kconfig | 5 ++- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-spi.c | 72 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regmap-spi.c (limited to 'drivers/base') diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index 03cf1abed401..fabbf6cc5367 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -3,8 +3,11 @@ # subsystems should select the appropriate symbols. config REGMAP - default y if REGMAP_I2C + default y if (REGMAP_I2C || REGMAP_SPI) bool config REGMAP_I2C tristate + +config REGMAP_SPI + tristate diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index ee5109d20f4e..f476f4571295 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -1,2 +1,3 @@ obj-$(CONFIG_REGMAP) += regmap.o obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o +obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o diff --git a/drivers/base/regmap/regmap-spi.c b/drivers/base/regmap/regmap-spi.c new file mode 100644 index 000000000000..4deba0621bc7 --- /dev/null +++ b/drivers/base/regmap/regmap-spi.c @@ -0,0 +1,72 @@ +/* + * Register map access API - SPI support + * + * 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 + +static int regmap_spi_write(struct device *dev, const void *data, size_t count) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_write(spi, data, count); +} + +static int regmap_spi_gather_write(struct device *dev, + const void *reg, size_t reg_len, + const void *val, size_t val_len) +{ + struct spi_device *spi = to_spi_device(dev); + struct spi_message m; + struct spi_transfer t[2] = { { .tx_buf = reg, .len = reg_len, }, + { .tx_buf = val, .len = val_len, }, }; + + spi_message_init(&m); + spi_message_add_tail(&t[0], &m); + spi_message_add_tail(&t[1], &m); + + return spi_sync(spi, &m); +} + +static int regmap_spi_read(struct device *dev, + const void *reg, size_t reg_size, + void *val, size_t val_size) +{ + struct spi_device *spi = to_spi_device(dev); + + return spi_write_then_read(spi, reg, reg_size, val, val_size); +} + +static struct regmap_bus regmap_spi = { + .type = &spi_bus_type, + .write = regmap_spi_write, + .gather_write = regmap_spi_gather_write, + .read = regmap_spi_read, + .owner = THIS_MODULE, + .read_flag_mask = 0x80, +}; + +/** + * regmap_init_spi(): Initialise register map + * + * @spi: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +struct regmap *regmap_init_spi(struct spi_device *spi, + const struct regmap_config *config) +{ + return regmap_init(&spi->dev, ®map_spi, config); +} +EXPORT_SYMBOL_GPL(regmap_init_spi); -- cgit v1.2.3