From patchwork Mon Apr 14 13:08:05 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Boris BREZILLON X-Patchwork-Id: 3980701 Return-Path: X-Original-To: patchwork-linux-arm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 70ACB9F2BA for ; Mon, 14 Apr 2014 13:10:42 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 36542201C8 for ; Mon, 14 Apr 2014 13:10:41 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.9]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id F3BD4201D3 for ; Mon, 14 Apr 2014 13:10:39 +0000 (UTC) Received: from localhost ([127.0.0.1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1WZgcn-0001n5-L2; Mon, 14 Apr 2014 13:08:37 +0000 Received: from top.free-electrons.com ([176.31.233.9] helo=mail.free-electrons.com) by bombadil.infradead.org with esmtp (Exim 4.80.1 #2 (Red Hat Linux)) id 1WZgcj-0001fC-He for linux-arm-kernel@lists.infradead.org; Mon, 14 Apr 2014 13:08:34 +0000 Received: by mail.free-electrons.com (Postfix, from userid 106) id 2A0388E5; Mon, 14 Apr 2014 15:08:10 +0200 (CEST) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from localhost.localdomain (col31-4-88-188-83-94.fbx.proxad.net [88.188.83.94]) by mail.free-electrons.com (Postfix) with ESMTPSA id 3F44F7A8; Mon, 14 Apr 2014 15:08:09 +0200 (CEST) From: Boris BREZILLON To: Mark Brown Subject: [RFC PATCH v2] regmap: smbus: add support for regmap over SMBus Date: Mon, 14 Apr 2014 15:08:05 +0200 Message-Id: <1397480885-11962-1-git-send-email-boris.brezillon@free-electrons.com> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1397292335-5516-1-git-send-email-boris.brezillon@free-electrons.com> References: <1397292335-5516-1-git-send-email-boris.brezillon@free-electrons.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140414_060834_040564_75CF92B1 X-CRM114-Status: GOOD ( 23.04 ) X-Spam-Score: -0.0 (/) Cc: Boris BREZILLON , dev@linux-sunxi.org, Greg Kroah-Hartman , linux-kernel@vger.kernel.org, Hans de Goede , Chen-Yu Tsai , Shuge , Maxime Ripard , Carlo Caione , kevin@allwinnertech.com, linux-arm-kernel@lists.infradead.org X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 + * + * 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 + +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, ®map_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, ®map_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,