diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-14 11:51:03 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-08-14 11:51:03 -0700 |
commit | 15bc88cd5f8e095fa7fe8494d1980ad29885d984 (patch) | |
tree | 6b54b64c76595fa0e593b10bdc2c6d06d5be166f | |
parent | be718b524d8d8a25c18b7403c4dbfddacc8e89e3 (diff) | |
parent | 1dce5d849f94b90e2ed52056e7eefab3c17277b8 (diff) | |
download | linux-15bc88cd5f8e095fa7fe8494d1980ad29885d984.tar.bz2 |
Merge tag 'regmap-v4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap
Pull regmap updates from Mark Brown:
"Several small new features for regmap this time around:
- Support for SCCB, an I2C variant used on some media cards. This has
also pulled in an I2C commit from Peter Rosin as a dependency.
- Addition of an API for reading repeatedly from registers where the
address doesn't automatically increment like some ADC outputs or
GPIO status registers.
- Support for bulk I/O on Slimbus"
* tag 'regmap-v4.19' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap:
regmap: Add regmap_noinc_read API
regmap: sccb: fix typo and sort headers alphabetically
i2c: smbus: add unlocked __i2c_smbus_xfer variant
regmap: add SCCB support
regmap: slimbus: add support to multi read/write
-rw-r--r-- | drivers/base/regmap/Kconfig | 4 | ||||
-rw-r--r-- | drivers/base/regmap/Makefile | 1 | ||||
-rw-r--r-- | drivers/base/regmap/internal.h | 3 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-sccb.c | 128 | ||||
-rw-r--r-- | drivers/base/regmap/regmap-slimbus.c | 23 | ||||
-rw-r--r-- | drivers/base/regmap/regmap.c | 79 | ||||
-rw-r--r-- | drivers/i2c/i2c-core-smbus.c | 28 | ||||
-rw-r--r-- | include/linux/i2c.h | 11 | ||||
-rw-r--r-- | include/linux/regmap.h | 54 |
9 files changed, 303 insertions, 28 deletions
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index aff34c0c2a3e..6ad5ef48b61e 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -45,3 +45,7 @@ config REGMAP_IRQ config REGMAP_SOUNDWIRE tristate depends on SOUNDWIRE_BUS + +config REGMAP_SCCB + tristate + depends on I2C diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index 5ed0023fabda..f5b4e8851d00 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o obj-$(CONFIG_REGMAP_W1) += regmap-w1.o obj-$(CONFIG_REGMAP_SOUNDWIRE) += regmap-sdw.o +obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 53785e0e297a..a6bf34d6394e 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -94,10 +94,12 @@ 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); + bool (*readable_noinc_reg)(struct device *dev, unsigned int reg); const struct regmap_access_table *wr_table; const struct regmap_access_table *rd_table; const struct regmap_access_table *volatile_table; const struct regmap_access_table *precious_table; + const struct regmap_access_table *rd_noinc_table; int (*reg_read)(void *context, unsigned int reg, unsigned int *val); int (*reg_write)(void *context, unsigned int reg, unsigned int val); @@ -181,6 +183,7 @@ 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); +bool regmap_readable_noinc(struct regmap *map, unsigned int reg); int _regmap_write(struct regmap *map, unsigned int reg, unsigned int val); diff --git a/drivers/base/regmap/regmap-sccb.c b/drivers/base/regmap/regmap-sccb.c new file mode 100644 index 000000000000..597042e2d009 --- /dev/null +++ b/drivers/base/regmap/regmap-sccb.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +// Register map access API - SCCB support + +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/regmap.h> + +#include "internal.h" + +/** + * sccb_is_available - Check if the adapter supports SCCB protocol + * @adap: I2C adapter + * + * Return true if the I2C adapter is capable of using SCCB helper functions, + * false otherwise. + */ +static bool sccb_is_available(struct i2c_adapter *adap) +{ + u32 needed_funcs = I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA; + + /* + * If we ever want support for hardware doing SCCB natively, we will + * introduce a sccb_xfer() callback to struct i2c_algorithm and check + * for it here. + */ + + return (i2c_get_functionality(adap) & needed_funcs) == needed_funcs; +} + +/** + * regmap_sccb_read - Read data from SCCB slave device + * @context: Device that will be interacted with + * @reg: Register to be read from + * @val: Pointer to store read value + * + * This executes the 2-phase write transmission cycle that is followed by a + * 2-phase read transmission cycle, returning negative errno else zero on + * success. + */ +static int regmap_sccb_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + int ret; + union i2c_smbus_data data; + + i2c_lock_bus(i2c->adapter, I2C_LOCK_SEGMENT); + + ret = __i2c_smbus_xfer(i2c->adapter, i2c->addr, i2c->flags, + I2C_SMBUS_WRITE, reg, I2C_SMBUS_BYTE, NULL); + if (ret < 0) + goto out; + + ret = __i2c_smbus_xfer(i2c->adapter, i2c->addr, i2c->flags, + I2C_SMBUS_READ, 0, I2C_SMBUS_BYTE, &data); + if (ret < 0) + goto out; + + *val = data.byte; +out: + i2c_unlock_bus(i2c->adapter, I2C_LOCK_SEGMENT); + + return ret; +} + +/** + * regmap_sccb_write - Write data to SCCB slave device + * @context: Device that will be interacted with + * @reg: Register to write to + * @val: Value to be written + * + * This executes the SCCB 3-phase write transmission cycle, returning negative + * errno else zero on success. + */ +static int regmap_sccb_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct i2c_client *i2c = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(i2c, reg, val); +} + +static struct regmap_bus regmap_sccb_bus = { + .reg_write = regmap_sccb_write, + .reg_read = regmap_sccb_read, +}; + +static const struct regmap_bus *regmap_get_sccb_bus(struct i2c_client *i2c, + const struct regmap_config *config) +{ + if (config->val_bits == 8 && config->reg_bits == 8 && + sccb_is_available(i2c->adapter)) + return ®map_sccb_bus; + + return ERR_PTR(-ENOTSUPP); +} + +struct regmap *__regmap_init_sccb(struct i2c_client *i2c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + const struct regmap_bus *bus = regmap_get_sccb_bus(i2c, config); + + if (IS_ERR(bus)) + return ERR_CAST(bus); + + return __regmap_init(&i2c->dev, bus, &i2c->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_sccb); + +struct regmap *__devm_regmap_init_sccb(struct i2c_client *i2c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + const struct regmap_bus *bus = regmap_get_sccb_bus(i2c, config); + + if (IS_ERR(bus)) + return ERR_CAST(bus); + + return __devm_regmap_init(&i2c->dev, bus, &i2c->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_sccb); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/base/regmap/regmap-slimbus.c b/drivers/base/regmap/regmap-slimbus.c index 91d501eda8a9..0968059f1ef5 100644 --- a/drivers/base/regmap/regmap-slimbus.c +++ b/drivers/base/regmap/regmap-slimbus.c @@ -7,33 +7,24 @@ #include "internal.h" -static int regmap_slimbus_byte_reg_read(void *context, unsigned int reg, - unsigned int *val) +static int regmap_slimbus_write(void *context, const void *data, size_t count) { struct slim_device *sdev = context; - int v; - v = slim_readb(sdev, reg); - - if (v < 0) - return v; - - *val = v; - - return 0; + return slim_write(sdev, *(u16 *)data, count - 2, (u8 *)data + 2); } -static int regmap_slimbus_byte_reg_write(void *context, unsigned int reg, - unsigned int val) +static int regmap_slimbus_read(void *context, const void *reg, size_t reg_size, + void *val, size_t val_size) { struct slim_device *sdev = context; - return slim_writeb(sdev, reg, val); + return slim_read(sdev, *(u16 *)reg, val_size, val); } static struct regmap_bus regmap_slimbus_bus = { - .reg_write = regmap_slimbus_byte_reg_write, - .reg_read = regmap_slimbus_byte_reg_read, + .write = regmap_slimbus_write, + .read = regmap_slimbus_read, .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, .val_format_endian_default = REGMAP_ENDIAN_LITTLE, }; diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index 3bc84885eb91..0360a90ad6b6 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -168,6 +168,17 @@ bool regmap_precious(struct regmap *map, unsigned int reg) return false; } +bool regmap_readable_noinc(struct regmap *map, unsigned int reg) +{ + if (map->readable_noinc_reg) + return map->readable_noinc_reg(map->dev, reg); + + if (map->rd_noinc_table) + return regmap_check_range_table(map, reg, map->rd_noinc_table); + + return true; +} + static bool regmap_volatile_range(struct regmap *map, unsigned int reg, size_t num) { @@ -766,10 +777,12 @@ struct regmap *__regmap_init(struct device *dev, map->rd_table = config->rd_table; map->volatile_table = config->volatile_table; map->precious_table = config->precious_table; + map->rd_noinc_table = config->rd_noinc_table; map->writeable_reg = config->writeable_reg; map->readable_reg = config->readable_reg; map->volatile_reg = config->volatile_reg; map->precious_reg = config->precious_reg; + map->readable_noinc_reg = config->readable_noinc_reg; map->cache_type = config->cache_type; spin_lock_init(&map->async_lock); @@ -1285,6 +1298,7 @@ int regmap_reinit_cache(struct regmap *map, const struct regmap_config *config) map->readable_reg = config->readable_reg; map->volatile_reg = config->volatile_reg; map->precious_reg = config->precious_reg; + map->readable_noinc_reg = config->readable_noinc_reg; map->cache_type = config->cache_type; regmap_debugfs_init(map, config->name); @@ -2564,7 +2578,70 @@ int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, EXPORT_SYMBOL_GPL(regmap_raw_read); /** - * regmap_field_read() - Read a value to a single register field + * regmap_noinc_read(): Read data from a register without incrementing the + * register number + * + * @map: Register map to read from + * @reg: Register to read from + * @val: Pointer to data buffer + * @val_len: Length of output buffer in bytes. + * + * The regmap API usually assumes that bulk bus read operations will read a + * range of registers. Some devices have certain registers for which a read + * operation read will read from an internal FIFO. + * + * The target register must be volatile but registers after it can be + * completely unrelated cacheable registers. + * + * This will attempt multiple reads as required to read val_len bytes. + * + * A value of zero will be returned on success, a negative errno will be + * returned in error cases. + */ +int regmap_noinc_read(struct regmap *map, unsigned int reg, + void *val, size_t val_len) +{ + size_t read_len; + int ret; + + if (!map->bus) + return -EINVAL; + if (!map->bus->read) + return -ENOTSUPP; + if (val_len % map->format.val_bytes) + return -EINVAL; + if (!IS_ALIGNED(reg, map->reg_stride)) + return -EINVAL; + if (val_len == 0) + return -EINVAL; + + map->lock(map->lock_arg); + + if (!regmap_volatile(map, reg) || !regmap_readable_noinc(map, reg)) { + ret = -EINVAL; + goto out_unlock; + } + + while (val_len) { + if (map->max_raw_read && map->max_raw_read < val_len) + read_len = map->max_raw_read; + else + read_len = val_len; + ret = _regmap_raw_read(map, reg, val, read_len); + if (ret) + goto out_unlock; + val = ((u8 *)val) + read_len; + val_len -= read_len; + } + +out_unlock: + map->unlock(map->lock_arg); + return ret; +} +EXPORT_SYMBOL_GPL(regmap_noinc_read); + +/** + * regmap_field_read(): Read a value to a single register field * * @field: Register field to read from * @val: Pointer to store read value diff --git a/drivers/i2c/i2c-core-smbus.c b/drivers/i2c/i2c-core-smbus.c index 51970bae3c4a..9cd66cabb84f 100644 --- a/drivers/i2c/i2c-core-smbus.c +++ b/drivers/i2c/i2c-core-smbus.c @@ -463,7 +463,7 @@ static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, msg[num-1].len++; } - status = i2c_transfer(adapter, msg, num); + status = __i2c_transfer(adapter, msg, num); if (status < 0) goto cleanup; if (status != num) { @@ -524,9 +524,24 @@ cleanup: * This executes an SMBus protocol operation, and returns a negative * errno code else zero on success. */ -s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, - char read_write, u8 command, int protocol, - union i2c_smbus_data *data) +s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int protocol, union i2c_smbus_data *data) +{ + s32 res; + + i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); + res = __i2c_smbus_xfer(adapter, addr, flags, read_write, + command, protocol, data); + i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); + + return res; +} +EXPORT_SYMBOL(i2c_smbus_xfer); + +s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, + u8 command, int protocol, union i2c_smbus_data *data) { unsigned long orig_jiffies; int try; @@ -543,8 +558,6 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB; if (adapter->algo->smbus_xfer) { - i2c_lock_bus(adapter, I2C_LOCK_SEGMENT); - /* Retry automatically on arbitration loss */ orig_jiffies = jiffies; for (res = 0, try = 0; try <= adapter->retries; try++) { @@ -557,7 +570,6 @@ s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, orig_jiffies + adapter->timeout)) break; } - i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT); if (res != -EOPNOTSUPP || !adapter->algo->master_xfer) goto trace; @@ -579,7 +591,7 @@ trace: return res; } -EXPORT_SYMBOL(i2c_smbus_xfer); +EXPORT_SYMBOL(__i2c_smbus_xfer); /** * i2c_smbus_read_i2c_block_data_or_emulated - read block or emulate diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 254cd34eeae2..465afb092fa7 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -140,9 +140,14 @@ extern int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, and probably just as fast. Note that we use i2c_adapter here, because you do not need a specific smbus adapter to call this function. */ -extern s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, - unsigned short flags, char read_write, u8 command, - int size, union i2c_smbus_data *data); +s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, u8 command, + int protocol, union i2c_smbus_data *data); + +/* Unlocked flavor */ +s32 __i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char read_write, u8 command, + int protocol, union i2c_smbus_data *data); /* Now follow the 'nice' access routines. These also document the calling conventions of i2c_smbus_xfer. */ diff --git a/include/linux/regmap.h b/include/linux/regmap.h index 4f38068ffb71..379505a53722 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -268,6 +268,13 @@ typedef void (*regmap_unlock)(void *); * field is NULL but precious_table (see below) is not, the * check is performed on such table (a register is precious if * it belongs to one of the ranges specified by precious_table). + * @readable_noinc_reg: Optional callback returning true if the register + * supports multiple read operations without incrementing + * the register number. If this field is NULL but + * rd_noinc_table (see below) is not, the check is + * performed on such table (a register is no increment + * readable if it belongs to one of the ranges specified + * by rd_noinc_table). * @disable_locking: This regmap is either protected by external means or * is guaranteed not be be accessed from multiple threads. * Don't use any locking mechanisms. @@ -295,6 +302,7 @@ typedef void (*regmap_unlock)(void *); * @rd_table: As above, for read access. * @volatile_table: As above, for volatile registers. * @precious_table: As above, for precious registers. + * @rd_noinc_table: As above, for no increment readable registers. * @reg_defaults: Power on reset values for registers (for use with * register cache support). * @num_reg_defaults: Number of elements in reg_defaults. @@ -344,6 +352,7 @@ struct regmap_config { 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); + bool (*readable_noinc_reg)(struct device *dev, unsigned int reg); bool disable_locking; regmap_lock lock; @@ -360,6 +369,7 @@ struct regmap_config { const struct regmap_access_table *rd_table; const struct regmap_access_table *volatile_table; const struct regmap_access_table *precious_table; + const struct regmap_access_table *rd_noinc_table; const struct reg_default *reg_defaults; unsigned int num_reg_defaults; enum regcache_type cache_type; @@ -514,6 +524,10 @@ struct regmap *__regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_sccb(struct i2c_client *i2c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_slimbus(struct slim_device *slimbus, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -558,6 +572,10 @@ struct regmap *__devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__devm_regmap_init_sccb(struct i2c_client *i2c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__devm_regmap_init_spi(struct spi_device *dev, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -646,6 +664,19 @@ int regmap_attach_dev(struct device *dev, struct regmap *map, i2c, config) /** + * regmap_init_sccb() - 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. + */ +#define regmap_init_sccb(i2c, config) \ + __regmap_lockdep_wrapper(__regmap_init_sccb, #config, \ + i2c, config) + +/** * regmap_init_slimbus() - Initialise register map * * @slimbus: Device that will be interacted with @@ -798,6 +829,20 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); i2c, config) /** + * devm_regmap_init_sccb() - Initialise managed 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. The regmap will be automatically freed by the + * device management code. + */ +#define devm_regmap_init_sccb(i2c, config) \ + __regmap_lockdep_wrapper(__devm_regmap_init_sccb, #config, \ + i2c, config) + +/** * devm_regmap_init_spi() - Initialise register map * * @dev: Device that will be interacted with @@ -946,6 +991,8 @@ int regmap_raw_write_async(struct regmap *map, unsigned int reg, int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val); int regmap_raw_read(struct regmap *map, unsigned int reg, void *val, size_t val_len); +int regmap_noinc_read(struct regmap *map, unsigned int reg, + void *val, size_t val_len); int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count); int regmap_update_bits_base(struct regmap *map, unsigned int reg, @@ -1196,6 +1243,13 @@ static inline int regmap_raw_read(struct regmap *map, unsigned int reg, return -EINVAL; } +static inline int regmap_noinc_read(struct regmap *map, unsigned int reg, + void *val, size_t val_len) +{ + WARN_ONCE(1, "regmap API is disabled"); + return -EINVAL; +} + static inline int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count) { |