Subject: [RFC PATCH] regmap: smbus: add support for regmap over SMBus

From: [email-masked] (Boris BREZILLON)
Date: Sat, 12 Apr 2014 11:02:01 +0200

On 12/04/2014 10:45, Boris BREZILLON wrote:
> SMBus is a subset of the I2C protocol, oftenly used to access registers on
> external devices.
>
> I2C adpaters are able to access SMBus devices thanks to the SMBus
> emulation layer. In the other hand SMBus adapters may not provide
> regular I2C transfers, and thus you may not be able to expose a regmap
> if your device is connected to such kind of adapter.
> Hence why we need this regmap over SMBus implementation.
>
> Signed-off-by: Boris BREZILLON <[email-masked]>
> ---
> Hello Mark,
>
> I know you were not in favor of the smbus regmap (according to this thread
> [1]), but I now hit a point where I need this functionnality.
>
> The A31 SoC have an SMBus adatper which do not support regular I2C transfers
> and on some boards (if not all) the AXP221 PMIC is connected to thi adapter.
> Moreover the AXP221 really looks like the AXP209 whose driver is being
> submitting ([2]), and this driver uses regmap to expose PMIC's registers.
>
> I'd like to reuse the driver, and the only thing I need to do so is to add
> support for regmap over SMBus, and patch the axp20x driver to register an
> smbus regmap instead of an i2c one.
>
> Let me now if you still think regmap over SMBus should not be supported, or
> if you've find a better way to achieve my goal.
>
> Best Regards,
>
> Boris
>
> [1] http://comments.gmane.org/gmane.linux.kernel/1167755
Oops, I forgot to add the 2nd reference link:

[2] http://www.spinics.net/lists/linux-input/msg30806.html

>  drivers/base/regmap/Kconfig        |   5 +-
>  drivers/base/regmap/Makefile       |   1 +
>  drivers/base/regmap/regmap-smbus.c | 364 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 369 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/base/regmap/regmap-smbus.c
>
> diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
> index 4251570..450b4c1 100644
> --- a/drivers/base/regmap/Kconfig
> +++ b/drivers/base/regmap/Kconfig
> @@ -3,7 +3,7 @@
>  # subsystems should select the appropriate symbols.
>  
>  config REGMAP
> -	default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_MMIO || REGMAP_IRQ)
> +	default y if (REGMAP_I2C || REGMAP_SMBUS || REGMAP_SPI || REGMAP_SPMI || REGMAP_MMIO || REGMAP_IRQ)
>  	select LZO_COMPRESS
>  	select LZO_DECOMPRESS
>  	select IRQ_DOMAIN if REGMAP_IRQ
> @@ -12,6 +12,9 @@ config REGMAP
>  config REGMAP_I2C
>  	tristate
>  
> +config REGMAP_SMBUS
> +	tristate
> +
>  config REGMAP_SPI
>  	tristate
>  
> diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
> index a7c670b..e752b9c 100644
> --- a/drivers/base/regmap/Makefile
> +++ b/drivers/base/regmap/Makefile
> @@ -2,6 +2,7 @@ obj-$(CONFIG_REGMAP) += regmap.o regcache.o
>  obj-$(CONFIG_REGMAP) += regcache-rbtree.o regcache-lzo.o regcache-flat.o
>  obj-$(CONFIG_DEBUG_FS) += regmap-debugfs.o
>  obj-$(CONFIG_REGMAP_I2C) += regmap-i2c.o
> +obj-$(CONFIG_REGMAP_SMBUS) += regmap-smbus.o
>  obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
>  obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o
>  obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o
> diff --git a/drivers/base/regmap/regmap-smbus.c b/drivers/base/regmap/regmap-smbus.c
> new file mode 100644
> index 0000000..c8b8075
> --- /dev/null
> +++ b/drivers/base/regmap/regmap-smbus.c
> @@ -0,0 +1,364 @@
> +/*
> + * Register map access API - SMBus support
> + *
> + * Copyright 2014 Free Electrons
> + *
> + * Author: Boris Brezillon <[email-masked]>
> + *
> + * 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 <linux/regmap.h>
> +#include <linux/i2c.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +
> +struct regmap_smbus_context {
> +	struct i2c_client *i2c;
> +	enum regmap_smbus_transfer_type transfer_type;
> +	int val_bytes;
> +};
> +
> +static int regmap_smbus_write(void *context, const void *data, size_t count)
> +{
> +	struct regmap_smbus_context *ctx = context;
> +	int ret = 0;
> +	u8 reg = *(u8 *)data++;
> +
> +	count--;
> +
> +	switch (ctx->transfer_type) {
> +	case REGMAP_SMBUS_BYTE_TRANSFER:
> +		while (count > 0 && !ret) {
> +			ret = i2c_smbus_write_byte_data(ctx->i2c, reg++,
> +							*(u8 *)data++);
> +
> +			count--;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_WORD_TRANSFER:
> +		while (count > 0 && !ret) {
> +			ret = i2c_smbus_write_word_data(ctx->i2c, reg,
> +							*(u16 *)data++);
> +
> +			reg += 2;
> +			count -= 2;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_BLOCK_TRANSFER:
> +		while (count > 0 && !ret) {
> +			ret = i2c_smbus_write_block_data(ctx->i2c,
> +							 reg,
> +							 ctx->val_bytes,
> +							 (const u8 *)data);
> +
> +			reg += ctx->val_bytes;
> +			count -= ctx->val_bytes;
> +			data += ctx->val_bytes;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_I2C_BLOCK_TRANSFER:
> +		while (count > 0 && !ret) {
> +			ret = i2c_smbus_write_i2c_block_data(ctx->i2c,
> +							     reg,
> +							     ctx->val_bytes,
> +							     (const u8 *)data);
> +
> +			reg += ctx->val_bytes;
> +			count -= ctx->val_bytes;
> +			data += ctx->val_bytes;
> +		}
> +		break;
> +
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	return ret;
> +}
> +
> +static int regmap_smbus_gather_write(void *context,
> +				     const void *reg, size_t reg_size,
> +				     const void *val, size_t val_size)
> +{
> +	struct regmap_smbus_context *ctx = context;
> +	u8 smbus_reg;
> +	int ret = 0;
> +
> +	if (reg_size != 1)
> +		return -ENOTSUPP;
> +
> +	smbus_reg = *(u8 *)reg;
> +
> +	switch (ctx->transfer_type) {
> +	case REGMAP_SMBUS_BYTE_TRANSFER:
> +		while (val_size && !ret) {
> +			ret = i2c_smbus_write_byte_data(ctx->i2c,
> +							smbus_reg++,
> +							*(u8 *)val++);
> +
> +			val_size--;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_WORD_TRANSFER:
> +		while (val_size && !ret) {
> +			ret = i2c_smbus_write_word_data(ctx->i2c,
> +							smbus_reg,
> +							*(u16 *)val++);
> +
> +			smbus_reg += 2;
> +			val_size -= 2;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_BLOCK_TRANSFER:
> +		while (val_size && !ret) {
> +			ret = i2c_smbus_write_block_data(ctx->i2c,
> +							 smbus_reg,
> +							 ctx->val_bytes,
> +							 (const u8 *)val);
> +
> +			smbus_reg += ctx->val_bytes;
> +			val_size -= ctx->val_bytes;
> +			val += ctx->val_bytes;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_I2C_BLOCK_TRANSFER:
> +		if (val_size > I2C_SMBUS_BLOCK_MAX)
> +			return -ENOTSUPP;
> +
> +		while (val_size && !ret) {
> +			ret = i2c_smbus_write_i2c_block_data(ctx->i2c,
> +							     smbus_reg,
> +							     ctx->val_bytes,
> +							     (const u8 *)val);
> +
> +			smbus_reg += ctx->val_bytes;
> +			val_size -= ctx->val_bytes;
> +			val += ctx->val_bytes;
> +		}
> +		break;
> +
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	return ret;
> +}
> +
> +static int regmap_smbus_read(void *context,
> +			     const void *reg, size_t reg_size,
> +			     void *val, size_t val_size)
> +{
> +	struct regmap_smbus_context *ctx = context;
> +	u8 buffer[I2C_SMBUS_BLOCK_MAX];
> +	u8 smbus_reg;
> +	int ret = 0;
> +
> +	if (reg_size != 1)
> +		return -ENOTSUPP;
> +
> +	smbus_reg = *(u8 *)reg;
> +
> +	switch (ctx->transfer_type) {
> +	case REGMAP_SMBUS_BYTE_TRANSFER:
> +		while (val_size && ret >= 0) {
> +			ret = i2c_smbus_read_byte_data(ctx->i2c, smbus_reg++);
> +			if (ret >= 0)
> +				*((u8 *)val++) = ret;
> +
> +			val_size--;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_WORD_TRANSFER:
> +		while (val_size && ret >= 0) {
> +			ret = i2c_smbus_read_word_data(ctx->i2c, smbus_reg);
> +			if (ret >= 0)
> +				*(u16 *)val++ = ret;
> +
> +			smbus_reg += 2;
> +			val_size -= 2;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_BLOCK_TRANSFER:
> +		while (val_size && ret >= 0) {
> +			ret = i2c_smbus_read_block_data(ctx->i2c,
> +							smbus_reg,
> +							buffer);
> +			if (ret >= 0 && ret < ctx->val_bytes) {
> +				ret = -EIO;
> +				break;
> +			}
> +
> +			memcpy(val, buffer, ctx->val_bytes);
> +			smbus_reg += ctx->val_bytes;
> +			val_size -= ctx->val_bytes;
> +			val += ctx->val_bytes;
> +		}
> +		break;
> +
> +	case REGMAP_SMBUS_I2C_BLOCK_TRANSFER:
> +		while (val_size && ret >= 0) {
> +			return -ENOTSUPP;
> +
> +			ret = i2c_smbus_read_i2c_block_data(ctx->i2c,
> +							    smbus_reg,
> +							    ctx->val_bytes,
> +							    (u8 *)val);
> +			if (ret >= 0 && ret < val_size) {
> +				ret = -EIO;
> +				break;
> +			}
> +
> +			smbus_reg += ctx->val_bytes;
> +			val_size -= ctx->val_bytes;
> +			val += ctx->val_bytes;
> +		}
> +		break;
> +
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static void regmap_smbus_free_context(void *context)
> +{
> +	kfree(context);
> +}
> +
> +struct regmap_smbus_context *regmap_smbus_gen_context(struct i2c_client *i2c,
> +				const struct regmap_config *config,
> +				enum regmap_smbus_transfer_type transfer_type)
> +{
> +	struct regmap_smbus_context *ctx;
> +	int val_bytes = 0;
> +
> +	if (config->reg_bits != 8 || config->pad_bits != 0)
> +		return ERR_PTR(-ENOTSUPP);
> +
> +	switch (transfer_type) {
> +	case REGMAP_SMBUS_BYTE_TRANSFER:
> +		if (config->val_bits != 8)
> +			return ERR_PTR(-EINVAL);
> +
> +		if (!i2c_check_functionality(i2c->adapter,
> +					     I2C_FUNC_SMBUS_BYTE_DATA))
> +			return ERR_PTR(-ENOTSUPP);
> +		break;
> +
> +	case REGMAP_SMBUS_WORD_TRANSFER:
> +		if (config->val_bits != 16)
> +			return ERR_PTR(-EINVAL);
> +
> +		if (!i2c_check_functionality(i2c->adapter,
> +					     I2C_FUNC_SMBUS_WORD_DATA))
> +			return ERR_PTR(-ENOTSUPP);
> +		break;
> +
> +	case REGMAP_SMBUS_BLOCK_TRANSFER:
> +		if (config->val_bits > (I2C_SMBUS_BLOCK_MAX * 8))
> +			return ERR_PTR(-EINVAL);
> +
> +		val_bytes = DIV_ROUND_UP(config->val_bits, 8);
> +		if (!i2c_check_functionality(i2c->adapter,
> +					     I2C_FUNC_SMBUS_BLOCK_DATA))
> +			return ERR_PTR(-ENOTSUPP);
> +		break;
> +
> +	case REGMAP_SMBUS_I2C_BLOCK_TRANSFER:
> +		if (config->val_bits > (I2C_SMBUS_BLOCK_MAX * 8))
> +			return ERR_PTR(-EINVAL);
> +
> +		val_bytes = DIV_ROUND_UP(config->val_bits, 8);
> +
> +		if (!i2c_check_functionality(i2c->adapter,
> +					     I2C_FUNC_SMBUS_I2C_BLOCK))
> +			return ERR_PTR(-ENOTSUPP);
> +		break;
> +
> +	default:
> +		return ERR_PTR(-ENOTSUPP);
> +	}
> +
> +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx)
> +		return ERR_PTR(-ENOMEM);
> +
> +	ctx->i2c = i2c;
> +	ctx->transfer_type = transfer_type;
> +	ctx->val_bytes = val_bytes;
> +
> +	return ctx;
> +}
> +
> +static struct regmap_bus regmap_smbus = {
> +	.write = regmap_smbus_write,
> +	.gather_write = regmap_smbus_gather_write,
> +	.read = regmap_smbus_read,
> +	.free_context = regmap_smbus_free_context,
> +};
> +
> +/**
> + * regmap_init_smbus(): Initialise register map
> + *
> + * @i2c: Device that will be interacted with
> + * @config: Configuration for register map
> + * @transfer_type: SMBUS transfer type
> + *
> + * The return value will be an ERR_PTR() on error or a valid pointer to
> + * a struct regmap.
> + */
> +struct regmap *regmap_init_smbus(struct i2c_client *i2c,
> +				 const struct regmap_config *config,
> +				 enum regmap_smbus_transfer_type transfer_type)
> +{
> +	struct regmap_smbus_context *ctx =
> +		regmap_smbus_gen_context(i2c, config, transfer_type);
> +
> +	if (IS_ERR(ctx))
> +		return ERR_PTR(PTR_ERR(ctx));
> +
> +	return regmap_init(&i2c->dev, &regmap_smbus, ctx, config);
> +}
> +EXPORT_SYMBOL_GPL(regmap_init_smbus);
> +
> +/**
> + * devm_regmap_init_smbus(): Initialise managed register map
> + *
> + * @i2c: Device that will be interacted with
> + * @config: Configuration for register map
> + * @transfer_type: SMBUS transfer type
> + *
> + * The return value will be an ERR_PTR() on error or a valid pointer
> + * to a struct regmap.  The regmap will be automatically freed by the
> + * device management code.
> + */
> +struct regmap *devm_regmap_init_smbus(struct i2c_client *i2c,
> +				const struct regmap_config *config,
> +				enum regmap_smbus_transfer_type transfer_type)
> +{
> +	struct regmap_smbus_context *ctx =
> +		regmap_smbus_gen_context(i2c, config, transfer_type);
> +
> +	if (IS_ERR(ctx))
> +		return ERR_PTR(PTR_ERR(ctx));
> +
> +	return devm_regmap_init(&i2c->dev, &regmap_smbus, ctx, config);
> +}
> +EXPORT_SYMBOL_GPL(devm_regmap_init_smbus);
> +
> +MODULE_LICENSE("GPL");

-- 
Boris Brezillon, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com