[PATCH v2 4/8] Add Advantech EIO I2C driver

Ramiro Oliveira <[email protected]>
Newsgroups dev.linux.lists.mfd,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-fbdev,org.kernel.vger.linux-gpio,org.kernel.vger.linux-hwmon,org.kernel.vger.linux-i2c,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm,org.kernel.vger.linux-watchdog
Message-ID <[email protected]>
This commit adds the driver to control the Advantech EIO I2C block, this
block is included in the Advantech EIO MFD.

Signed-off-by: Ramiro Oliveira <[email protected]>
---
 MAINTAINERS                  |    1 +
 drivers/i2c/busses/Kconfig   |    6 +
 drivers/i2c/busses/Makefile  |    1 +
 drivers/i2c/busses/i2c-eio.c | 1135 ++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1143 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 6d727d5182e4..e228d12a2326 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -614,6 +614,7 @@ M:	Ramiro Oliveira <[email protected]>
 S:	Maintained
 F:	drivers/gpio/gpio-eio.c
 F:	drivers/hwmon/eio-hwmon.c
+F:	drivers/i2c/busses/i2c-eio.c
 F:	drivers/mfd/eio_core.c
 F:	include/linux/mfd/eio.h
 
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index d7b89508311f..ba982bb00cf9 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -649,6 +649,12 @@ config I2C_DIGICOLOR
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-digicolor.
 
+config I2C_EIO
+	tristate "Advantech EIO I2C bus"
+	depends on MFD_EIO
+	help
+	  Say Y or M to build support for Advantech EIO I2C block.
+
 config I2C_EG20T
 	tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) I2C"
 	depends on PCI && (X86_32 || MIPS || COMPILE_TEST)
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 3755c54b3d82..2d8d4f37838c 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -62,6 +62,7 @@ obj-$(CONFIG_I2C_DESIGNWARE_AMDISP) += i2c-designware-amdisp.o
 obj-$(CONFIG_I2C_DESIGNWARE_PCI)			+= i2c-designware-pci.o
 i2c-designware-pci-y					:= i2c-designware-pcidrv.o
 obj-$(CONFIG_I2C_DIGICOLOR)	+= i2c-digicolor.o
+obj-$(CONFIG_I2C_EIO)		+= i2c-eio.o
 obj-$(CONFIG_I2C_EG20T)		+= i2c-eg20t.o
 obj-$(CONFIG_I2C_EMEV2)		+= i2c-emev2.o
 obj-$(CONFIG_I2C_EXYNOS5)	+= i2c-exynos5.o
diff --git a/drivers/i2c/busses/i2c-eio.c b/drivers/i2c/busses/i2c-eio.c
new file mode 100644
index 000000000000..56a89ca18f32
--- /dev/null
+++ b/drivers/i2c/busses/i2c-eio.c
@@ -0,0 +1,1135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * I2C and SMBus driver of EIO embedded driver
+ *
+ * Copyright (C) 2025 Advantech Co., Ltd.
+ */
+
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/eio.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+
+#define SUPPORTED_COMMON (I2C_FUNC_I2C | \
+			  I2C_FUNC_SMBUS_QUICK | \
+			  I2C_FUNC_SMBUS_BYTE | \
+			  I2C_FUNC_SMBUS_BYTE_DATA | \
+			  I2C_FUNC_SMBUS_WORD_DATA | \
+			  I2C_FUNC_SMBUS_I2C_BLOCK)
+#define SUPPORTED_SMB	(SUPPORTED_COMMON | I2C_FUNC_SMBUS_BLOCK_DATA)
+#define SUPPORTED_I2C	(SUPPORTED_COMMON | I2C_FUNC_10BIT_ADDR)
+
+#define MAX_I2C_SMB		4
+
+#define REG_PNP_INDEX		0x299
+#define REG_PNP_DATA		0x29A
+#define REG_SUB_PNP_INDEX	0x499
+#define REG_SUB_PNP_DATA	0x49A
+#define REG_EXT_MODE_ENTER	0x87
+#define REG_EXT_MODE_EXIT	0xAA
+#define REG_LDN			0x07
+
+#define LDN_I2C0		0x20
+#define LDN_I2C1		0x21
+#define LDN_SMBUS0		0x22
+#define LDN_SMBUS1		0x23
+
+#define REG_BASE_HI		0x60
+#define REG_BASE_LO		0x61
+
+#define I2C_REG_CTRL		0x00
+#define I2C_CTRL_STOP		BIT(1)
+
+#define I2C_REG_STAT		0x01
+#define I2C_STAT_RXREADY	BIT(6)
+#define I2C_STAT_TXDONE		BIT(5)
+#define I2C_STAT_NAK_ERR	BIT(4)
+#define I2C_STAT_ARL_ERR	BIT(3)
+#define I2C_STAT_SLV_STP	BIT(2)
+#define I2C_STAT_BUSY		BIT(1)
+#define I2C_STAT_MST_SLV	BIT(0)
+
+#define I2C_REG_MYADDR		0x02
+#define I2C_REG_ADDR		0x03
+#define I2C_REG_DATA		0x04
+#define I2C_REG_PRESCALE1	0x05
+#define I2C_REG_PRESCALE2	0x06
+
+#define I2C_REG_ECTRL		0x07
+#define I2C_ECTRL_RST		BIT(7)
+
+#define I2C_REG_SEM		0x08
+#define I2C_SEM_INUSE		BIT(1)
+
+#define SMB_REG_HC2		0x0C
+
+#define SMB_REG_HS		0x00
+#define SMB_HS_BUSY		BIT(0)
+#define SMB_HS_FINISH		BIT(1)
+#define SMB_HS_ARL_ERR		BIT(3)
+#define SMB_HS_FAILED		BIT(4)
+#define SMB_HS_RX_READY		BIT(5)
+#define SMB_HS_INUSE		BIT(6)
+#define SMB_HS_TX_DONE		BIT(7)
+
+#define SMB_REG_HS2		0x01
+#define SMB_HS2_HNOTIFY		BIT(0)
+#define SMB_HS2_PEC_ERR		BIT(1)
+#define SMB_HS2_NACK_ERR	BIT(2)
+#define SMB_HS2_ALERT_STS	BIT(3)
+#define SMB_HS2_TO_ERR		BIT(4)
+#define SMB_HS2_SSTOP_STS	BIT(5)
+#define SMB_HS2_STX_REQ		BIT(6)
+#define SMB_HS2_SMODE		BIT(7)
+
+#define SMB_REG_HC		0x02
+#define SMB_HC_I2C_NACKEN	BIT(0)
+#define SMB_HC_KILL		BIT(1)
+#define SMB_HC_CMD_SHIFT	2
+#define SMB_HC_LAST_BYTE	BIT(5)
+#define SMB_HC_START		BIT(6)
+#define SMB_HC_PEC_EN		BIT(7)
+
+#define SMB_REG_HCMD		0x03
+#define SMB_REG_HADDR		0x04
+#define SMB_REG_HD0		0x05
+#define SMB_REG_HD1		0x06
+#define SMB_REG_HBLOCK		0x07
+#define SMB_REG_HPEC		0x08
+#define SMB_REG_SADDR		0x09
+#define SMB_REG_SD0		0x0A
+#define SMB_REG_SD1		0x0B
+
+#define SMB_REG_HC2		0x0C
+#define SMB_HC2_HNOTIFY_DIS	BIT(0)
+#define SMB_HC2_I2C_EN		BIT(1)
+#define SMB_HC2_AAPEC		BIT(2)
+#define SMB_HC2_E32B		BIT(3)
+#define SMB_HC2_SRESET		BIT(7)
+
+#define SMB_REG_HPIN		0x0D
+#define SMB_REG_HC3		0x0E
+#define SMB_REG_HC4		0x0F
+#define SMB_REG_NOTIFY_D0	0x11
+#define SMB_REG_NOTIFY_D1	0x12
+#define SMB_REG_HPRESCALE1	0x13
+#define SMB_REG_HPRESCALE2	0x14
+#define SMB_REG_HEXTRA		0x15
+
+#define I2C_TIMEOUT		(10 * USEC_PER_MSEC)
+#define USE_DEFAULT		-1
+
+#define CHIP_CLK		50000
+#define I2C_SCLH_HIGH		2500
+#define I2C_SCLH_LOW		1000
+#define I2C_SCL_FAST_MODE	0x80
+#define I2C_THRESHOLD_SPEED	100
+#define I2C_THRESHOLD_SCLH	30
+#define I2C_FREQ_MAX		400
+#define I2C_FREQ_MIN		8
+
+enum eio_chan_id {
+	EIO_I2C0 = 0,
+	EIO_I2C1,
+	EIO_SMB0,
+	EIO_SMB1,
+};
+
+struct eio_i2c_dev {
+	struct device *dev;
+	struct device *mfd;
+	struct regmap *regmap;
+	struct mutex pnp_mutex; /* Mutex for PNP acces */
+	struct eio_i2c_chan *chan[MAX_I2C_SMB];
+};
+
+struct eio_i2c_chan {
+	u16 base;
+	enum eio_chan_id id;
+	struct eio_i2c_dev *parent;
+	struct i2c_adapter adap;
+	struct mutex lock; /* Mutex for regmap writes */
+	int freq_override;                  /* kHz or USE_DEFAULT */
+};
+
+static int i2c0_freq = USE_DEFAULT;
+module_param(i2c0_freq, int, 0444);
+MODULE_PARM_DESC(i2c0_freq, "Set EIO's I2C0 freq.\n");
+
+static int i2c1_freq = USE_DEFAULT;
+module_param(i2c1_freq, int, 0444);
+MODULE_PARM_DESC(i2c1_freq, "Set EIO's I2C1 freq.\n");
+
+static int smb0_freq = USE_DEFAULT;
+module_param(smb0_freq, int, 0444);
+MODULE_PARM_DESC(smb0_freq, "Set EIO's SMB0 freq.\n");
+
+static int smb1_freq = USE_DEFAULT;
+module_param(smb1_freq, int, 0444);
+MODULE_PARM_DESC(smb1_freq, "Set EIO's SMB1 freq.\n");
+
+static inline u16 eio_enc_7bit_addr(u16 x)
+{
+	return ((x & 0x07F) << 1);
+}
+
+static inline u16 eio_enc_10bit_addr(u16 x)
+{
+	return ((x & 0xFF) | ((x & 0x0300) << 1) | 0xF000);
+}
+
+static inline bool is_i2c(const struct eio_i2c_chan *i2c_chan)
+{
+	return i2c_chan->id == EIO_I2C0 || i2c_chan->id == EIO_I2C1;
+}
+
+static inline struct device *eio_dev(const struct eio_i2c_chan *i2c_chan)
+{
+	return i2c_chan->parent->dev;
+}
+
+static inline struct regmap *eio_map(const struct eio_i2c_chan *i2c_chan)
+{
+	return i2c_chan->parent->regmap;
+}
+
+static inline int eio_reg_write(struct eio_i2c_chan *i2c_chan,
+				unsigned int reg_off, unsigned int val)
+{
+	return regmap_write(eio_map(i2c_chan), i2c_chan->base + reg_off, val);
+}
+
+static inline int eio_reg_read(const struct eio_i2c_chan *chan,
+			       unsigned int reg, unsigned int *val)
+{
+	int ret;
+
+	ret = regmap_read(chan->parent->regmap, chan->base + reg, val);
+	return ret;
+}
+
+static inline int eio_reg_set_bits(const struct eio_i2c_chan *chan,
+				   unsigned int reg, unsigned int mask)
+{
+	return regmap_update_bits(chan->parent->regmap, reg, mask, mask);
+}
+
+static inline int eio_reg_clear_bits(const struct eio_i2c_chan *chan,
+				     unsigned int reg, unsigned int mask)
+{
+	return regmap_update_bits(chan->parent->regmap, reg, mask, 0);
+}
+
+static inline int eio_reg_or(struct eio_i2c_chan *chan,
+			     unsigned int reg, unsigned int mask)
+{
+	return eio_reg_set_bits(chan, reg, mask);
+}
+
+static inline int eio_reg_and(struct eio_i2c_chan *chan,
+			      unsigned int reg, unsigned int mask)
+{
+	return eio_reg_clear_bits(chan, reg, ~mask);
+}
+
+static inline unsigned int eio_chan_reg(const struct eio_i2c_chan *i2c_chan,
+					unsigned int i2c_reg,
+					unsigned int smb_reg)
+{
+	return is_i2c(i2c_chan) ? i2c_reg : smb_reg;
+}
+
+static inline int eio_trigger_read(struct eio_i2c_chan *i2c_chan, u32 *data)
+{
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0);
+
+	return eio_reg_read(i2c_chan, reg, data);
+}
+
+static int wait_busy(struct eio_i2c_chan *i2c_chan)
+{
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_BUSY, SMB_HS_BUSY);
+	unsigned int val;
+	int cnt = 0;
+
+	do {
+		fsleep(cnt++);
+
+		if (ktime_after(ktime_get(), time_end)) {
+			dev_err(eio_dev(i2c_chan), "Wait I2C bus busy timeout\n");
+			return -ETIME;
+		}
+
+		if (eio_reg_read(i2c_chan, reg, &val))
+			return -EIO;
+
+	} while (val & target);
+
+	return 0;
+}
+
+static void reset_bus(struct eio_i2c_chan *i2c_chan)
+{
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRESET);
+	unsigned int val = 0;
+	unsigned int cnt = 0;
+
+	dev_dbg(eio_dev(i2c_chan), "i2c[%d] bus reset\n", i2c_chan->id);
+
+	if (is_i2c(i2c_chan))
+		eio_reg_write(i2c_chan, I2C_REG_ECTRL, I2C_ECTRL_RST);
+	else
+		eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_SRESET);
+
+	do {
+		fsleep(cnt++);
+
+		if (ktime_after(ktime_get(), time_end)) {
+			dev_err(eio_dev(i2c_chan), "bus reset timeout\n");
+			return;
+		}
+
+		if (eio_reg_read(i2c_chan, reg, &val))
+			return;
+
+	} while (val & target);
+
+	wait_busy(i2c_chan);
+}
+
+static int wait_bus_free(struct eio_i2c_chan *i2c_chan)
+{
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int val;
+	int cnt = 1;
+
+	/* Wait if channel is resetting */
+	do {
+		fsleep(cnt);
+
+		if (ktime_after(ktime_get(), time_end)) {
+			dev_err(eio_dev(i2c_chan), "Wait bus reset timeout\n");
+			return -ETIME;
+		}
+
+		if (eio_reg_read(i2c_chan,
+				 eio_chan_reg(i2c_chan, I2C_REG_ECTRL, SMB_REG_HC2),
+				 &val))
+			return -EIO;
+
+	} while (val & eio_chan_reg(i2c_chan, I2C_ECTRL_RST, SMB_HC2_SRESET));
+
+	/* Wait INUSE */
+	time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+
+	do {
+		fsleep(cnt);
+
+		if (ktime_after(ktime_get(), time_end)) {
+			dev_err(eio_dev(i2c_chan), "Timeout: I2C bus in use\n");
+			return -ETIME;
+		}
+
+		if (eio_reg_read(i2c_chan,
+				 eio_chan_reg(i2c_chan, I2C_REG_SEM, SMB_REG_HS),
+				 &val))
+			return -EIO;
+
+	} while (val & eio_chan_reg(i2c_chan, I2C_SEM_INUSE, SMB_HS_INUSE));
+
+	return 0;
+}
+
+static int let_stop(struct eio_i2c_chan *i2c_chan)
+{
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST_BYTE);
+
+	return eio_reg_or(i2c_chan, reg, target);
+}
+
+static int clr_inuse(struct eio_i2c_chan *i2c_chan)
+{
+	if (is_i2c(i2c_chan))
+		return eio_reg_write(i2c_chan, I2C_REG_SEM, I2C_SEM_INUSE);
+
+	return eio_reg_or(i2c_chan, SMB_REG_HS, SMB_HS_INUSE);
+}
+
+static int bus_stop(struct eio_i2c_chan *i2c_chan)
+{
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_CTRL, SMB_REG_HC);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_CTRL_STOP, SMB_HC_LAST_BYTE);
+	unsigned int val = 0;
+	int cnt = 0;
+
+	/* Set STOP bit */
+	eio_reg_or(i2c_chan, reg, target);
+
+	/* Wait until STOP bit clears */
+	do {
+		fsleep(cnt++);
+
+		if (ktime_after(ktime_get(), time_end))
+			return -ETIME;
+
+		if (eio_reg_read(i2c_chan, reg, &val))
+			return -EIO;
+
+	} while (val & target);
+
+	return 0;
+}
+
+static void switch_i2c_mode(struct eio_i2c_chan *i2c_chan, bool on)
+{
+	u32 tmp;
+
+	if (is_i2c(i2c_chan))
+		return;
+
+	if (eio_reg_read(i2c_chan, SMB_REG_HC2, &tmp))
+		return;
+
+	eio_reg_write(i2c_chan, SMB_REG_HC2,
+		      on ? (tmp | SMB_HC2_I2C_EN | SMB_HC2_SRESET)
+		      : (tmp & ~SMB_HC2_I2C_EN));
+}
+
+static void i2c_clear(struct eio_i2c_chan *i2c_chan)
+{
+	if (is_i2c(i2c_chan)) {
+		eio_reg_write(i2c_chan, I2C_REG_STAT, 0xFF);
+	} else {
+		eio_reg_or(i2c_chan, SMB_REG_HS,  0xA9);
+		eio_reg_or(i2c_chan, SMB_REG_HS2, 0x4C);
+	}
+}
+
+static int wait_write_done(struct eio_i2c_chan *i2c_chan, bool no_ack)
+{
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int val = 0;
+	int cnt = 0;
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_TXDONE, SMB_HS_TX_DONE);
+
+	do {
+		fsleep(cnt++);
+		if (ktime_after(ktime_get(), time_end)) {
+			if (is_i2c(i2c_chan)) {
+				eio_reg_or(i2c_chan, I2C_REG_STAT, 0);
+			} else {
+				eio_reg_or(i2c_chan, SMB_REG_HS,  0);
+				eio_reg_or(i2c_chan, SMB_REG_HS2, 0);
+			}
+			dev_err(eio_dev(i2c_chan), "wait write complete timeout %X %X\n",
+				val, target);
+			return -ETIME;
+		}
+		if (eio_reg_read(i2c_chan, reg, &val))
+			return -EIO;
+
+	} while ((val & target) == 0);
+
+	if (no_ack)
+		return 0;
+
+	if (is_i2c(i2c_chan)) {
+		eio_reg_or(i2c_chan, I2C_REG_STAT, 0);
+		return (val & I2C_STAT_NAK_ERR) ? -EIO : 0;
+	}
+
+	eio_reg_or(i2c_chan, SMB_REG_HS, 0);
+	if (eio_reg_read(i2c_chan, SMB_REG_HS2, &val))
+		return -EIO;
+	eio_reg_write(i2c_chan, SMB_REG_HS2, val);
+
+	return (val & SMB_HS2_NACK_ERR) ? -EIO : 0;
+}
+
+static int wait_ready(struct eio_i2c_chan *i2c_chan)
+{
+	int ret;
+
+	ret = wait_bus_free(i2c_chan);
+	if (ret)
+		return ret;
+
+	if (wait_busy(i2c_chan) == 0)
+		return 0;
+
+	reset_bus(i2c_chan);
+
+	return wait_busy(i2c_chan);
+}
+
+static int write_addr(struct eio_i2c_chan *i2c_chan, int addr, bool no_ack)
+{
+	eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_ADDR, SMB_REG_HADDR),
+		      addr);
+
+	return wait_write_done(i2c_chan, no_ack);
+}
+
+static int write_data(struct eio_i2c_chan *i2c_chan, int data, bool no_ack)
+{
+	eio_reg_write(i2c_chan, eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0),
+		      data);
+
+	return wait_write_done(i2c_chan, no_ack);
+}
+
+static int read_data(struct eio_i2c_chan *i2c_chan, u8 *data)
+{
+	unsigned int val = 0, tmp;
+	int cnt = 0;
+	ktime_t time_end = ktime_add_us(ktime_get(), I2C_TIMEOUT);
+	unsigned int stat = eio_chan_reg(i2c_chan, I2C_REG_STAT, SMB_REG_HS);
+	unsigned int target = eio_chan_reg(i2c_chan, I2C_STAT_RXREADY, SMB_HS_RX_READY);
+	unsigned int reg = eio_chan_reg(i2c_chan, I2C_REG_DATA, SMB_REG_HD0);
+
+	do {
+		fsleep(cnt++);
+
+		if (ktime_after(ktime_get(), time_end)) {
+			eio_reg_or(i2c_chan, stat, 0);
+			dev_err(eio_dev(i2c_chan), "read data timeout\n");
+			return -ETIME;
+		}
+
+		if (eio_reg_read(i2c_chan, stat, &val))
+			return -EIO;
+
+	} while ((val & target) != target);
+
+	/* clear status */
+	eio_reg_write(i2c_chan, stat, val);
+
+	/* Must read data after clearing status */
+	if (eio_reg_read(i2c_chan, reg, &tmp))
+		return -EIO;
+	*data = (u8)tmp;
+
+	return 0;
+}
+
+static int set_freq(struct eio_i2c_chan *i2c_chan, int freq)
+{
+	u8 pre1, pre2;
+	u16 speed;
+	unsigned int reg1 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_HPRESCALE1);
+	unsigned int reg2 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_HPRESCALE2);
+
+	dev_dbg(eio_dev(i2c_chan), "set freq: %dkHz\n", freq);
+	if (freq > I2C_FREQ_MAX || freq < I2C_FREQ_MIN) {
+		dev_err(eio_dev(i2c_chan), "Invalid i2c freq: %d\n", freq);
+		return -EINVAL;
+	}
+
+	speed = (freq < I2C_THRESHOLD_SCLH) ? I2C_SCLH_LOW : I2C_SCLH_HIGH;
+
+	pre1 = (u8)(CHIP_CLK / speed);
+	pre2 = (u8)((speed / freq) - 1);
+
+	if (freq > I2C_THRESHOLD_SCLH)
+		pre2 |= I2C_SCL_FAST_MODE;
+
+	eio_reg_write(i2c_chan, reg1, pre1);
+	eio_reg_write(i2c_chan, reg2, pre2);
+
+	return 0;
+}
+
+static int get_freq(struct eio_i2c_chan *i2c_chan, int *freq)
+{
+	int clk;
+	unsigned int pre1 = 0, pre2 = 0;
+	unsigned int reg1 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE1, SMB_REG_HPRESCALE1);
+	unsigned int reg2 = eio_chan_reg(i2c_chan, I2C_REG_PRESCALE2, SMB_REG_HPRESCALE2);
+
+	if (eio_reg_read(i2c_chan, reg1, &pre1))
+		return -EIO;
+	if (eio_reg_read(i2c_chan, reg2, &pre2))
+		return -EIO;
+
+	clk = (pre2 & I2C_SCL_FAST_MODE) ? I2C_SCLH_HIGH : I2C_SCLH_LOW;
+	pre2 &= ~I2C_SCL_FAST_MODE;
+
+	*freq = clk / ((int)pre2 + 1);
+
+	return 0;
+}
+
+static int smb_access(struct eio_i2c_chan *i2c_chan, u8 addr, bool is_read, u8 cmd,
+		      int size, union i2c_smbus_data *data)
+{
+	int i, tmp, ret = 0;
+	unsigned int st1, st2;
+	int len = 0;
+
+	mutex_lock(&i2c_chan->lock);
+
+	ret = wait_ready(i2c_chan);
+	if (ret)
+		goto exit;
+
+	/* Force SMBus mode */
+	switch_i2c_mode(i2c_chan, false);
+
+	addr = eio_enc_7bit_addr(addr) | (is_read ? 1 : 0);
+	eio_reg_write(i2c_chan, SMB_REG_HADDR, addr);
+	eio_reg_write(i2c_chan, SMB_REG_HCMD,  cmd);
+
+	dev_dbg(eio_dev(i2c_chan), "SMB[%d], addr:0x%02X, cmd:0x%02X size=%d\n",
+		i2c_chan->id, addr, cmd, size);
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n");
+		break;
+
+	case I2C_SMBUS_BYTE:
+		if (!is_read) {
+			dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE\n");
+			eio_reg_write(i2c_chan, SMB_REG_HCMD, cmd);
+		}
+		break;
+
+	case I2C_SMBUS_BYTE_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA\n");
+		if (!is_read) {
+			eio_reg_write(i2c_chan, SMB_REG_HD0, data->byte);
+			dev_dbg(eio_dev(i2c_chan), "write %X\n", data->byte);
+		}
+		break;
+
+	case I2C_SMBUS_WORD_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n");
+		if (!is_read) {
+			eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+			eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]);
+		}
+		break;
+
+	case I2C_SMBUS_PROC_CALL:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n");
+		eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+		eio_reg_write(i2c_chan, SMB_REG_HD1, data->block[1]);
+		break;
+
+	case I2C_SMBUS_BLOCK_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n");
+		if (is_read)
+			break;
+
+		/* Program command type */
+		eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp);
+		tmp &= ~(0x07 << SMB_HC_CMD_SHIFT);
+		tmp |=  (size << SMB_HC_CMD_SHIFT);
+		eio_reg_write(i2c_chan, SMB_REG_HC, tmp);
+
+		/* Force write for payload stage */
+		eio_reg_write(i2c_chan, SMB_REG_HADDR, addr & ~0x01);
+
+		/* Reset internal buffer index pointer */
+		eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B);
+		eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B);
+
+		/* Write length + data */
+		eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+		for (i = 1; i <= data->block[0]; i++)
+			eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]);
+		break;
+
+	case I2C_SMBUS_BLOCK_PROC_CALL:
+		/* Set command type field */
+		eio_reg_and(i2c_chan, SMB_REG_HC, (0x07 << SMB_HC_CMD_SHIFT));
+		eio_reg_write(i2c_chan, SMB_REG_HD0, data->block[0]);
+
+		/* Reset buffer index */
+		eio_reg_and(i2c_chan, SMB_REG_HC2, (int)~SMB_HC2_E32B);
+		eio_reg_or(i2c_chan, SMB_REG_HC2, SMB_HC2_E32B);
+
+		for (i = 1; i <= data->block[0]; i++)
+			eio_reg_write(i2c_chan, SMB_REG_HBLOCK, data->block[i]);
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	/* Launch transaction */
+	eio_reg_read(i2c_chan, SMB_REG_HC, (unsigned int *)&tmp);
+	tmp &= ~(0x07 << SMB_HC_CMD_SHIFT);
+	tmp |=  (size << SMB_HC_CMD_SHIFT) | SMB_HC_START;
+	tmp &= ~(SMB_HC_I2C_NACKEN | SMB_HC_KILL | SMB_HC_PEC_EN);
+	eio_reg_write(i2c_chan, SMB_REG_HC, tmp);
+
+	ret = wait_busy(i2c_chan);
+	if (ret)
+		goto exit;
+
+	eio_reg_read(i2c_chan, SMB_REG_HS,  &st1);
+	eio_reg_read(i2c_chan, SMB_REG_HS2, &st2);
+
+	if (st1 & SMB_HS_FAILED) {
+		dev_err(eio_dev(i2c_chan), "HS FAILED\n");
+		ret = -EIO;
+	} else if (st1 & SMB_HS_ARL_ERR) {
+		dev_err(eio_dev(i2c_chan), "ARL FAILED\n");
+		ret = -EIO;
+	} else if (st2 & SMB_HS2_TO_ERR) {
+		dev_err(eio_dev(i2c_chan), "timeout\n");
+		ret = -ETIME;
+	} else if (st2 & SMB_HS2_NACK_ERR) {
+		dev_err(eio_dev(i2c_chan), "NACK err\n");
+		ret = -EIO;
+	} else if (st2 & SMB_HS2_PEC_ERR) {
+		dev_err(eio_dev(i2c_chan), "PEC err\n");
+		ret = -EIO;
+	}
+	if (ret)
+		goto exit;
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK\n");
+		break;
+
+	case I2C_SMBUS_BYTE:
+	case I2C_SMBUS_BYTE_DATA:
+		if (is_read) {
+			unsigned int v;
+
+			dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE/I2C_SMBUS_BYTE_DATA\n");
+			eio_reg_read(i2c_chan, SMB_REG_HD0, &v);
+			data->block[0] = (u8)v;
+			dev_dbg(eio_dev(i2c_chan), "read %X\n", data->block[0]);
+		}
+		break;
+
+	case I2C_SMBUS_WORD_DATA: {
+		unsigned int v0, v1;
+
+		if (is_read) {
+			dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA\n");
+			eio_reg_read(i2c_chan, SMB_REG_HD0, &v0);
+			eio_reg_read(i2c_chan, SMB_REG_HD1, &v1);
+			data->block[0] = (u8)v0;
+			data->block[1] = (u8)v1;
+		}
+		break;
+	}
+
+	case I2C_SMBUS_PROC_CALL: {
+		unsigned int v0, v1;
+
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL\n");
+		eio_reg_read(i2c_chan, SMB_REG_HD0, &v0);
+		eio_reg_read(i2c_chan, SMB_REG_HD1, &v1);
+		data->block[0] = (u8)v0;
+		data->block[1] = (u8)v1;
+		break;
+	}
+
+	case I2C_SMBUS_BLOCK_DATA:
+		if (!is_read)
+			break;
+
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA\n");
+		eio_reg_read(i2c_chan, SMB_REG_HD0, (unsigned int *)&len);
+		len = min(len, I2C_SMBUS_BLOCK_MAX);
+		data->block[0] = len;
+
+		for (i = 1; i <= len; i++)
+			eio_reg_read(i2c_chan, SMB_REG_HBLOCK,
+				     (unsigned int *)&data->block[i]);
+		break;
+
+	default:
+		ret = -EINVAL;
+		goto exit;
+	}
+
+exit:
+	/* Clear latched status */
+	eio_reg_write(i2c_chan, SMB_REG_HS,  0xFF);
+	eio_reg_write(i2c_chan, SMB_REG_HS2, 0xFF);
+
+	mutex_unlock(&i2c_chan->lock);
+	return ret;
+}
+
+static int i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int nmsgs)
+{
+	int msg, data;
+	int addr = 0;
+	int dummy;
+	int ret = 0;
+	struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+
+	mutex_lock(&i2c_chan->lock);
+
+	ret = wait_ready(i2c_chan);
+	if (ret)
+		goto exit;
+
+	switch_i2c_mode(i2c_chan, true);
+
+	dev_dbg(eio_dev(i2c_chan), "Transmit %d I2C messages\n", nmsgs);
+	for (msg = 0; msg < nmsgs; msg++) {
+		int is_read = msgs[msg].flags & I2C_M_RD;
+		bool no_ack = msgs[msg].flags & I2C_M_IGNORE_NAK;
+
+		dev_dbg(eio_dev(i2c_chan), "message %d len=%d\n", msg, msgs[msg].len);
+
+		if (!msgs[msg].len)
+			let_stop(i2c_chan);
+
+		if (msgs[msg].flags & I2C_M_TEN) {
+			addr = eio_enc_10bit_addr(msgs[msg].addr);
+			addr |= is_read;
+			dev_dbg(eio_dev(i2c_chan), "10-bit addr: %X\n", addr);
+
+			ret = write_addr(i2c_chan, addr >> 8, no_ack);
+			if (!ret)
+				ret = write_data(i2c_chan, addr & 0x7F, no_ack);
+		} else {
+			addr = eio_enc_7bit_addr(msgs[msg].addr);
+			addr |= is_read;
+			dev_dbg(eio_dev(i2c_chan), "7-bit addr: %X\n", addr);
+
+			ret = write_addr(i2c_chan, addr, no_ack);
+		}
+
+		if (ret)
+			goto exit;
+
+		if (!msgs[msg].len)
+			goto exit;
+
+		if (is_read)
+			ret = eio_trigger_read(i2c_chan, (u32 *)&dummy);
+
+		/* Transmit all messages */
+		for (data = 0; data < msgs[msg].len; data++) {
+			if (msgs[msg].flags & I2C_M_RD) {
+				bool last = (msgs[msg].len == data + 1);
+
+				if (last)
+					let_stop(i2c_chan);
+
+				ret = read_data(i2c_chan, &msgs[msg].buf[data]);
+				dev_dbg(eio_dev(i2c_chan), "I2C read[%d] = %x\n",
+					data, msgs[msg].buf[data]);
+
+				/* Don't stop twice */
+				if (last && ret == 0)
+					goto exit;
+			} else {
+				ret = write_data(i2c_chan, msgs[msg].buf[data], no_ack);
+				dev_dbg(eio_dev(i2c_chan), "I2C write[%d] = %x\n",
+					data, msgs[msg].buf[data]);
+			}
+			if (ret)
+				goto exit;
+		}
+	}
+
+	if (!ret)
+		ret = bus_stop(i2c_chan);
+
+	if (!ret)
+		goto exit;
+
+exit:
+	if (ret)
+		reset_bus(i2c_chan);
+
+	i2c_clear(i2c_chan);
+	clr_inuse(i2c_chan);
+
+	mutex_unlock(&i2c_chan->lock);
+	return ret ? ret : nmsgs;
+}
+
+static int smbus_xfer(struct i2c_adapter *adap, u16 addr,
+		      u16 flags, char is_read, u8 cmd,
+		      int size, union i2c_smbus_data *data)
+{
+	int ret;
+	struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+	int nmsgs = is_read ? 2 : 1;
+	u8 buf[I2C_SMBUS_BLOCK_MAX + sizeof(u32)] = { cmd, };
+	struct i2c_msg msgs[2] = {
+		{ .addr = addr, .flags = flags & ~I2C_M_RD, .buf = buf + 0 },
+		{ .addr = addr, .flags = flags |  I2C_M_RD, .buf = buf + 1 },
+	};
+
+	/* Non-I2C channels use the SMB engine, except I2C block variants we emulate */
+	if (!is_i2c(i2c_chan) && size != I2C_SMBUS_I2C_BLOCK_DATA)
+		return smb_access(i2c_chan, addr, is_read, cmd, size, data);
+
+	if (data) {
+		buf[0] = cmd;
+		/* FIX: preserve other flags; only toggle I2C_M_RD */
+		msgs[0].flags = is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD);
+		msgs[1].buf   = data->block;
+	}
+
+	switch (size) {
+	case I2C_SMBUS_QUICK:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_QUICK on I2C\n");
+		nmsgs = 1;
+		break;
+
+	case I2C_SMBUS_BYTE:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE on I2C\n");
+		nmsgs = 1;
+		msgs[0].len   = 1;
+		msgs[0].buf   = is_read ? data->block : buf;
+		msgs[0].flags = is_read ? (flags | I2C_M_RD) : (flags & ~I2C_M_RD);
+		break;
+
+	case I2C_SMBUS_BYTE_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BYTE_DATA on I2C\n");
+		if (!data)
+			return -EINVAL;
+		msgs[0].len = is_read ? 1 : 2;
+		buf[1]      = data->block[0];
+		msgs[1].len = 1;
+		break;
+
+	case I2C_SMBUS_WORD_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_WORD_DATA on I2C\n");
+		if (!data)
+			return -EINVAL;
+		msgs[0].len = is_read ? 1 : 3;
+		msgs[1].len = 2;
+		buf[1]      = data->block[0];
+		buf[2]      = data->block[1];
+		msgs[1].buf = data->block;
+		break;
+
+	case I2C_SMBUS_I2C_BLOCK_DATA:
+	case I2C_SMBUS_I2C_BLOCK_BROKEN:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_I2C_BLOCK_(DATA/BROKEN) on I2C len=%d\n",
+			data->block[0]);
+		if (!data)
+			return -EINVAL;
+		msgs[0].len = is_read ? 1 : data->block[0] + 1;
+		msgs[1].len = data->block[0];
+		msgs[1].buf = data->block + 1;
+		if (msgs[0].len >= I2C_SMBUS_BLOCK_MAX ||
+		    msgs[1].len >= I2C_SMBUS_BLOCK_MAX)
+			return -EINVAL;
+		if (!is_read)
+			memcpy(buf + 1, data->block + 1, msgs[0].len);
+		break;
+
+	case I2C_SMBUS_PROC_CALL:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_PROC_CALL on I2C\n");
+		if (!data)
+			return -EINVAL;
+		nmsgs = 2;
+		msgs[0].flags = flags & ~I2C_M_RD;
+		msgs[0].len   = 3;
+		buf[1]        = data->block[0];
+		buf[2]        = data->block[1];
+		msgs[1].len   = 2;
+		break;
+
+	case I2C_SMBUS_BLOCK_DATA:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_DATA on I2C not supported\n");
+		return -EINVAL;
+
+	case I2C_SMBUS_BLOCK_PROC_CALL:
+		dev_dbg(eio_dev(i2c_chan), "I2C_SMBUS_BLOCK_PROC_CALL on I2C not supported\n");
+		return -EINVAL;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = i2c_xfer(adap, msgs, nmsgs);
+	return ret < 0 ? ret : 0;
+}
+
+static int load_i2c(struct device *dev, enum eio_chan_id id,
+		    struct eio_i2c_chan *i2c_chan)
+{
+	u32 base_lo, base_hi, base;
+	int ldn = LDN_I2C0 + id;
+	struct eio_i2c_dev *eio_i2c = i2c_chan->parent;
+	struct regmap *map;
+
+	if (!eio_i2c || !eio_i2c->regmap)
+		return dev_err_probe(dev, -ENODEV, "missing parent/regmap\n");
+
+	map = eio_i2c->regmap;
+
+	/* Read channel I/O base via shared PNP window */
+	mutex_lock(&eio_i2c->pnp_mutex);
+	if (regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) ||
+	    regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_ENTER) ||
+	    regmap_write(map, REG_PNP_INDEX, REG_LDN) ||
+	    regmap_write(map, REG_PNP_DATA,  ldn) ||
+	    regmap_write(map, REG_PNP_INDEX, REG_BASE_HI) ||
+	    regmap_read(map,  REG_PNP_DATA,  &base_hi) ||
+	    regmap_write(map, REG_PNP_INDEX, REG_BASE_LO) ||
+	    regmap_read(map,  REG_PNP_DATA,  &base_lo) ||
+	    regmap_write(map, REG_PNP_INDEX, REG_EXT_MODE_EXIT)) {
+		mutex_unlock(&eio_i2c->pnp_mutex);
+		dev_err(dev, "error read/write I2C[%d] IO port\n", id);
+		return -EIO;
+	}
+	mutex_unlock(&eio_i2c->pnp_mutex);
+
+	base = (base_hi << 8) | base_lo;
+	if (base == 0xFFFF || base == 0) {
+		dev_dbg(dev, "i2c[%d] base addr=%#x (not in-use)\n", id, base);
+		return -ENODEV;
+	}
+
+	dev_dbg(dev, "i2c[%d] base addr=%#x\n", id, base);
+
+	/* Bind channel (no per-chan dev) */
+	i2c_chan->base = (u16)base;
+	i2c_chan->id   = id;
+
+	/* Per-channel frequency policy */
+	if (i2c_chan->freq_override != USE_DEFAULT)
+		set_freq(i2c_chan, i2c_chan->freq_override);
+
+	get_freq(i2c_chan, &i2c_chan->freq_override);
+
+	return 0;
+}
+
+static u32 functionality(struct i2c_adapter *adap)
+{
+	struct eio_i2c_chan *i2c_chan = i2c_get_adapdata(adap);
+
+	return is_i2c(i2c_chan) ? SUPPORTED_I2C : SUPPORTED_SMB;
+}
+
+static const struct i2c_algorithm algo = {
+	.smbus_xfer	= smbus_xfer,
+	.master_xfer	= i2c_xfer,
+	.functionality	= functionality,
+};
+
+static int eio_i2c_probe(struct platform_device *pdev)
+{
+	static const char * const names[] = { "i2c0", "i2c1", "smb0", "smb1" };
+	struct device *dev = &pdev->dev;
+	struct eio_i2c_dev *eio_i2c;
+	struct eio_dev *eio_dev = dev_get_drvdata(dev->parent);
+	int ret = 0;
+	enum eio_chan_id ch;
+
+	if (!eio_dev) {
+		dev_err(dev, "Error contact eio_core\n");
+		return -ENODEV;
+	}
+
+	eio_i2c = devm_kzalloc(dev, sizeof(*eio_i2c), GFP_KERNEL);
+	if (!eio_i2c)
+		return -ENOMEM;
+
+	eio_i2c->dev = dev;
+	eio_i2c->mfd = dev->parent;
+	eio_i2c->regmap = dev_get_regmap(dev->parent, NULL);
+	if (!eio_i2c->regmap)
+		return dev_err_probe(dev, -ENODEV, "parent regmap not found\n");
+
+	mutex_init(&eio_i2c->pnp_mutex);
+	platform_set_drvdata(pdev, eio_i2c);
+
+	for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+		struct eio_i2c_chan *i2c_chan;
+
+		i2c_chan = devm_kzalloc(dev, sizeof(*i2c_chan), GFP_KERNEL);
+		if (!i2c_chan) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		i2c_chan->parent = eio_i2c;
+		i2c_chan->freq_override = USE_DEFAULT;
+		mutex_init(&i2c_chan->lock);
+
+		if (load_i2c(dev, ch, i2c_chan)) {
+			dev_info(dev, "No %s%d!\n", (ch < 2) ? "I2C" : "SMBus", ch & 1);
+			continue;
+		}
+
+		i2c_chan->adap.owner      = THIS_MODULE;
+		i2c_chan->adap.class      = I2C_CLASS_HWMON;
+		i2c_chan->adap.algo       = &algo;
+		i2c_chan->adap.dev.parent = dev;
+		snprintf(i2c_chan->adap.name, sizeof(i2c_chan->adap.name), "eio-%s",
+			 names[ch]);
+
+		i2c_set_adapdata(&i2c_chan->adap, i2c_chan);
+
+		ret = i2c_add_adapter(&i2c_chan->adap);
+		dev_info(dev, "Add %s%d %s. %d\n", (ch < 2) ? "I2C" : "SMBus",
+			 ch, ret ? "Error" : "Success", ret);
+		if (ret)
+			break;
+
+		eio_i2c->chan[ch] = i2c_chan;
+	}
+
+	if (ret) {
+		for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+			if (eio_i2c->chan[ch]) {
+				i2c_del_adapter(&eio_i2c->chan[ch]->adap);
+				eio_i2c->chan[ch] = NULL;
+			}
+		}
+	}
+
+	return ret;
+}
+
+static void eio_i2c_remove(struct platform_device *pdev)
+{
+	struct eio_i2c_dev *eio_i2c = platform_get_drvdata(pdev);
+	enum eio_chan_id ch;
+
+	for (ch = EIO_I2C0; ch < MAX_I2C_SMB; ch++) {
+		if (eio_i2c->chan[ch]) {
+			i2c_del_adapter(&eio_i2c->chan[ch]->adap);
+			eio_i2c->chan[ch] = NULL;
+		}
+	}
+}
+
+static struct platform_driver eio_i2c_driver = {
+	.probe  = eio_i2c_probe,
+	.remove = eio_i2c_remove,
+	.driver = {
+		.name = "i2c_eio",
+	},
+};
+
+module_platform_driver(eio_i2c_driver);
+
+MODULE_AUTHOR("Wenkai Chung <[email protected]>");
+MODULE_AUTHOR("Ramiro Oliveira <[email protected]>");
+MODULE_DESCRIPTION("I2C driver for Advantech EIO embedded controller");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: eio_core");

-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.