From patchwork Wed Nov 2 08:37:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: richard.dorsch@gmail.com X-Patchwork-Id: 690316 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3t81mL5Mg6z9tl4 for ; Wed, 2 Nov 2016 19:41:38 +1100 (AEDT) Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b=O+pAfHJK; dkim-atps=neutral Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754444AbcKBIlU (ORCPT ); Wed, 2 Nov 2016 04:41:20 -0400 Received: from mail-pf0-f194.google.com ([209.85.192.194]:34642 "EHLO mail-pf0-f194.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754703AbcKBIiJ (ORCPT ); Wed, 2 Nov 2016 04:38:09 -0400 Received: by mail-pf0-f194.google.com with SMTP id y68so1156812pfb.1; Wed, 02 Nov 2016 01:38:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=rVdyp7xXAn0jttQ+98XHdtTRA/7v3wkxDBfTRKID1Fg=; b=O+pAfHJK54kRYzehk3PkN81Ns8oYNCvNfMozZa7egRoNQHrFwp/vP+ipOj1H8Jsmfc wJncLBHiKwq6QQkLh+2e5LoGhh5keYWtkPvms3S9bvgsH0V5SWhQaxLBwJc5ppYHRxAl WkYlxGHyIdLzqr1gSRtoZQQv/W2zA0xIuLnDG+g4S8Q96MwB2Hg+vH1KxdykoHeuYm7t 2TCTCA4DDzSD6ZPr/G75fjcrcl+DYzdesX4mGAJBf38Ecwec8/+DWktZ/ARWQwCgpkoH SB0lRzqpZMQjmjb0D1/Eee64H/SvYNEsbPlIcAKAbA89tpaBvP6ZULYeApbobegWDkgk 7WBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=rVdyp7xXAn0jttQ+98XHdtTRA/7v3wkxDBfTRKID1Fg=; b=MQpZoguhhNtek99SzcuPnfmtRhEM7a4TtBxUDzz2aMvKDj/yp2oUxLN3or/oGUlB8x /uPhhPyq1GVC+QbhEOJESS4SggI5Fr0kouJxjGdsfLtd26s0FzdEpi4lsQV+0dv0eFuB tQSilfLjUAHc5ryjt5M9LZolP6krxbl+vdYJF8Du0+AvBXfCPffme2wvzmFL6ieo9SDf 1neBRJ+duEa/GQNqHPMBZVr0Q7vYYAkClXhqYcHcdVLKsNMHiF5UcErNAhNg7obttUvI gNrvJEQ9sfVTl6kwn4XLJmvDIKP641c+c/3qOzeOWlHshvmklXkmKwT6uc4sND4GSlOk ZHpA== X-Gm-Message-State: ABUngvfvatzG5ZsF1aiJ1iTxKU5SfCNXKfAc3i/1tWIbGIU/1WGM6a8a3m0oDfBC3Jzqzg== X-Received: by 10.98.89.6 with SMTP id n6mr4750319pfb.43.1478075888386; Wed, 02 Nov 2016 01:38:08 -0700 (PDT) Received: from localhost.localdomain.localdomain (cpe-172-89-102-93.socal.res.rr.com. [172.89.102.93]) by smtp.gmail.com with ESMTPSA id 188sm2588231pfd.9.2016.11.02.01.38.06 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 02 Nov 2016 01:38:08 -0700 (PDT) From: Richard Vidal-Dorsch To: linus.walleij@linaro.org, gnurou@gmail.com, jdelvare@suse.com, linux@roeck-us.net, wsa@the-dreams.de, lee.jones@linaro.org, jingoohan1@gmail.com, tomi.valkeinen@ti.com, wim@iguana.be, linux-kernel@vger.kernel.org, linux-gpio@vger.kernel.org, linux-hwmon@vger.kernel.org, linux-i2c@vger.kernel.org, linux-fbdev@vger.kernel.org, linux-watchdog@vger.kernel.org, k.kozlowski@samsung.com Cc: Richard Vidal-Dorsch , jo.sunga@advantech.com, weilun.huang@advantech.com, andrew.chou@advantech.com Subject: [PATCH v4 4/6] Add Advantech iManager I2C driver Date: Wed, 2 Nov 2016 01:37:49 -0700 Message-Id: <20161102083751.6335-5-richard.dorsch@gmail.com> X-Mailer: git-send-email 2.10.1 In-Reply-To: <20161102083751.6335-1-richard.dorsch@gmail.com> References: <20161102083751.6335-1-richard.dorsch@gmail.com> Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org Signed-off-by: Richard Vidal-Dorsch --- drivers/i2c/busses/Kconfig | 10 + drivers/i2c/busses/Makefile | 1 + drivers/i2c/busses/i2c-imanager.c | 461 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 472 insertions(+) create mode 100644 drivers/i2c/busses/i2c-imanager.c diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index d252276..7d1ecb4 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -42,6 +42,16 @@ config I2C_ALI15X3 This driver can also be built as a module. If so, the module will be called i2c-ali15x3. +config I2C_IMANAGER + tristate "Advantech iManager I2C Interface" + depends on MFD_IMANAGER + help + This enables support for Advantech iManager I2C of some + Advantech SOM, MIO, AIMB, and PCM modules/boards. + + This driver can also be built as a module. If so, the module + will be called i2c-imanager. + config I2C_AMD756 tristate "AMD 756/766/768/8111 and nVidia nForce" depends on PCI diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 29764cc..d9ff210 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -54,6 +54,7 @@ obj-$(CONFIG_I2C_GPIO) += i2c-gpio.o obj-$(CONFIG_I2C_HIGHLANDER) += i2c-highlander.o obj-$(CONFIG_I2C_HIX5HD2) += i2c-hix5hd2.o obj-$(CONFIG_I2C_IBM_IIC) += i2c-ibm_iic.o +obj-$(CONFIG_I2C_IMANAGER) += i2c-imanager.o obj-$(CONFIG_I2C_IMG) += i2c-img-scb.o obj-$(CONFIG_I2C_IMX) += i2c-imx.o obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o diff --git a/drivers/i2c/busses/i2c-imanager.c b/drivers/i2c/busses/i2c-imanager.c new file mode 100644 index 0000000..15c496d --- /dev/null +++ b/drivers/i2c/busses/i2c-imanager.c @@ -0,0 +1,461 @@ +/* + * Advantech iManager SMBus bus driver + * + * Copyright (C) 2016 Advantech Co., Ltd. + * Author: Richard Vidal-Dorsch + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#define I2C_SMBUS_BLOCK_SIZE 32UL +#define I2C_MAX_READ_SIZE I2C_SMBUS_BLOCK_SIZE +#define I2C_MAX_WRITE_SIZE (I2C_SMBUS_BLOCK_SIZE - 1) + +#define EC_HWRAM_OFFSET_STATUS 0UL + +#define I2C_ERR_PROTO 0x19UL +#define I2C_ERR_TIMEOUT 0x18UL +#define I2C_ERR_ACCESS 0x17UL +#define I2C_ERR_UNKNOWN 0x13UL +#define I2C_ERR_ADDR_NACK 0x10UL + +#define SMBUS_FREQ_50KHZ 0x0100 +#define SMBUS_FREQ_100KHZ 0x0200 +#define SMBUS_FREQ_400KHZ 0x0300 + +#define imanager_i2c_wr_combined(ec, message) \ + imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_WR) + +#define imanager_i2c_rw_combined(ec, message) \ + imanager_i2c_block_wr_rw_combined(ec, message, EC_CMD_I2C_RW) + +struct ec_i2c_status { + u32 error : 7; + u32 complete : 1; +}; + +struct adapter_info { + struct i2c_adapter adapter; + int smb_devid; +}; + +struct imanager_i2c_data { + struct device *dev; + struct imanager_device_data *imgr; + struct adapter_info adap_info[EC_MAX_SMB_NUM]; + int nadap; +}; + +static int imanager_i2c_eval_status(u8 status) +{ + struct ec_i2c_status *_status = (struct ec_i2c_status *)&status; + + switch (_status->error) { + case 0: + return 0; + case I2C_ERR_ADDR_NACK: + return -ENXIO; + case I2C_ERR_ACCESS: + return -EACCES; + case I2C_ERR_UNKNOWN: + return -EAGAIN; + case I2C_ERR_TIMEOUT: + return -ETIME; + case I2C_ERR_PROTO: + return -EPROTO; + } + + return -EIO; +} + +static int imanager_i2c_wait_proc_complete(struct imanager_ec_data *ec) +{ + int ret, i; + u8 val; + + for (i = 0; i < EC_MAX_RETRY; i++) { + ret = imanager_read_ram(ec, EC_RAM_HW, EC_HWRAM_OFFSET_STATUS, + &val, sizeof(val)); + if (ret < 0) + return ret; + + if (!val) + return 0; + + usleep_range(EC_DELAY_MIN, EC_DELAY_MAX); + } + + return -ETIME; +} + +static int imanager_i2c_block_wr_rw_combined(struct imanager_ec_data *ec, + struct imanager_ec_message *msg, + unsigned int protocol) +{ + int ret; + + ret = imanager_i2c_wait_proc_complete(ec); + if (ret) + return ret; + + ret = imanager_write(ec, protocol, msg); + if (ret) + return imanager_i2c_eval_status(ret); + + if (msg->rlen) { + if (msg->rlen == 1) + return msg->u.data[0]; + else if (msg->rlen == 2) + return (msg->u.data[1] << 8) | msg->u.data[0]; + else + return msg->rlen; + } + + return 0; +} + +static inline int +imanager_i2c_write_freq(struct imanager_ec_data *ec, int did, int freq) +{ + return imanager_write16(ec, EC_CMD_SMB_FREQ_WR, did, freq); +} + +static inline int eval_read_len(int len) +{ + return len && len <= I2C_MAX_READ_SIZE ? len : I2C_MAX_READ_SIZE; +} + +static inline int +imanager_i2c_read_block(struct imanager_ec_data *ec, + struct imanager_ec_message *msg, u8 *buf) +{ + int ret; + + ret = imanager_i2c_wr_combined(ec, msg); + if (ret < 0) + return ret; + + buf[0] = ret; + memcpy(&buf[1], msg->u.data, ret); + + return 0; +} + +static inline int +imanager_i2c_write_block(struct imanager_ec_data *ec, + struct imanager_ec_message *msg, u8 *buf) +{ + if (!buf[0] || (buf[0] > I2C_MAX_WRITE_SIZE)) + return -EINVAL; + + memcpy(&msg->u.data[EC_MSG_HDR_SIZE], &buf[1], buf[0]); + + return imanager_i2c_wr_combined(ec, msg); +} + +static s32 imanager_i2c_xfer(struct i2c_adapter *adap, u16 addr, ushort flags, + char read_write, u8 command, int size, + union i2c_smbus_data *smb_data) +{ + struct imanager_i2c_data *data = i2c_get_adapdata(adap); + struct imanager_device_data *imgr = data->imgr; + struct imanager_ec_data *ec = &imgr->ec; + struct device *dev = data->dev; + int smb_devid = *(int *)adap->algo_data; + int val, ret = 0; + u16 addr16 = addr << 1; /* convert to 8-bit i2c slave address */ + u8 *buf = smb_data->block; + struct imanager_ec_message msg = { + .rlen = 0, + .wlen = EC_MSG_HDR_SIZE, + .param = smb_devid, + .u = { + .smb.hdr = { + .addr_low = addr16 & 0x00ff, + .addr_high = addr16 >> 8, + .rlen = 0, + .wlen = 0, + }, + }, + }; + struct imanager_ec_smb_message *smb = &msg.u.smb; + + mutex_lock(&imgr->lock); + + switch (size) { + case I2C_SMBUS_QUICK: + msg.rlen = 0; + smb->hdr.rlen = 0; + smb->hdr.wlen = 1; + ret = imanager_i2c_wr_combined(ec, &msg); + break; + case I2C_SMBUS_BYTE: + if (read_write == I2C_SMBUS_WRITE) { + msg.rlen = 1; + smb->hdr.rlen = 1; + smb->hdr.wlen = 1; + smb->hdr.cmd = command; + val = imanager_i2c_wr_combined(ec, &msg); + if (val < 0) + ret = val; + } else { + if (!smb_data) { + ret = -EINVAL; + break; + } + msg.rlen = 1; + smb->hdr.rlen = 1; + smb->hdr.wlen = 0; + val = imanager_i2c_rw_combined(ec, &msg); + if (val < 0) + ret = val; + else + smb_data->byte = val; + break; + } + case I2C_SMBUS_BYTE_DATA: + if (!smb_data) { + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + msg.rlen = 1; + msg.wlen += 1; + smb->hdr.rlen = 0; + smb->hdr.wlen = 2; + smb->hdr.cmd = command; + smb->data[0] = smb_data->byte; + val = imanager_i2c_wr_combined(ec, &msg); + } else { + msg.rlen = 1; + smb->hdr.rlen = 1; + smb->hdr.wlen = 1; + smb->hdr.cmd = command; + val = imanager_i2c_wr_combined(ec, &msg); + } + if (val < 0) + ret = val; + else + smb_data->byte = val; + break; + case I2C_SMBUS_WORD_DATA: + if (!smb_data) { + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + msg.rlen = 1; + msg.wlen += 2; + smb->hdr.rlen = 0; + smb->hdr.wlen = 3; + smb->hdr.cmd = command; + smb->data[0] = smb_data->word & 0x00ff; + smb->data[1] = smb_data->word >> 8; + val = imanager_i2c_wr_combined(ec, &msg); + } else { + msg.rlen = 2; + smb->hdr.rlen = 2; + smb->hdr.wlen = 1; + smb->hdr.cmd = command; + val = imanager_i2c_wr_combined(ec, &msg); + } + if (val < 0) + ret = val; + else + smb_data->word = val; + break; + case I2C_SMBUS_BLOCK_DATA: + if (!smb_data) { + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + msg.rlen = 1; + msg.wlen += buf[0]; + smb->hdr.rlen = 0; + smb->hdr.wlen = 1 + buf[0]; + smb->hdr.cmd = command; + ret = imanager_i2c_write_block(ec, &msg, buf); + } else { + msg.rlen = eval_read_len(buf[0]); + smb->hdr.rlen = msg.rlen; + smb->hdr.wlen = 1; + smb->hdr.cmd = command; + ret = imanager_i2c_read_block(ec, &msg, buf); + } + break; + case I2C_SMBUS_I2C_BLOCK_DATA: + if (!smb_data) { + ret = -EINVAL; + break; + } + if (read_write == I2C_SMBUS_WRITE) { + msg.rlen = 1; + msg.wlen += buf[0]; + smb->hdr.rlen = 0; + smb->hdr.wlen = 1 + buf[0]; + smb->hdr.cmd = command; + ret = imanager_i2c_write_block(ec, &msg, buf); + } else { + msg.rlen = eval_read_len(buf[0]); + smb->hdr.rlen = msg.rlen; + smb->hdr.wlen = 1; + smb->hdr.cmd = command; + ret = imanager_i2c_read_block(ec, &msg, buf); + } + break; + default: + dev_err(dev, "Unsupported transaction %d\n", size); + ret = -EOPNOTSUPP; + } + + mutex_unlock(&imgr->lock); + + return ret; +} + +static u32 imanager_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | + I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_I2C_BLOCK; +} + +static const struct i2c_algorithm imanager_i2c_algorithm = { + .smbus_xfer = imanager_i2c_xfer, + .functionality = imanager_i2c_func, +}; + +static const struct i2c_adapter imanager_i2c_adapters[] = { + [SMB_EEP] = { + .owner = THIS_MODULE, + .name = "iManager SMB EEP adapter", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &imanager_i2c_algorithm, + }, + [I2C_OEM] = { + .owner = THIS_MODULE, + .name = "iManager I2C OEM adapter", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &imanager_i2c_algorithm, + }, + [SMB_1] = { + .owner = THIS_MODULE, + .name = "iManager SMB 1 adapter", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &imanager_i2c_algorithm, + }, + [SMB_PECI] = { + .owner = THIS_MODULE, + .name = "iManager SMB PECI adapter", + .class = I2C_CLASS_HWMON | I2C_CLASS_SPD, + .algo = &imanager_i2c_algorithm, + }, +}; + +static int +imanager_i2c_add_bus(struct imanager_i2c_data *i2c, struct adapter_info *info, + const struct i2c_adapter *adap, int did, int freq) +{ + int ret; + + info->adapter = *adap; + info->adapter.dev.parent = i2c->dev; + info->smb_devid = did; + info->adapter.algo_data = &info->smb_devid; + i2c_set_adapdata(&info->adapter, i2c); + + ret = i2c_add_adapter(&info->adapter); + if (ret) { + dev_warn(i2c->dev, "Failed to add %s\n", info->adapter.name); + return ret; + } + + ret = imanager_i2c_write_freq(&i2c->imgr->ec, did, freq); + if (ret < 0) + dev_warn(i2c->dev, "Failed to set bus frequency of %s\n", + info->adapter.name); + + return 0; +} + +static int imanager_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct imanager_device_data *imgr = dev_get_drvdata(dev->parent); + struct imanager_device_attribute **attr = imgr->ec.i2c.attr; + struct imanager_i2c_data *data; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->imgr = imgr; + data->dev = dev; + + if (attr[SMB_EEP]) + imanager_i2c_add_bus(data, &data->adap_info[data->nadap++], + &imanager_i2c_adapters[SMB_EEP], + attr[SMB_EEP]->did, SMBUS_FREQ_100KHZ); + + if (attr[I2C_OEM]) + imanager_i2c_add_bus(data, &data->adap_info[data->nadap++], + &imanager_i2c_adapters[I2C_OEM], + attr[I2C_OEM]->did, SMBUS_FREQ_400KHZ); + + if (attr[SMB_1]) + imanager_i2c_add_bus(data, &data->adap_info[data->nadap++], + &imanager_i2c_adapters[SMB_1], + attr[SMB_1]->did, SMBUS_FREQ_100KHZ); + + if (attr[SMB_PECI]) + imanager_i2c_add_bus(data, &data->adap_info[data->nadap++], + &imanager_i2c_adapters[SMB_PECI], + attr[SMB_PECI]->did, SMBUS_FREQ_100KHZ); + + platform_set_drvdata(pdev, data); + + return 0; +} + +static int imanager_i2c_remove(struct platform_device *pdev) +{ + struct imanager_i2c_data *i2c = dev_get_drvdata(&pdev->dev); + int i; + + for (i = 0; i < i2c->nadap; i++) { + i2c_del_adapter(&i2c->adap_info[i].adapter); + i2c_set_adapdata(&i2c->adap_info[i].adapter, NULL); + } + + return 0; +} + +static struct platform_driver imanager_i2c_driver = { + .driver = { + .name = "imanager-smbus", + }, + .probe = imanager_i2c_probe, + .remove = imanager_i2c_remove, +}; + +module_platform_driver(imanager_i2c_driver); + +MODULE_DESCRIPTION("Advantech iManager SMBus Driver"); +MODULE_AUTHOR("Richard Vidal-Dorsch "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imanager-smbus");