lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Date:	Mon, 14 Apr 2014 15:08:05 +0200
From:	Boris BREZILLON <boris.brezillon@...e-electrons.com>
To:	Mark Brown <broonie@...nel.org>
Cc:	Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
	Maxime Ripard <maxime.ripard@...e-electrons.com>,
	Shuge <shuge@...winnertech.com>, kevin@...winnertech.com,
	Chen-Yu Tsai <wens@...e.org>,
	Hans de Goede <hdegoede@...hat.com>,
	Carlo Caione <carlo@...one.org>,
	linux-arm-kernel@...ts.infradead.org, linux-kernel@...r.kernel.org,
	dev@...ux-sunxi.org,
	Boris BREZILLON <boris.brezillon@...e-electrons.com>
Subject: [RFC PATCH v2] regmap: smbus: add support for regmap over SMBus

SMBus is a subset of the I2C protocol, oftenly used to access registers on
external devices.

I2C adapters 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@...e-electrons.com>
---
Hello Mark,

Sorry for the noise, but I forgot to add the changes in regmap.h in my
previous commit.

Best Regards,

Boris

 drivers/base/regmap/Kconfig        |   5 +-
 drivers/base/regmap/Makefile       |   1 +
 drivers/base/regmap/regmap-smbus.c | 364 +++++++++++++++++++++++++++++++++++++
 include/linux/regmap.h             |  13 ++
 4 files changed, 382 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@...e-electrons.com>
+ *
+ * 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");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 85691b9..34ef2c7 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -317,6 +317,13 @@ struct regmap_bus {
 	enum regmap_endian val_format_endian_default;
 };
 
+enum regmap_smbus_transfer_type {
+	REGMAP_SMBUS_BYTE_TRANSFER,
+	REGMAP_SMBUS_WORD_TRANSFER,
+	REGMAP_SMBUS_BLOCK_TRANSFER,
+	REGMAP_SMBUS_I2C_BLOCK_TRANSFER,
+};
+
 struct regmap *regmap_init(struct device *dev,
 			   const struct regmap_bus *bus,
 			   void *bus_context,
@@ -325,6 +332,9 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
 				 const struct regmap_config *config);
 struct regmap *regmap_init_i2c(struct i2c_client *i2c,
 			       const struct regmap_config *config);
+struct regmap *regmap_init_smbus(struct i2c_client *i2c,
+				const struct regmap_config *config,
+				enum regmap_smbus_transfer_type transfer_type);
 struct regmap *regmap_init_spi(struct spi_device *dev,
 			       const struct regmap_config *config);
 struct regmap *regmap_init_spmi_base(struct spmi_device *dev,
@@ -341,6 +351,9 @@ struct regmap *devm_regmap_init(struct device *dev,
 				const struct regmap_config *config);
 struct regmap *devm_regmap_init_i2c(struct i2c_client *i2c,
 				    const struct regmap_config *config);
+struct regmap *devm_regmap_init_smbus(struct i2c_client *i2c,
+				const struct regmap_config *config,
+				enum regmap_smbus_transfer_type transfer_type);
 struct regmap *devm_regmap_init_spi(struct spi_device *dev,
 				    const struct regmap_config *config);
 struct regmap *devm_regmap_init_spmi_base(struct spmi_device *dev,
-- 
1.8.3.2

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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ