[4/6] Add Advantech iManager I2C driver
diff mbox

Message ID 1452292166-20118-5-git-send-email-richard.dorsch@gmail.com
State Superseded
Headers show

Commit Message

richard.dorsch@gmail.com Jan. 8, 2016, 10:29 p.m. UTC
From: Richard Vidal-Dorsch <richard.dorsch@gmail.com>

---
 Documentation/i2c/busses/i2c-imanager |  48 ++++
 drivers/i2c/busses/Kconfig            |  11 +
 drivers/i2c/busses/Makefile           |   2 +
 drivers/i2c/busses/imanager-ec-i2c.c  | 466 ++++++++++++++++++++++++++++++++++
 drivers/i2c/busses/imanager-i2c.c     | 240 +++++++++++++++++
 include/linux/mfd/imanager/i2c.h      |  55 ++++
 6 files changed, 822 insertions(+)
 create mode 100644 Documentation/i2c/busses/i2c-imanager
 create mode 100644 drivers/i2c/busses/imanager-ec-i2c.c
 create mode 100644 drivers/i2c/busses/imanager-i2c.c
 create mode 100644 include/linux/mfd/imanager/i2c.h

Patch
diff mbox

diff --git a/Documentation/i2c/busses/i2c-imanager b/Documentation/i2c/busses/i2c-imanager
new file mode 100644
index 0000000..d149fbf
--- /dev/null
+++ b/Documentation/i2c/busses/i2c-imanager
@@ -0,0 +1,48 @@ 
+Kernel driver imanager_i2c
+==========================
+
+This platform driver provides support for iManager I2C/SMBus.
+
+This driver depends on imanager (mfd).
+
+Module Parameters
+-----------------
+
+* bus_frequency (unsigned short)
+Set desired bus frequency. Valid values (kHz) are:
+  50  Slow
+ 100  Standard (default)
+ 400  Fast
+
+
+Description
+-----------
+
+The Advantech iManager provides up to four SMBus controllers. One of them
+is configured for I2C compatibility.
+
+
+Process Call Support
+--------------------
+
+Not supported.
+
+
+I2C Block Read Support
+----------------------
+
+I2C block read is supported.
+
+
+SMBus 2.0 Support
+-----------------
+
+Several SMBus 2.0 features are supported.
+No PEC support.
+
+
+Interrupt Support
+-----------------
+
+No interrupt support available
+
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 7b0aa82..4c401a4 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -42,6 +42,17 @@  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.
+	  Requires mfd-core and imanager-core to function properly.
+
+	  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 37f2819..da76404 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -15,6 +15,8 @@  obj-$(CONFIG_I2C_AMD8111)	+= i2c-amd8111.o
 obj-$(CONFIG_I2C_I801)		+= i2c-i801.o
 obj-$(CONFIG_I2C_ISCH)		+= i2c-isch.o
 obj-$(CONFIG_I2C_ISMT)		+= i2c-ismt.o
+i2c-imanager-objs		:= imanager-i2c.o imanager-ec-i2c.o
+obj-$(CONFIG_I2C_IMANAGER)	+= i2c-imanager.o
 obj-$(CONFIG_I2C_NFORCE2)	+= i2c-nforce2.o
 obj-$(CONFIG_I2C_NFORCE2_S4985)	+= i2c-nforce2-s4985.o
 obj-$(CONFIG_I2C_PIIX4)		+= i2c-piix4.o
diff --git a/drivers/i2c/busses/imanager-ec-i2c.c b/drivers/i2c/busses/imanager-ec-i2c.c
new file mode 100644
index 0000000..e94964f
--- /dev/null
+++ b/drivers/i2c/busses/imanager-ec-i2c.c
@@ -0,0 +1,466 @@ 
+/*
+ * Advantech iManager I2C bus core
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/byteorder/generic.h>
+#include <linux/mfd/imanager/ec.h>
+#include <linux/mfd/imanager/i2c.h>
+
+#define I2C_SMBUS_BLOCK_SIZE	32
+
+#define EVAL_WR_SIZE(x)	\
+		(x < I2C_SMBUS_BLOCK_SIZE ? x : I2C_SMBUS_BLOCK_SIZE - 1)
+#define EVAL_RD_SIZE(x) \
+		(x && (x <= I2C_SMBUS_BLOCK_SIZE) ? x : I2C_SMBUS_BLOCK_SIZE)
+
+#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 EC_SMB_DID(N)		(0x28 + N)
+
+struct ec_i2c_status {
+	u32 error	: 7;
+	u32 complete	: 1;
+};
+
+static const struct imanager_i2c_device *i2c;
+
+static int i2c_core_eval_status(u8 _status)
+{
+	struct ec_i2c_status *status = (struct ec_i2c_status *)&_status;
+	int err = 0;
+
+	switch (status->error) {
+	case 0:
+		break;
+	case I2C_ERR_ADDR_NACK:
+		err = -ENODEV;
+		break;
+	case I2C_ERR_ACCESS:
+	case I2C_ERR_UNKNOWN:
+		err = -EAGAIN;
+		break;
+	case I2C_ERR_TIMEOUT:
+		err = -ETIME;
+		break;
+	case I2C_ERR_PROTO:
+		err = -EPROTO;
+		break;
+	default:
+		pr_err("Undefined status code 0x%02X\n", status->error);
+		err = -EIO;
+		break;
+	}
+
+	return err;
+}
+
+static inline int i2c_send_message(u8 cmd, u8 param, struct ec_message *msg)
+{
+	int err;
+
+	err = imanager_msg_write(cmd, param, msg);
+	if (err)
+		return i2c_core_eval_status(err);
+
+	return 0;
+}
+
+static int i2c_core_blk_wr_rw_combined(u8 proto, struct ec_message *msg)
+{
+	int err;
+
+	if (WARN_ON(!msg))
+		return -EINVAL;
+
+	err = imanager_wait_proc_complete(EC_HWRAM_OFFSET_STATUS, 0);
+	if (err)
+		return err;
+
+	err = i2c_send_message(proto, i2c->i2coem->did, msg);
+	if (err)
+		return err;
+
+	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;
+}
+
+/* Write-Read and Read-Write wrappers */
+static inline int i2c_core_wr_combined(struct ec_message *msg)
+{
+	return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_WR, msg);
+}
+
+static inline int i2c_core_rw_combined(struct ec_message *msg)
+{
+	return i2c_core_blk_wr_rw_combined(EC_CMD_I2C_RW, msg);
+}
+
+/*
+ * iManager I2C core API
+ */
+int i2c_core_write_quick(u16 addr)
+{
+	struct ec_message msg = {
+		.rlen = 0,
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 0,
+				.wlen = 1,
+			},
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_byte(u16 addr)
+{
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 1,
+				.wlen = 0,
+			},
+		},
+	};
+
+	return i2c_core_rw_combined(&msg);
+}
+
+int i2c_core_write_byte(u16 addr, u8 cmd)
+{
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 1,
+				.wlen = 1,
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_byte_data(u16 addr, u8 cmd)
+{
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 1,
+				.wlen = 1,
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value)
+{
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header) + 1,
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 0,
+				.wlen = 2,
+				.cmd  = cmd,
+			},
+			.smb.data[0] = value,
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_word_data(u16 addr, u8 cmd)
+{
+	struct ec_message msg = {
+		.rlen = 2,
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 2,
+				.wlen = 1,
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value)
+{
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header) + 2,
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 0,
+				.wlen = 3,
+				.cmd  = cmd,
+			},
+			.smb.data[0] = LOBYTE16(value),
+			.smb.data[1] = HIBYTE16(value),
+		},
+	};
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+	int i;
+	struct ec_message msg = {
+		.rlen = 1,
+		.wlen = sizeof(struct ec_message_header) +
+			EVAL_WR_SIZE(buf[0]),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = 0,
+				.wlen = 1 + EVAL_WR_SIZE(buf[0]),
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	if ((buf[0] == 0) || (buf[0] >= I2C_MAX_WRITE_BYTES)) {
+		pr_err("Invalid I2C write length %d\n", buf[0]);
+		return -EINVAL;
+	}
+
+	for (i = 0; i < EVAL_WR_SIZE(buf[0]); i++)
+		msg.u.data[i + sizeof(struct ec_message_header)] = buf[i + 1];
+
+	return i2c_core_wr_combined(&msg);
+}
+
+int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+	int i;
+	int ret;
+	struct ec_message msg = {
+		.rlen = EVAL_RD_SIZE(buf[0]),
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = EVAL_RD_SIZE(buf[0]),
+				.wlen = 1,
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	/*
+	 * If buf[0] == 0 EC will read I2C_MAX_READ_BYTES
+	 */
+	ret = i2c_core_wr_combined(&msg);
+	if (ret < 0) {
+		pr_err("I2C transaction failed\n");
+		return ret;
+	}
+
+	buf[0] = ret;
+	for (i = 0; i < ret; i++)
+		buf[i + 1] = msg.u.data[i];
+
+	return 0;
+}
+
+int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+	int i;
+	int ret;
+	struct ec_message msg = {
+		.rlen = EVAL_RD_SIZE(buf[0]),
+		.wlen = sizeof(struct ec_message_header),
+		.u = {
+			.smb.hdr = {
+				.addr_low  = LOADDR16(addr),
+				.addr_high = HIADDR16(addr),
+				.rlen = EVAL_RD_SIZE(buf[0]),
+				.wlen = 1,
+				.cmd  = cmd,
+			},
+		},
+	};
+
+	if ((buf[0] == 0) || (buf[0] > I2C_MAX_READ_BYTES)) {
+		pr_err("Invalid I2C read length\n");
+		return -EINVAL;
+	}
+
+	ret = i2c_core_wr_combined(&msg);
+	if (ret < 0) {
+		pr_err("I2C transaction failed\n");
+		return ret;
+	}
+
+	buf[0] = ret;
+	for (i = 0; i < ret; i++)
+		buf[i + 1] = msg.u.data[i];
+
+	return 0;
+}
+
+int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf)
+{
+	if (WARN_ON(!buf))
+		return -EINVAL;
+
+	return i2c_core_write_block_data(addr, cmd, buf);
+}
+
+int i2c_core_smb_get_freq(u32 bus_id)
+{
+	int ret = 0, f;
+	int freq_id, freq;
+
+	if (WARN_ON(bus_id > I2C_OEM_1))
+		return -EINVAL;
+
+	switch (i2c->ecdev->id) {
+	case IT8518:
+	case IT8528:
+		ret = imanager_read_word(EC_CMD_SMB_FREQ_RD,
+					 EC_SMB_DID(bus_id));
+		if (ret < 0) {
+			pr_err("Failed to get bus frequency\n");
+			return ret;
+		}
+
+		freq_id = HIBYTE16(ret);
+		f = LOBYTE16(ret);
+		switch (freq_id) {
+		case 0:
+			freq = f;
+			break;
+		case 1:
+			freq = 50;
+			break;
+		case 2:
+			freq = 100;
+			break;
+		case 3:
+			freq = 400;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	default:
+		pr_err("EC version not supported!\n");
+		return -ENODEV;
+	}
+
+	return freq;
+}
+
+int i2c_core_smb_set_freq(u32 bus_id, u32 freq)
+{
+	int err;
+	u16 val;
+
+	if (WARN_ON(bus_id > I2C_OEM_1))
+		return -EINVAL;
+
+	switch (i2c->ecdev->id) {
+	case IT8518:
+	case IT8528:
+		switch (freq) {
+		case 50:
+			val = 0x0100;
+			break;
+		case 100:
+			val = 0x0200;
+			break;
+		case 400:
+			val = 0x0300;
+			break;
+		default:
+			if (freq < 50)
+				val = freq;
+			else
+				return -EINVAL;
+		}
+
+		err = imanager_write_word(EC_CMD_SMB_FREQ_WR,
+					  EC_SMB_DID(bus_id), val);
+		if (err) {
+			pr_err("Failed to set I2C bus frequency\n");
+			return err;
+		}
+		break;
+	default:
+		pr_err("EC version not supported!\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+int i2c_core_init(void)
+{
+	i2c = imanager_get_i2c_device();
+	if (!i2c)
+		return -ENODEV;
+
+	return 0;
+}
+
diff --git a/drivers/i2c/busses/imanager-i2c.c b/drivers/i2c/busses/imanager-i2c.c
new file mode 100644
index 0000000..fb7236d
--- /dev/null
+++ b/drivers/i2c/busses/imanager-i2c.c
@@ -0,0 +1,240 @@ 
+/*
+ * Advantech iManager I2C bus driver
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/mfd/imanager/core.h>
+#include <linux/mfd/imanager/i2c.h>
+
+static uint bus_frequency = 100;
+module_param(bus_frequency, uint, 0);
+MODULE_PARM_DESC(bus_frequency,
+	"I2C bus frequency [50, 100, 400]kHz (defaults to 100kHz)");
+
+struct imanager_i2c_data {
+	struct imanager_device_data *idev;
+	struct i2c_adapter adapter;
+};
+
+static int imanager_smb_access(struct i2c_adapter *adap, u16 addr,
+	unsigned short flags, char read_write, u8 command,
+	int size, union i2c_smbus_data *smb_data)
+{
+	struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+	struct device *dev = data->adapter.dev.parent;
+	int ret = 0;
+	int val = 0;
+
+	if (!data)
+		return -ENODEV;
+
+	addr <<= 1;
+
+	mutex_lock(&data->idev->lock);
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		ret = i2c_core_write_quick(addr);
+		break;
+	case I2C_SMBUS_BYTE:
+		if (read_write == I2C_SMBUS_WRITE) /* NOT tested */
+			val = i2c_core_write_byte(addr, command);
+		else
+			val = i2c_core_read_byte(addr);
+
+		if (val < 0)
+			ret = val;
+		else
+			smb_data->byte = val;
+		break;
+	case I2C_SMBUS_BYTE_DATA:
+		if (read_write == I2C_SMBUS_WRITE)
+			val = i2c_core_write_byte_data(addr, command,
+				smb_data->byte);
+		else
+			val = i2c_core_read_byte_data(addr, command);
+
+		if (val < 0)
+			ret = val;
+		else
+			smb_data->byte = val;
+		break;
+	case I2C_SMBUS_WORD_DATA:
+		if (read_write == I2C_SMBUS_WRITE)
+			val = i2c_core_write_word_data(addr, command,
+				smb_data->word);
+		else
+			val = i2c_core_read_word_data(addr, command);
+
+		if (val < 0)
+			ret = val;
+		else
+			smb_data->word = val;
+		break;
+	case I2C_SMBUS_BLOCK_DATA:
+		if (read_write == I2C_SMBUS_WRITE)
+			ret = i2c_core_write_block_data(addr, command,
+				smb_data->block);
+		else
+			ret = i2c_core_read_block_data(addr, command,
+				smb_data->block);
+		break;
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+		if (read_write == I2C_SMBUS_WRITE)
+			ret = i2c_core_write_i2c_block_data(addr, command,
+				smb_data->block);
+		else
+			ret = i2c_core_read_i2c_block_data(addr, command,
+				smb_data->block);
+		break;
+	default:
+		dev_err(dev, "Unsupported SMB transaction %d\n", size);
+		ret = -EOPNOTSUPP;
+	}
+
+	mutex_unlock(&data->idev->lock);
+
+	return ret;
+}
+
+static int imanager_i2c_access(struct i2c_adapter *adap, struct i2c_msg *msg,
+			int num)
+{
+	struct imanager_i2c_data *data = i2c_get_adapdata(adap);
+	struct device *dev = data->adapter.dev.parent;
+
+	/*
+	 * To be implemented
+	 */
+
+	dev_info(dev, "i2c_access() is not yet implemented. msg=%p, num=%d\n",
+		 msg, num);
+
+	return 0;
+}
+
+static u32 imanager_smb_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 | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm imanager_algorithm = {
+	.smbus_xfer     = imanager_smb_access,
+	.master_xfer    = imanager_i2c_access,
+	.functionality  = imanager_smb_i2c_func,
+};
+
+static int imanager_i2c_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_device_data *idev = dev_get_drvdata(dev->parent);
+	struct imanager_i2c_data *i2c;
+	int ret;
+
+	if (!idev) {
+		dev_err(dev, "Invalid platform data\n");
+		return -EINVAL;
+	}
+
+	ret = i2c_core_init();
+	if (ret) {
+		dev_err(dev, "Failed to initialize I2C core\n");
+		return -EIO;
+	}
+
+	if (bus_frequency > 100)
+		bus_frequency = 400;
+	else if (bus_frequency < 50)
+		bus_frequency = 50;
+	else
+		bus_frequency = 100;
+
+	ret = i2c_core_smb_set_freq(I2C_OEM_1, bus_frequency);
+	if (ret < 0) {
+		dev_err(dev, "Failed to set I2C bus frequency to %d kHz\n",
+				bus_frequency);
+		return ret;
+	}
+
+	ret = i2c_core_smb_get_freq(I2C_OEM_1);
+	if (ret < 0) {
+		dev_err(dev, "Failed to get I2C bus frequency\n");
+		return ret;
+	}
+	bus_frequency = ret;
+	dev_info(dev, "Bus frequency: %d kHz\n", bus_frequency);
+
+	i2c = devm_kzalloc(dev, sizeof(struct imanager_i2c_data), GFP_KERNEL);
+	if (!i2c)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, i2c);
+	i2c_set_adapdata(&i2c->adapter, i2c);
+
+	i2c->idev = idev;
+
+	i2c->adapter.owner	= THIS_MODULE;
+	i2c->adapter.class	= I2C_CLASS_HWMON | I2C_CLASS_SPD;
+	i2c->adapter.algo	= &imanager_algorithm;
+
+	/* set up the sysfs linkage to our parent device */
+	i2c->adapter.dev.parent = dev;
+
+	/* Retry up to 3 times on lost arbitration */
+	i2c->adapter.retries = 3;
+
+	snprintf(i2c->adapter.name, sizeof(i2c->adapter.name),
+		"iManager I2C driver");
+
+	ret = i2c_add_adapter(&i2c->adapter);
+	if (ret) {
+		dev_err(dev, "Failed to add SMBus adapter\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int imanager_i2c_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct imanager_i2c_data *i2c = dev_get_drvdata(dev);
+
+	i2c_del_adapter(&i2c->adapter);
+	i2c_set_adapdata(&i2c->adapter, NULL);
+
+	return 0;
+}
+
+static struct platform_driver imanager_i2c_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name  = "imanager_i2c",
+	},
+	.probe	= imanager_i2c_probe,
+	.remove	= imanager_i2c_remove,
+};
+
+module_platform_driver(imanager_i2c_driver);
+
+MODULE_DESCRIPTION("Advantech iManager I2C Driver");
+MODULE_AUTHOR("Richard Vidal-Dorsch <richard.dorsch at advantech.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:imanager_i2c");
diff --git a/include/linux/mfd/imanager/i2c.h b/include/linux/mfd/imanager/i2c.h
new file mode 100644
index 0000000..455f89f
--- /dev/null
+++ b/include/linux/mfd/imanager/i2c.h
@@ -0,0 +1,55 @@ 
+/*
+ * Advantech iManager I2C bus core
+ *
+ * Copyright (C) 2015 Advantech Co., Ltd., Irvine, CA, USA
+ * 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.
+ */
+
+#ifndef __I2C_H__
+#define __I2C_H__
+
+#include <linux/types.h>
+
+#define I2C_MAX_READ_BYTES	32
+#define I2C_MAX_WRITE_BYTES	32
+
+/* Only for setting SMBus frequency */
+enum smb_bus_id {
+	SMB_OEM_0,
+	SMB_OEM_1,
+	SMB_OEM_2,
+	SMB_EEPROM,
+	SMB_TH_0,
+	SMB_TH_1,
+	SMB_SECURITY_EEPROM,
+	I2C_OEM_1,
+};
+
+int i2c_core_init(void);
+
+int i2c_core_write_quick(u16 addr);
+
+int i2c_core_read_byte(u16 addr);
+int i2c_core_write_byte(u16 addr, u8 cmd);
+
+int i2c_core_write_byte_data(u16 addr, u8 cmd, u8 value);
+int i2c_core_read_byte_data(u16 addr, u8 cmd);
+
+int i2c_core_write_word_data(u16 addr, u8 cmd, u16 value);
+int i2c_core_read_word_data(u16 addr, u8 cmd);
+
+int i2c_core_write_block_data(u16 addr, u8 cmd, u8 *buf);
+int i2c_core_read_block_data(u16 addr, u8 cmd, u8 *buf);
+
+int i2c_core_write_i2c_block_data(u16 addr, u8 cmd, u8 *buf);
+int i2c_core_read_i2c_block_data(u16 addr, u8 cmd, u8 *buf);
+
+int i2c_core_smb_get_freq(u32 bus_id);
+int i2c_core_smb_set_freq(u32 bus_id, u32 freq);
+
+#endif