Signed-off-by: Richard Vidal-Dorsch <richard.dor...@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

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 <richard.dor...@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");
-- 
2.10.1

Reply via email to