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

From: Boris BREZILLON
Date: Sat Apr 12 2014 - 04:46:52 EST


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 <boris.brezillon@xxxxxxxxxxxxxxxxxx>
---
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

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 <boris.brezillon@xxxxxxxxxxxxxxxxxx>
+ *
+ * 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");
--
1.8.3.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/