@@ -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
@@ -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
new file mode 100644
@@ -0,0 +1,461 @@
+/*
+ * Advantech iManager SMBus bus driver
+ *
+ * Copyright (C) 2016 Advantech Co., Ltd.
+ * Author: Richard Vidal-Dorsch <richard.dorsch@advantech.com>
+ *
+ * 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 <linux/device.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/mfd/imanager.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#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 <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager-smbus");
Signed-off-by: Richard Vidal-Dorsch <richard.dorsch@gmail.com> --- 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