From patchwork Sat Apr 12 08:45:35 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Boris BREZILLON X-Patchwork-Id: 3972741 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 8B96A9F374 for ; Sat, 12 Apr 2014 08:48:58 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 66787202E9 for ; Sat, 12 Apr 2014 08:48:57 +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 3E90920272 for ; Sat, 12 Apr 2014 08:48:56 +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 1WYtZp-0000FJ-3A; Sat, 12 Apr 2014 08:46:17 +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 1WYtZm-0000AN-3P for linux-arm-kernel@lists.infradead.org; Sat, 12 Apr 2014 08:46:15 +0000 Received: by mail.free-electrons.com (Postfix, from userid 106) id BA16F912; Sat, 12 Apr 2014 10:45:47 +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.3 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-80-5.fbx.proxad.net [88.188.80.5]) by mail.free-electrons.com (Postfix) with ESMTPSA id C2A5D839; Sat, 12 Apr 2014 10:45:46 +0200 (CEST) From: Boris BREZILLON To: Mark Brown , Greg Kroah-Hartman Subject: [RFC PATCH] regmap: smbus: add support for regmap over SMBus Date: Sat, 12 Apr 2014 10:45:35 +0200 Message-Id: <1397292335-5516-1-git-send-email-boris.brezillon@free-electrons.com> X-Mailer: git-send-email 1.8.3.2 X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20140412_014614_630348_886ADD2F X-CRM114-Status: GOOD ( 23.50 ) X-Spam-Score: 0.6 (/) Cc: Boris BREZILLON , dev@linux-sunxi.org, linux-kernel@vger.kernel.org, kevin@allwinnertech.com, Hans de Goede , Chen-Yu Tsai , Shuge , Maxime Ripard , Carlo Caione , 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 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 --- 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 + * + * 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");