diff mbox series

i2c: add support for Zhaoxin I2C controller # 标题部分,简明扼要

Message ID 20230504060043.13155-1-hanshu-oc@zhaoxin.com
State New
Headers show
Series i2c: add support for Zhaoxin I2C controller # 标题部分,简明扼要 | expand

Commit Message

Hans Hu May 4, 2023, 6 a.m. UTC
Add Zhaoxin I2C controller driver. It provides the access to the i2c
busses, which connects to the touchpad, eeprom, etc.

Zhaoxin I2C controller have two separate busses, so may accommodate up
to two I2C adapters. Those adapters are listed in the ACPI namespace
with the "IIC1D17" HID, and probed by a platform driver.

The driver works with IRQ mode, and supports basic I2C features. Flags
I2C_AQ_NO_ZERO_LEN and I2C_AQ_COMB_WRITE_THEN_READ are used to limit
the unsupported access.

Signed-off-by: hanshuoc <hanshu-oc@zhaoxin.com>
---
 drivers/i2c/busses/Kconfig       |  10 +
 drivers/i2c/busses/Makefile      |   1 +
 drivers/i2c/busses/i2c-zhaoxin.c | 685 +++++++++++++++++++++++++++++++
 3 files changed, 696 insertions(+)
 create mode 100644 drivers/i2c/busses/i2c-zhaoxin.c
diff mbox series

Patch

diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 25eb4e8fd22f..a6c79a428501 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -333,6 +333,16 @@  config I2C_VIAPRO
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-viapro.
 
+config I2C_ZHAOXIN
+	tristate "ZHAOXIN I2C Interface"
+	depends on PCI && ACPI
+	help
+	  If you say yes to this option, support will be included for the
+	  ZHAOXIN I2C interface
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-zhaoxin.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index af56fe2c75c0..cc470ce470ca 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -29,6 +29,7 @@  obj-$(CONFIG_I2C_SIS630)	+= i2c-sis630.o
 obj-$(CONFIG_I2C_SIS96X)	+= i2c-sis96x.o
 obj-$(CONFIG_I2C_VIA)		+= i2c-via.o
 obj-$(CONFIG_I2C_VIAPRO)	+= i2c-viapro.o
+obj-$(CONFIG_I2C_ZHAOXIN)	+= i2c-zhaoxin.o
 
 # Mac SMBus host controller drivers
 obj-$(CONFIG_I2C_HYDRA)		+= i2c-hydra.o
diff --git a/drivers/i2c/busses/i2c-zhaoxin.c b/drivers/i2c/busses/i2c-zhaoxin.c
new file mode 100644
index 000000000000..83f1f30f73a9
--- /dev/null
+++ b/drivers/i2c/busses/i2c-zhaoxin.c
@@ -0,0 +1,685 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  i2c-zhaoxin-i2c.c - Zhaoxin I2C controller driver
+ *
+ *  Copyright(c) 2021 Shanghai Zhaoxin Semiconductor Corporation.
+ *                    All rights reserved.
+ *
+ *  CONTACTS:
+ *
+ *  Hans Hu             hanshu@zhaoxin.com
+ *  Cobe Chen           cobechen@zhaoxin.com
+ */
+
+#define DRIVER_VERSION "1.4.0"
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/acpi.h>
+#include <linux/interrupt.h>
+#include <linux/version.h>
+#include <linux/platform_device.h>
+#include <linux/pci.h>
+
+#define ZX_I2C_NAME             "Zhaoxin-I2C"
+
+/*
+ * registers
+ */
+/* I2C MMIO Address Constants */
+#define IICCR_L                  0x00
+#define   MST_RST          BIT(7)
+#define   MST_RST_PATCH    BIT(6)
+#define   CPU_RDY          BIT(3)
+#define   TX_END           BIT(2)
+#define   RX_ACK           BIT(1)
+#define IICCR_H                  0x01
+#define   FIFO_EN          BIT(6)
+#define IICSLVADDR               0x02
+#define IICTCR                   0x03
+#define   FAST_SEL         BIT(7)
+#define   MASTER_RECV      BIT(6)
+#define   HS_SEL           BIT(5)
+#define IICSR                    0x04
+#define   SDA_I            BIT(3)
+#define   SCL_I            BIT(2)
+#define   READY            BIT(1)
+#define   RCV_NACK         BIT(0)
+#define IICISR                   0x06
+#define   IRQ_STS_BYTENACK BIT(5)
+#define   IRQ_STS_FIFONACK BIT(4)
+#define   IRQ_STS_FIFOEND  BIT(3)
+#define   IRQ_SCL_TIMEOUT  BIT(2)
+#define   IRQ_STS_BYTEEND  BIT(1)
+#define   IRQ_STS_ADDRNACK BIT(0)
+#define   IRQ_STS_MASK   (IRQ_STS_FIFOEND | IRQ_SCL_TIMEOUT | \
+			  IRQ_STS_BYTEEND | IRQ_STS_ADDRNACK)
+#define IICIMR                   0x08
+#define   IRQ_EN_FIFOEND   BIT(3)
+#define   IRQ_EN_TIMEOUT   BIT(2)
+#define   IRQ_EN_BYTEEND   BIT(1)
+#define   IRQ_EN_ADDRNACK  BIT(0)
+#define IICDATA2IIC              0x0A
+#define IICDATA2CPU              0x0B
+#define IICTR_FSTP               0x0C
+#define IICTR_SCLTP              0x0D
+#define IICMCR                   0x0E
+#define   DYCLK_EN         BIT(0)
+#define IIC_MST_CODE             0x0F
+#define IICCS                    0x10
+#define   CLKSEL_50M       BIT(0)
+#define IICREV                   0x11
+#define IICHCR                   0x12
+#define   FIFO_RST         (BIT(1) | BIT(0))
+#define IICHTDR                  0x13
+#define IICHRDR                  0x14
+#define IICHTLR                  0x15
+#define IICHRLR                  0x16
+#define IICHWCNTR                0x18
+#define IICHRCNTR                0x19
+
+#define FIFO_SIZE       32
+#define ZXI2C_TIMEOUT   1000
+
+struct zxi2c {
+	/* controller resources information */
+	struct device *dev;
+	struct pci_dev *pci;
+	struct i2c_adapter adap;
+	void __iomem *regs;
+	u8 irq;
+	u8 id;
+	u8 hrv;
+	u32 speed;
+	u8 fstp;		/* freq control by BIOS */
+	u8 reset_bitmask;
+
+	/* process control information */
+	u8 event;
+	u16 byte_left;
+	wait_queue_head_t waitq;
+
+	/* processing msg information */
+	u8 addr;
+	u16 len;
+	bool is_read;
+	bool is_last_msg;
+	bool dynamic;
+};
+
+#define set_byte(r, d)           iowrite8(d, r+IICDATA2IIC)
+#define get_byte(r)              ioread8(r+IICDATA2CPU)
+#define is_ready(r)              (ioread8(r+IICSR)&READY)
+#define is_nack(r)               (ioread8(r+IICSR)&RCV_NACK)
+#define get_irq_status(r)        ioread8(r+IICISR)
+#define get_reversion(r)         ioread8(r+IICREV)
+#define clear_irq_status(r)      iowrite8(IRQ_STS_MASK, r+IICISR)
+#define set_fifo_byte(r, d)      iowrite8(d, r+IICHTDR)
+#define get_fifo_byte(r)         ioread8(r+IICHRDR)
+#define set_fifo_wr_len(r, d)    iowrite8(d, r+IICHTLR)
+#define set_fifo_rd_len(r, d)    iowrite8(d, r+IICHRLR)
+#define get_fifo_wr_cnt(r)       ioread8(r+IICHWCNTR)
+#define get_fifo_rd_cnt(r)       ioread8(r+IICHRCNTR)
+#define master_regs_reset(r)     iowrite8(MST_RST|0x41, r+IICCR_L)
+#define set_dynamic_clock(r, d)  iowrite8(d, r+IICMCR)
+#define get_dynamic_clock(r)     (ioread8(r+IICMCR)&DYCLK_EN)
+#define stop_write_byte(r)       iowrite8(TX_END|0x41, r+IICCR_L)
+#define get_fstp_value(r)        ioread8(r+IICTR_FSTP)
+#define enable_fifo_mode(r)      iowrite8(FIFO_EN, r+IICCR_H)
+#define enable_byte_mode(r)      iowrite8(0, r+IICCR_H)
+
+static inline void zxi2c_prepare_next_read(void __iomem *regs, u16 left)
+{
+	u8 tmp = ioread8(regs + IICCR_L);
+
+	if (left > 1)
+		tmp &= ~RX_ACK;
+	else
+		tmp |= RX_ACK;
+
+	iowrite8(tmp, regs + IICCR_L);
+}
+
+static inline void zxi2c_enable_irq(void __iomem *regs, u8 type, int enable)
+{
+	if (enable)
+		iowrite8(IRQ_EN_ADDRNACK | type, regs + IICIMR);
+	else
+		iowrite8(0, regs + IICIMR);
+}
+
+static inline void zxi2c_continue(struct zxi2c *i2c)
+{
+	u8 tmp;
+
+	i2c->event = 0;
+	tmp = ioread8(i2c->regs + IICCR_L);
+	iowrite8(tmp |= CPU_RDY, i2c->regs + IICCR_L);
+}
+
+static void zxi2c_reset_fifo(struct zxi2c *i2c)
+{
+	u8 tmp;
+	u8 count;
+	void __iomem *regs = i2c->regs;
+
+	tmp = ioread8(regs + IICHCR);
+	iowrite8(tmp | FIFO_RST, regs + IICHCR);
+	for (count = 0; count < 50; count++)
+		if (!(ioread8(regs + IICHCR) & FIFO_RST))
+			break;
+	if (count >= 50)
+		dev_warn(i2c->dev, "%s failed\n", __func__);
+}
+
+static void zxi2c_set_wr(void __iomem *regs, bool is_read)
+{
+	u8 tmp;
+
+	tmp = ioread8(regs + IICTCR);
+	if (is_read)
+		tmp |= MASTER_RECV;
+	else
+		tmp &= ~MASTER_RECV;
+	iowrite8(tmp, regs + IICTCR);
+}
+
+static void zxi2c_start(struct zxi2c *i2c)
+{
+	i2c->event = 0;
+	iowrite8(i2c->addr & 0x7f, i2c->regs + IICSLVADDR);
+}
+
+static const u32 speed_params_table[][5] = {
+	/* speed, IICTCR, IICTR_FSTP, IICCS, IICTR_SCLTP */
+	{I2C_MAX_STANDARD_MODE_FREQ, 0, 0xF3, CLKSEL_50M, 0xff},
+	{I2C_MAX_FAST_MODE_FREQ, FAST_SEL, 0x38, CLKSEL_50M, 0xff},
+	{I2C_MAX_FAST_MODE_PLUS_FREQ, FAST_SEL, 19, CLKSEL_50M, 0xff},
+	{I2C_MAX_HIGH_SPEED_MODE_FREQ, HS_SEL, 0x37, CLKSEL_50M, 0xff},
+	/* never reached, keep for debug. freq src is 27M mode */
+	{I2C_MAX_STANDARD_MODE_FREQ, 0, 0x83, 0, 0x80 },
+	{I2C_MAX_FAST_MODE_FREQ, FAST_SEL, 0x1e, 0, 0x80 },
+	{I2C_MAX_FAST_MODE_PLUS_FREQ, FAST_SEL, 10, 0, 0x80 }
+};
+
+static void zxi2c_set_bus_speed(struct zxi2c *i2c)
+{
+	int i, count;
+	const u32 *params = NULL;
+
+	count = ARRAY_SIZE(speed_params_table);
+	for (i = 0; i < count; i++) {
+		if (speed_params_table[i][0] == i2c->speed) {
+			params = speed_params_table[i];
+			break;
+		}
+	}
+
+	iowrite8(params[1], i2c->regs + IICTCR);
+	if (abs(i2c->fstp - params[2]) > 0x10) {
+		/* if BIOS setting value far from golden value,
+		 * use golden value and warn user
+		 */
+		dev_warn_once(i2c->dev, "speed:%d, fstp:0x%x, golden:0x%x\n",
+				i2c->speed, i2c->fstp, params[2]);
+		iowrite8(params[2], i2c->regs + IICTR_FSTP);
+	} else
+		iowrite8(i2c->fstp, i2c->regs + IICTR_FSTP);
+	iowrite8(params[3], i2c->regs + IICCS);
+	iowrite8(params[4], i2c->regs + IICTR_SCLTP);
+
+	/* for Hs-mode, use 0000 1000 as master code */
+	if (i2c->speed == I2C_MAX_HIGH_SPEED_MODE_FREQ)
+		iowrite8(0x08, i2c->regs + IIC_MST_CODE);
+}
+
+static void zxi2c_get_bus_speed(struct zxi2c *i2c)
+{
+	int i, count;
+	u32 acpi_speed = i2c_acpi_find_bus_speed(i2c->dev);
+
+	count = ARRAY_SIZE(speed_params_table);
+	for (i = 0; i < count; i++)
+		if (acpi_speed == speed_params_table[i][0])
+			break;
+
+	/* if not found, use 400k as default */
+	i2c->speed = i < count ? acpi_speed : I2C_MAX_FAST_MODE_FREQ;
+
+	dev_info(i2c->dev, "speed mode is %s",
+			i2c_freq_mode_string(i2c->speed));
+}
+
+static void zxi2c_module_reset(struct zxi2c *i2c)
+{
+	u8 tmp;
+	u8 bitmask = i2c->reset_bitmask;
+
+	pci_read_config_byte(i2c->pci, 0x4F, &tmp);
+	pci_write_config_byte(i2c->pci, 0x4F, tmp & ~bitmask);
+	usleep_range(3000, 5000);
+	pci_write_config_byte(i2c->pci, 0x4F, tmp | bitmask);
+	usleep_range(3000, 5000);
+
+	set_dynamic_clock(i2c->regs, i2c->dynamic);
+}
+
+static irqreturn_t zxi2c_irq_handle(int irq, void *dev_id)
+{
+	struct zxi2c *i2c = (struct zxi2c *)dev_id;
+	void __iomem *regs = i2c->regs;
+	u8 status = get_irq_status(regs);
+
+	if ((status & IRQ_STS_MASK) == 0)
+		return IRQ_NONE;
+
+	if (status & IRQ_SCL_TIMEOUT)
+		dev_warn(i2c->dev, "timeout(HW), ID: 0x%X\n", i2c->addr);
+
+	if (status & IRQ_STS_ADDRNACK) {
+		dev_dbg(i2c->dev, "addr NACK, ID: 0x%X\n", i2c->addr);
+	} else if (status & IRQ_STS_BYTEEND) {
+		i2c->byte_left--;
+		if (!i2c->is_read) {
+			if (is_nack(regs)) {
+				status = IRQ_STS_BYTENACK;
+				i2c->byte_left++;
+				dev_err(i2c->dev, "data NACK, ID: 0x%X\n",
+					i2c->addr);
+			} else if (i2c->byte_left == 0 && i2c->is_last_msg) {
+				stop_write_byte(regs);
+			}
+		}
+	}
+
+	i2c->event = status;
+	clear_irq_status(regs);
+	wake_up(&i2c->waitq);
+
+	return IRQ_HANDLED;
+}
+
+static int zxi2c_wait_event(struct zxi2c *i2c, u8 event)
+{
+	int timeout;
+
+	timeout = wait_event_interruptible_timeout(i2c->waitq,
+			i2c->event != 0,
+			msecs_to_jiffies(ZXI2C_TIMEOUT));
+
+	if (i2c->event & event)
+		return 0;
+
+	if (timeout == 0) {
+		dev_err(i2c->dev, "timeout(SW), ID: 0x%X\n", i2c->addr);
+		/* Clock streching timeout, do recovery */
+		if (!is_nack(i2c->regs))
+			dev_err(i2c->dev, "device hang? reset, ID: 0x%X\n",
+				i2c->addr);
+
+		master_regs_reset(i2c->regs);
+		zxi2c_set_bus_speed(i2c);
+	}
+	return -ENODEV;
+}
+
+static int zxi2c_byte_xfer(struct zxi2c *i2c, struct i2c_msg *msgs, int num)
+{
+	u16 i, finished;
+	int error;
+	u8 index, ret = 0;
+	struct i2c_msg *msg;
+	void __iomem *regs = i2c->regs;
+
+	clear_irq_status(regs);
+	enable_byte_mode(regs);
+	zxi2c_enable_irq(regs, IRQ_EN_BYTEEND, true);
+
+	for (index = 0; index < num; index++) {
+		msg = msgs + index;
+		i2c->addr = msg->addr;
+		i2c->is_read = !!(msg->flags & I2C_M_RD);
+		i2c->byte_left = i2c->len = msg->len;
+
+		zxi2c_set_wr(regs, i2c->is_read);
+		if (i2c->is_read) {
+			zxi2c_prepare_next_read(regs, i2c->byte_left);
+			zxi2c_start(i2c);
+			/* create restart for non-first msg */
+			if (index)
+				zxi2c_continue(i2c);
+
+			for (i = 1; i <= msg->len; i++) {
+				error = zxi2c_wait_event(i2c, IRQ_STS_BYTEEND);
+				if (error)
+					break;
+
+				msg->buf[i - 1] = get_byte(regs);
+				if (i2c->byte_left == 0)
+					break;
+
+				zxi2c_prepare_next_read(regs, i2c->byte_left);
+				zxi2c_continue(i2c);
+			}
+		} else {
+			set_byte(regs, msg->buf[0]);
+			/* mark whether this is the last msg */
+			i2c->is_last_msg = index == !!(num - 1);
+			zxi2c_start(i2c);
+			/* create restart for non-first msg */
+			if (index)
+				zxi2c_continue(i2c);
+
+			for (i = 1; i <= msg->len; i++) {
+				error = zxi2c_wait_event(i2c, IRQ_STS_BYTEEND);
+				if (error)
+					break;
+
+				if (i2c->byte_left == 0)
+					break;
+				set_byte(regs, msg->buf[i]);
+				zxi2c_continue(i2c);
+			}
+		}
+
+		if (error) {
+			/* check if NACK during transmitting */
+			finished = msg->len - i2c->byte_left;
+			if (finished)
+				dev_err(i2c->dev,
+					"%s: %s finished %d bytes: %*ph\n",
+					__func__,
+					i2c->is_read ? "read" : "write",
+					finished, finished, msg->buf);
+			return error;
+		}
+		ret++;
+	}
+
+	zxi2c_enable_irq(regs, IRQ_EN_BYTEEND, false);
+	return ret;
+}
+
+static int zxi2c_fifo_xfer(struct zxi2c *i2c, struct i2c_msg *msgs)
+{
+	void __iomem *regs = i2c->regs;
+	struct i2c_msg *msg = msgs;
+	int i;
+	u8 finished;
+
+	i2c->addr = msg->addr;
+	i2c->is_read = !!(msg->flags & I2C_M_RD);
+	i2c->len = msg->len;
+
+	zxi2c_reset_fifo(i2c);
+	enable_fifo_mode(regs);
+
+	clear_irq_status(regs);
+	zxi2c_enable_irq(regs, IRQ_EN_FIFOEND, true);
+
+	zxi2c_set_wr(regs, i2c->is_read);
+	if (i2c->is_read) {
+		set_fifo_rd_len(regs, msg->len - 1);
+	} else {
+		set_fifo_wr_len(regs, msg->len - 1);
+		for (i = 0; i < msg->len; i++)
+			set_fifo_byte(regs, msg->buf[i]);
+	}
+
+	zxi2c_start(i2c);
+	if (zxi2c_wait_event(i2c, IRQ_STS_FIFOEND))
+		return -ENODEV;
+
+	if (i2c->is_read) {
+		finished = get_fifo_rd_cnt(regs);
+		for (i = 0; i < finished; i++)
+			msg->buf[i] = get_fifo_byte(regs);
+	} else {
+		finished = get_fifo_wr_cnt(regs);
+	}
+
+	/* check if NACK during transmitting */
+	if (finished != msg->len) {
+		if (finished)
+			dev_err(i2c->dev,
+				"%s: %s only finished %d/%d bytes: %*ph\n",
+				__func__, i2c->is_read ? "read" : "write",
+				finished, msg->len, finished, msg->buf);
+		return -EAGAIN;
+	}
+
+	zxi2c_enable_irq(regs, IRQ_EN_FIFOEND, false);
+	return 1;
+}
+
+static int zxi2c_master_xfer(struct i2c_adapter *adap,
+			     struct i2c_msg *msgs, int num)
+{
+	int error;
+	struct zxi2c *i2c = (struct zxi2c *)i2c_get_adapdata(adap);
+
+	if (!is_ready(i2c->regs)) {
+		zxi2c_module_reset(i2c);
+		zxi2c_set_bus_speed(i2c);
+		dev_err(i2c->dev, "controller busy, reset try again\n");
+
+		return -EAGAIN;
+	}
+
+	if (num == 1 && msgs->len <= FIFO_SIZE && msgs->len >= 3)
+		error = zxi2c_fifo_xfer(i2c, msgs);
+	else
+		error = zxi2c_byte_xfer(i2c, msgs, num);
+
+	return error;
+}
+
+static u32 zxi2c_func(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm zxi2c_algorithm = {
+	.master_xfer = zxi2c_master_xfer,
+	.functionality = zxi2c_func,
+};
+
+static const struct i2c_adapter_quirks zxi2c_quirks = {
+	.flags = I2C_AQ_NO_ZERO_LEN | I2C_AQ_COMB_WRITE_THEN_READ,
+};
+
+static int zxi2c_get_acpi_index(struct zxi2c *i2c)
+{
+	union acpi_object *obj;
+	acpi_handle handle;
+	char *unique_id;
+
+	handle = ACPI_HANDLE(i2c->dev);
+	if (!handle)
+		return -ENODEV;
+
+	obj = acpi_evaluate_dsm_typed(handle, &NULL_UUID_LE, 0, 1, NULL,
+				      ACPI_TYPE_INTEGER);
+	if (!obj) {
+		/* Used for compatibility with older BIOS */
+		unique_id = acpi_device_uid(ACPI_COMPANION(i2c->dev));
+		if (!unique_id || kstrtou8(unique_id, 10, &i2c->id)) {
+			dev_err(i2c->dev, "_DSM and _UID method failed\n");
+			return -ENODEV;
+		}
+	} else {
+		i2c->id = obj->integer.value;
+		ACPI_FREE(obj);
+	}
+
+	if (i2c->id == 1)
+		i2c->reset_bitmask = BIT(5);
+	else if (i2c->id == 2)
+		i2c->reset_bitmask = BIT(4);
+	else {
+		dev_err(i2c->dev, "error id: %d\n", i2c->id);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int zxi2c_parse_resources(struct zxi2c *i2c)
+{
+	struct platform_device *pdev = to_platform_device(i2c->dev);
+
+	i2c->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(i2c->regs)) {
+		dev_err(&pdev->dev, "get base addr failed\n");
+		return -ENOMEM;
+	}
+
+	i2c->irq = platform_get_irq(pdev, 0);
+	if (i2c->irq < 0) {
+		dev_err(&pdev->dev, "get irq failed\n");
+		return i2c->irq;
+	}
+
+	if (zxi2c_get_acpi_index(i2c))
+		return -ENODEV;
+
+	zxi2c_get_bus_speed(i2c);
+
+	i2c->hrv = get_reversion(i2c->regs);
+	if (!i2c->hrv)
+		i2c->hrv = 1;
+
+	i2c->dynamic = get_dynamic_clock(i2c->regs);
+	set_dynamic_clock(i2c->regs, i2c->dynamic);
+
+	i2c->fstp = get_fstp_value(i2c->regs);
+	zxi2c_set_bus_speed(i2c);
+
+	return 0;
+}
+
+static int zxi2c_probe(struct platform_device *pdev)
+{
+	int error;
+	struct zxi2c *i2c;
+	struct pci_dev *pci;
+	struct device *dev;
+
+	/* make sure this is zhaoxin platform */
+	dev = pdev->dev.parent;
+	if (dev && dev_is_pci(dev)) {
+		pci = to_pci_dev(dev);
+		if (pci->vendor != 0x1d17 || pci->device != 0x1001)
+			return -ENODEV;
+	} else
+		return -ENODEV;
+
+	i2c = devm_kzalloc(&pdev->dev, sizeof(*i2c), GFP_KERNEL);
+	if (IS_ERR(i2c)) {
+		dev_err(&pdev->dev, "devm_kzalloc failed\n");
+		return -ENOMEM;
+	}
+
+	i2c->pci = pci;
+	i2c->dev = &pdev->dev;
+	error = zxi2c_parse_resources(i2c);
+	if (error)
+		return error;
+
+	platform_set_drvdata(pdev, (void *)i2c);
+
+	if (devm_request_irq(&pdev->dev, i2c->irq, zxi2c_irq_handle,
+			     IRQF_SHARED, pdev->name, i2c)) {
+		dev_err(i2c->dev, "IRQ%d request failed\n", i2c->irq);
+		return -ENODEV;
+	}
+
+	init_waitqueue_head(&i2c->waitq);
+
+	i2c->adap.owner = THIS_MODULE;
+	i2c->adap.algo = &zxi2c_algorithm;
+	i2c->adap.retries = 2;
+	i2c->adap.quirks = &zxi2c_quirks;
+	i2c->adap.dev.parent = &pdev->dev;
+	ACPI_COMPANION_SET(&i2c->adap.dev, ACPI_COMPANION(&pdev->dev));
+	snprintf(i2c->adap.name, sizeof(i2c->adap.name),
+		 "%s.%d", ZX_I2C_NAME, i2c->id);
+	i2c_set_adapdata(&i2c->adap, i2c);
+
+	error = i2c_add_adapter(&i2c->adap);
+	if (unlikely(error)) {
+		dev_err(i2c->dev, "failed to register, %d\n", error);
+		return error;
+	}
+
+	dev_info(i2c->dev, "adapter /dev/i2c-%d registered. hw version %x\n",
+		 i2c->adap.nr, i2c->hrv);
+
+	return 0;
+}
+
+static int zxi2c_remove(struct platform_device *pdev)
+{
+	struct zxi2c *i2c = platform_get_drvdata(pdev);
+
+	i2c_lock_bus(&i2c->adap, I2C_LOCK_ROOT_ADAPTER);
+
+	zxi2c_module_reset(i2c);
+	master_regs_reset(i2c->regs);
+
+	devm_free_irq(&pdev->dev, i2c->irq, i2c);
+
+	i2c_unlock_bus(&i2c->adap, I2C_LOCK_ROOT_ADAPTER);
+
+	i2c_del_adapter(&i2c->adap);
+
+	platform_set_drvdata(pdev, NULL);
+	devm_kfree(&pdev->dev, i2c);
+
+	dev_info(&pdev->dev, "adapter unregistered\n");
+
+	return 0;
+}
+
+static int zxi2c_suspend(struct device *dev)
+{
+	return 0;
+}
+
+static int zxi2c_resume(struct device *dev)
+{
+	struct zxi2c *i2c = dev_get_drvdata(dev);
+
+	zxi2c_module_reset(i2c);
+	zxi2c_set_bus_speed(i2c);
+
+	return 0;
+}
+
+const struct dev_pm_ops zxi2c_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(zxi2c_suspend, zxi2c_resume)
+};
+
+static const struct acpi_device_id zxi2c_acpi_match[] = {
+	{"IIC1D17", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(acpi, zxi2c_acpi_match);
+
+static struct platform_driver zxi2c_driver = {
+	.probe = zxi2c_probe,
+	.remove = zxi2c_remove,
+	.driver = {
+		.name = ZX_I2C_NAME,
+		.owner = THIS_MODULE,
+		.acpi_match_table = ACPI_PTR(zxi2c_acpi_match),
+		.pm = &zxi2c_pm,
+	},
+};
+module_platform_driver(zxi2c_driver);
+
+MODULE_AUTHOR("HansHu@zhaoxin.com");
+MODULE_DESCRIPTION("Shanghai Zhaoxin IIC driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");