i2c: Add SpacemiT K1-X i2c driver 32/316532/1
authorMichal Wilczynski <m.wilczynski@samsung.com>
Thu, 22 Aug 2024 06:59:41 +0000 (08:59 +0200)
committerMichal Wilczynski <m.wilczynski@samsung.com>
Thu, 22 Aug 2024 11:53:28 +0000 (13:53 +0200)
The i2c is a dependence for SD card among other important hardware.
Port it from the vendor kernel [1].

[1] - https://github.com/BPI-SINOVOIP/pi-linux.git

Change-Id: Ieb3aec002103b000bec5531a4c1cb898842dd975
Signed-off-by: Michal Wilczynski <m.wilczynski@samsung.com>
drivers/i2c/busses/Kconfig
drivers/i2c/busses/Makefile
drivers/i2c/busses/i2c-k1x.c [new file with mode: 0644]
drivers/i2c/busses/i2c-k1x.h [new file with mode: 0644]

index 97d27e01a6ee27dcbcfaacbe94d712aa26fab4fc..5df7ecfc053dc5c90a7aae59a19b61a6d93e37e8 100644 (file)
@@ -301,6 +301,14 @@ config I2C_SIS96X
          This driver can also be built as a module.  If so, the module
          will be called i2c-sis96x.
 
+config I2C_SPACEMIT_K1X
+       tristate "Spacemit k1x I2C adapter"
+       help
+         Say yes if you want to use I2C interface on sapcemit k1x platform.
+
+         This driver can also be built as a module. If so, the module will be
+         called i2c-spacemit-k1x.
+
 config I2C_VIA
        tristate "VIA VT82C586B"
        depends on PCI
index 9be9fdb07f3dca5dc335553eab2a623d80050ac2..c276d596464ae91f712e513051f86790d776d7f6 100644 (file)
@@ -35,6 +35,7 @@ obj-$(CONFIG_I2C_HYDRA)               += i2c-hydra.o
 obj-$(CONFIG_I2C_POWERMAC)     += i2c-powermac.o
 
 # Embedded system I2C/SMBus host controller drivers
+obj-$(CONFIG_I2C_SPACEMIT_K1X)      += i2c-k1x.o
 obj-$(CONFIG_I2C_ALTERA)       += i2c-altera.o
 obj-$(CONFIG_I2C_AMD_MP2)      += i2c-amd-mp2-pci.o i2c-amd-mp2-plat.o
 obj-$(CONFIG_I2C_ASPEED)       += i2c-aspeed.o
diff --git a/drivers/i2c/busses/i2c-k1x.c b/drivers/i2c/busses/i2c-k1x.c
new file mode 100644 (file)
index 0000000..3991d78
--- /dev/null
@@ -0,0 +1,2049 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2023 Spacemit
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/scatterlist.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include <linux/reboot.h>
+
+#include "i2c-k1x.h"
+
+static inline u32 spacemit_i2c_read_reg(struct spacemit_i2c_dev *spacemit_i2c, int reg)
+{
+       return readl(spacemit_i2c->mapbase + reg);
+}
+
+static inline void
+spacemit_i2c_write_reg(struct spacemit_i2c_dev *spacemit_i2c, int reg, u32 val)
+{
+       writel(val, spacemit_i2c->mapbase + reg);
+}
+
+static void spacemit_i2c_enable(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR,
+       spacemit_i2c_read_reg(spacemit_i2c, REG_CR) | CR_IUE);
+}
+
+static void spacemit_i2c_disable(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       spacemit_i2c->i2c_ctrl_reg_value = spacemit_i2c_read_reg(spacemit_i2c, REG_CR) & ~CR_IUE;
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, spacemit_i2c->i2c_ctrl_reg_value);
+}
+
+static void spacemit_i2c_flush_fifo_buffer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       /* flush REG_WFIFO_WPTR and REG_WFIFO_RPTR */
+       spacemit_i2c_write_reg(spacemit_i2c, REG_WFIFO_WPTR, 0);
+       spacemit_i2c_write_reg(spacemit_i2c, REG_WFIFO_RPTR, 0);
+
+       /* flush REG_RFIFO_WPTR and REG_RFIFO_RPTR */
+       spacemit_i2c_write_reg(spacemit_i2c, REG_RFIFO_WPTR, 0);
+       spacemit_i2c_write_reg(spacemit_i2c, REG_RFIFO_RPTR, 0);
+}
+
+static void spacemit_i2c_controller_reset(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       /* i2c controller reset */
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, CR_UR);
+       udelay(5);
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, 0);
+
+       /* set load counter register */
+       if (spacemit_i2c->i2c_lcr)
+               spacemit_i2c_write_reg(spacemit_i2c, REG_LCR, spacemit_i2c->i2c_lcr);
+
+       /* set wait counter register */
+       if (spacemit_i2c->i2c_wcr)
+               spacemit_i2c_write_reg(spacemit_i2c, REG_WCR, spacemit_i2c->i2c_wcr);
+}
+
+static void spacemit_i2c_bus_reset(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int clk_cnt = 0;
+       u32 bus_status;
+
+       /* if bus is locked, reset unit. 0: locked */
+       bus_status = spacemit_i2c_read_reg(spacemit_i2c, REG_BMR);
+       if (!(bus_status & BMR_SDA) || !(bus_status & BMR_SCL)) {
+               spacemit_i2c_controller_reset(spacemit_i2c);
+               usleep_range(10, 20);
+
+               /* check scl status again */
+               bus_status = spacemit_i2c_read_reg(spacemit_i2c, REG_BMR);
+               if (!(bus_status & BMR_SCL))
+                       dev_alert(spacemit_i2c->dev, "unit reset failed\n");
+       }
+
+       while (clk_cnt < 9) {
+               /* check whether the SDA is still locked by slave */
+               bus_status = spacemit_i2c_read_reg(spacemit_i2c, REG_BMR);
+               if (bus_status & BMR_SDA)
+                       break;
+
+               /* if still locked, send one clk to slave to request release */
+               spacemit_i2c_write_reg(spacemit_i2c, REG_RST_CYC, 0x1);
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, CR_RSTREQ);
+               usleep_range(20, 30);
+               clk_cnt++;
+       }
+
+       bus_status = spacemit_i2c_read_reg(spacemit_i2c, REG_BMR);
+       if (clk_cnt >= 9 && !(bus_status & BMR_SDA))
+               dev_alert(spacemit_i2c->dev, "bus reset clk reaches the max 9-clocks\n");
+       else
+               dev_alert(spacemit_i2c->dev, "bus reset, send clk: %d\n", clk_cnt);
+}
+
+static void spacemit_i2c_reset(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       spacemit_i2c_controller_reset(spacemit_i2c);
+}
+
+static int spacemit_i2c_recover_bus_busy(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int timeout;
+       int cnt, ret = 0;
+
+       if (spacemit_i2c->high_mode)
+               timeout = 1000; /* 1000us */
+       else
+               timeout = 1500; /* 1500us  */
+
+       cnt = SPACEMIT_I2C_BUS_RECOVER_TIMEOUT / timeout;
+
+       if (likely(!(spacemit_i2c_read_reg(spacemit_i2c, REG_SR) & (SR_UB | SR_IBB))))
+               return 0;
+
+       /* wait unit and bus to recover idle */
+       while (unlikely(spacemit_i2c_read_reg(spacemit_i2c, REG_SR) & (SR_UB | SR_IBB))) {
+               if (cnt-- <= 0)
+                       break;
+
+               usleep_range(timeout / 2, timeout);
+       }
+
+       if (unlikely(cnt <= 0)) {
+               /* reset controller */
+               spacemit_i2c_reset(spacemit_i2c);
+               ret = -EAGAIN;
+       }
+
+       return ret;
+}
+
+static void spacemit_i2c_check_bus_release(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       /* in case bus is not released after transfer completes */
+       if (unlikely(spacemit_i2c_read_reg(spacemit_i2c, REG_SR) & SR_EBB)) {
+               spacemit_i2c_bus_reset(spacemit_i2c);
+               usleep_range(90, 150);
+       }
+}
+
+static void spacemit_i2c_unit_init(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       u32 cr_val = 0;
+
+       /*
+        * Unmask interrupt bits for all xfer mode:
+        * bus error, arbitration loss detected.
+        * For transaction complete signal, we use master stop
+        * interrupt, so we don't need to unmask CR_TXDONEIE.
+        */
+       cr_val |= CR_BEIE | CR_ALDIE;
+
+       if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT))
+               /*
+                * Unmask interrupt bits for interrupt xfer mode:
+                * DBR rx full.
+                * For tx empty interrupt CR_DTEIE, we only
+                * need to enable when trigger byte transfer to start
+                * data sending.
+                */
+               cr_val |= CR_DRFIE;
+       else if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_FIFO))
+               /* enable i2c FIFO mode*/
+               cr_val |= CR_FIFOEN;
+       else if (spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_DMA)
+               /* enable i2c DMA mode*/
+               cr_val |= CR_DMAEN | CR_FIFOEN;
+
+       /* set speed bits */
+       if (spacemit_i2c->fast_mode)
+               cr_val |= CR_MODE_FAST;
+       if (spacemit_i2c->high_mode)
+               cr_val |= CR_MODE_HIGH | CR_GPIOEN;
+
+       /* disable response to general call */
+       cr_val |= CR_GCD;
+
+       /* enable SCL clock output */
+       cr_val |= CR_SCLE;
+
+       /* enable master stop detected */
+       cr_val |= CR_MSDE | CR_MSDIE;
+
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+}
+
+static void spacemit_i2c_trigger_byte_xfer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       u32 cr_val = spacemit_i2c_read_reg(spacemit_i2c, REG_CR);
+
+       /* send start pulse */
+       cr_val &= ~CR_STOP;
+       cr_val |= CR_START | CR_TB | CR_DTEIE;
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+}
+
+static inline void
+spacemit_i2c_clear_int_status(struct spacemit_i2c_dev *spacemit_i2c, u32 mask)
+{
+       spacemit_i2c_write_reg(spacemit_i2c, REG_SR, mask & SPACEMIT_I2C_INT_STATUS_MASK);
+}
+
+static bool spacemit_i2c_is_last_byte_to_send(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       return (spacemit_i2c->tx_cnt == spacemit_i2c->cur_msg->len &&
+               spacemit_i2c->msg_idx == spacemit_i2c->num - 1) ? true : false;
+}
+
+static bool spacemit_i2c_is_last_byte_to_receive(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       /*
+        * if the message length is received from slave device,
+        * should at least to read out the length byte from slave.
+        */
+       if (unlikely((spacemit_i2c->cur_msg->flags & I2C_M_RECV_LEN) &&
+               !spacemit_i2c->smbus_rcv_len)) {
+               return false;
+       } else {
+               return (spacemit_i2c->rx_cnt == spacemit_i2c->cur_msg->len - 1 &&
+                       spacemit_i2c->msg_idx == spacemit_i2c->num - 1) ? true : false;
+       }
+}
+
+static void spacemit_i2c_mark_rw_flag(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       if (spacemit_i2c->cur_msg->flags & I2C_M_RD) {
+               spacemit_i2c->is_rx = true;
+               spacemit_i2c->slave_addr_rw =
+                       ((spacemit_i2c->cur_msg->addr & 0x7f) << 1) | 1;
+       } else {
+               spacemit_i2c->is_rx = false;
+               spacemit_i2c->slave_addr_rw = (spacemit_i2c->cur_msg->addr & 0x7f) << 1;
+       }
+}
+
+static void spacemit_i2c_byte_xfer_send_master_code(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       u32 cr_val = spacemit_i2c_read_reg(spacemit_i2c, REG_CR);
+
+       spacemit_i2c->phase = SPACEMIT_I2C_XFER_MASTER_CODE;
+
+       spacemit_i2c_write_reg(spacemit_i2c, REG_DBR, spacemit_i2c->master_code);
+
+       cr_val &= ~(CR_STOP | CR_ALDIE);
+
+       /* high mode: enable gpio to let I2C core generates SCL clock */
+       cr_val |= CR_GPIOEN | CR_START | CR_TB | CR_DTEIE;
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+}
+
+static void spacemit_i2c_byte_xfer_send_slave_addr(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       spacemit_i2c->phase = SPACEMIT_I2C_XFER_SLAVE_ADDR;
+
+       /* write slave address to DBR for interrupt mode */
+       spacemit_i2c_write_reg(spacemit_i2c, REG_DBR, spacemit_i2c->slave_addr_rw);
+
+       spacemit_i2c_trigger_byte_xfer(spacemit_i2c);
+}
+
+static int spacemit_i2c_byte_xfer(struct spacemit_i2c_dev *spacemit_i2c);
+static int spacemit_i2c_byte_xfer_next_msg(struct spacemit_i2c_dev *spacemit_i2c);
+
+static int spacemit_i2c_byte_xfer_body(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int ret = 0;
+       u8  msglen = 0;
+       u32 cr_val = spacemit_i2c_read_reg(spacemit_i2c, REG_CR);
+
+       cr_val &= ~(CR_TB | CR_ACKNAK | CR_STOP | CR_START);
+       spacemit_i2c->phase = SPACEMIT_I2C_XFER_BODY;
+
+       if (spacemit_i2c->i2c_status & SR_IRF) { /* i2c receive full */
+               /* if current is transmit mode, ignore this signal */
+               if (!spacemit_i2c->is_rx)
+                       return 0;
+
+               /*
+                * if the message length is received from slave device,
+                * according to i2c spec, we should restrict the length size.
+                */
+               if (unlikely((spacemit_i2c->cur_msg->flags & I2C_M_RECV_LEN) &&
+                               !spacemit_i2c->smbus_rcv_len)) {
+                       spacemit_i2c->smbus_rcv_len = true;
+                       msglen = (u8)spacemit_i2c_read_reg(spacemit_i2c, REG_DBR);
+                       if ((msglen == 0) ||
+                               (msglen > I2C_SMBUS_BLOCK_MAX)) {
+                               dev_err(spacemit_i2c->dev,
+                                               "SMbus len out of range\n");
+                               *spacemit_i2c->msg_buf++ = 0;
+                               spacemit_i2c->rx_cnt = spacemit_i2c->cur_msg->len;
+                               cr_val |= CR_STOP | CR_ACKNAK;
+                               cr_val |= CR_ALDIE | CR_TB;
+                               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+
+                               return 0;
+                       } else {
+                               *spacemit_i2c->msg_buf++ = msglen;
+                               spacemit_i2c->cur_msg->len = msglen + 1;
+                               spacemit_i2c->rx_cnt++;
+                       }
+               } else {
+                       if (spacemit_i2c->rx_cnt < spacemit_i2c->cur_msg->len) {
+                               *spacemit_i2c->msg_buf++ =
+                                       spacemit_i2c_read_reg(spacemit_i2c, REG_DBR);
+                               spacemit_i2c->rx_cnt++;
+                       }
+               }
+               /* if transfer completes, ISR will handle it */
+               if (spacemit_i2c->i2c_status & (SR_MSD | SR_ACKNAK))
+                       return 0;
+
+               /* trigger next byte receive */
+               if (spacemit_i2c->rx_cnt < spacemit_i2c->cur_msg->len) {
+                       /* send stop pulse for last byte of last msg */
+                       if (spacemit_i2c_is_last_byte_to_receive(spacemit_i2c))
+                               cr_val |= CR_STOP | CR_ACKNAK;
+
+                       cr_val |= CR_ALDIE | CR_TB;
+                       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+               } else if (spacemit_i2c->msg_idx < spacemit_i2c->num - 1) {
+                       ret = spacemit_i2c_byte_xfer_next_msg(spacemit_i2c);
+               } else {
+                       /*
+                        * For this branch, we do nothing, here the receive
+                        * transfer is already done, the master stop interrupt
+                        * should be generated to complete this transaction.
+                       */
+               }
+       } else if (spacemit_i2c->i2c_status & SR_ITE) { /* i2c transmit empty */
+               /* MSD comes with ITE */
+               if (spacemit_i2c->i2c_status & SR_MSD)
+                       return ret;
+
+               if (spacemit_i2c->i2c_status & SR_RWM) { /* receive mode */
+                       /* if current is transmit mode, ignore this signal */
+                       if (!spacemit_i2c->is_rx)
+                               return 0;
+
+                       if (spacemit_i2c_is_last_byte_to_receive(spacemit_i2c))
+                               cr_val |= CR_STOP | CR_ACKNAK;
+
+                       /* trigger next byte receive */
+                       cr_val |= CR_ALDIE | CR_TB;
+
+                       /*
+                        * Mask transmit empty interrupt to avoid useless tx
+                        * interrupt signal after switch to receive mode, the
+                        * next expected is receive full interrupt signal.
+                        */
+                       cr_val &= ~CR_DTEIE;
+                       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+               } else { /* transmit mode */
+                       /* if current is receive mode, ignore this signal */
+                       if (spacemit_i2c->is_rx)
+                               return 0;
+
+                       if (spacemit_i2c->tx_cnt < spacemit_i2c->cur_msg->len) {
+                               spacemit_i2c_write_reg(spacemit_i2c, REG_DBR,
+                                               *spacemit_i2c->msg_buf++);
+                               spacemit_i2c->tx_cnt++;
+
+                               /* send stop pulse for last byte of last msg */
+                               if (spacemit_i2c_is_last_byte_to_send(spacemit_i2c))
+                                       cr_val |= CR_STOP;
+
+                               cr_val |= CR_ALDIE | CR_TB;
+                               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, cr_val);
+                       } else if (spacemit_i2c->msg_idx < spacemit_i2c->num - 1) {
+                               ret = spacemit_i2c_byte_xfer_next_msg(spacemit_i2c);
+                       } else {
+                               /*
+                                * For this branch, we do nothing, here the
+                                * sending transfer is already done, the master
+                                * stop interrupt should be generated to
+                                * complete this transaction.
+                               */
+                       }
+               }
+       }
+
+       return ret;
+}
+
+static int spacemit_i2c_byte_xfer_next_msg(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       if (spacemit_i2c->msg_idx == spacemit_i2c->num - 1)
+               return 0;
+
+       spacemit_i2c->msg_idx++;
+       spacemit_i2c->cur_msg = spacemit_i2c->msgs + spacemit_i2c->msg_idx;
+       spacemit_i2c->msg_buf = spacemit_i2c->cur_msg->buf;
+       spacemit_i2c->rx_cnt = 0;
+       spacemit_i2c->tx_cnt = 0;
+       spacemit_i2c->i2c_err = 0;
+       spacemit_i2c->i2c_status = 0;
+       spacemit_i2c->smbus_rcv_len = false;
+       spacemit_i2c->phase = SPACEMIT_I2C_XFER_IDLE;
+
+       spacemit_i2c_mark_rw_flag(spacemit_i2c);
+
+       return spacemit_i2c_byte_xfer(spacemit_i2c);
+}
+
+static void spacemit_i2c_fifo_xfer_fill_buffer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int finish, count = 0, fill = 0;
+       u32 data = 0;
+       u32 data_buf[SPACEMIT_I2C_TX_FIFO_DEPTH * 2];
+       int data_cnt = 0, i;
+       unsigned long flags;
+
+       while (spacemit_i2c->msg_idx < spacemit_i2c->num) {
+               spacemit_i2c_mark_rw_flag(spacemit_i2c);
+
+               if (spacemit_i2c->is_rx)
+                       finish = spacemit_i2c->rx_cnt;
+               else
+                       finish = spacemit_i2c->tx_cnt;
+
+               /* write master code to fifo buffer */
+               if (spacemit_i2c->high_mode && spacemit_i2c->is_xfer_start) {
+                       data = spacemit_i2c->master_code;
+                       data |= WFIFO_CTRL_TB | WFIFO_CTRL_START;
+                       data_buf[data_cnt++] = data;
+
+                       fill += 2;
+                       count = min_t(size_t, spacemit_i2c->cur_msg->len - finish,
+                                       SPACEMIT_I2C_TX_FIFO_DEPTH - fill);
+               } else {
+                       fill += 1;
+                       count = min_t(size_t, spacemit_i2c->cur_msg->len - finish,
+                                       SPACEMIT_I2C_TX_FIFO_DEPTH - fill);
+               }
+
+               spacemit_i2c->is_xfer_start = false;
+               fill += count;
+               data = spacemit_i2c->slave_addr_rw;
+               data |= WFIFO_CTRL_TB | WFIFO_CTRL_START;
+
+               /* write slave address to fifo buffer */
+               data_buf[data_cnt++] = data;
+
+               if (spacemit_i2c->is_rx) {
+                       spacemit_i2c->rx_cnt += count;
+
+                       if (spacemit_i2c->rx_cnt == spacemit_i2c->cur_msg->len &&
+                               spacemit_i2c->msg_idx == spacemit_i2c->num - 1)
+                               count -= 1;
+
+                       while (count > 0) {
+                               data = *spacemit_i2c->msg_buf | WFIFO_CTRL_TB;
+                               data_buf[data_cnt++] = data;
+                               spacemit_i2c->msg_buf++;
+                               count--;
+                       }
+
+                       if (spacemit_i2c->rx_cnt == spacemit_i2c->cur_msg->len &&
+                               spacemit_i2c->msg_idx == spacemit_i2c->num - 1) {
+                               data = *spacemit_i2c->msg_buf++;
+                               data = spacemit_i2c->slave_addr_rw | WFIFO_CTRL_TB |
+                                       WFIFO_CTRL_STOP | WFIFO_CTRL_ACKNAK;
+                               data_buf[data_cnt++] = data;
+                       }
+               } else {
+                       spacemit_i2c->tx_cnt += count;
+                       if (spacemit_i2c_is_last_byte_to_send(spacemit_i2c))
+                               count -= 1;
+
+                       while (count > 0) {
+                               data = *spacemit_i2c->msg_buf | WFIFO_CTRL_TB;
+                               data_buf[data_cnt++] = data;
+                               spacemit_i2c->msg_buf++;
+                               count--;
+                       }
+                       if (spacemit_i2c_is_last_byte_to_send(spacemit_i2c)) {
+                               data = *spacemit_i2c->msg_buf | WFIFO_CTRL_TB |
+                                               WFIFO_CTRL_STOP;
+                               data_buf[data_cnt++] = data;
+                       }
+               }
+
+               if (spacemit_i2c->tx_cnt == spacemit_i2c->cur_msg->len ||
+                       spacemit_i2c->rx_cnt == spacemit_i2c->cur_msg->len) {
+                       spacemit_i2c->msg_idx++;
+                       if (spacemit_i2c->msg_idx == spacemit_i2c->num)
+                               break;
+
+                       spacemit_i2c->cur_msg = spacemit_i2c->msgs + spacemit_i2c->msg_idx;
+                       spacemit_i2c->msg_buf = spacemit_i2c->cur_msg->buf;
+                       spacemit_i2c->rx_cnt = 0;
+                       spacemit_i2c->tx_cnt = 0;
+               }
+
+               if (fill == SPACEMIT_I2C_TX_FIFO_DEPTH)
+                       break;
+       }
+
+       spin_lock_irqsave(&spacemit_i2c->fifo_lock, flags);
+       for (i = 0; i < data_cnt; i++)
+               spacemit_i2c_write_reg(spacemit_i2c, REG_WFIFO, data_buf[i]);
+       spin_unlock_irqrestore(&spacemit_i2c->fifo_lock, flags);
+}
+
+static void spacemit_i2c_fifo_xfer_copy_buffer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int idx = 0, cnt = 0;
+       struct i2c_msg *msg;
+
+       /* copy the rx FIFO buffer to msg */
+       while (idx < spacemit_i2c->num) {
+               msg = spacemit_i2c->msgs + idx;
+               if (msg->flags & I2C_M_RD) {
+                       cnt = msg->len;
+                       while (cnt > 0) {
+                               *(msg->buf + msg->len - cnt)
+                                       = spacemit_i2c_read_reg(spacemit_i2c, REG_RFIFO);
+                               cnt--;
+                       }
+               }
+               idx++;
+       }
+}
+
+static int spacemit_i2c_fifo_xfer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int ret = 0;
+       unsigned long time_left;
+
+       spacemit_i2c_fifo_xfer_fill_buffer(spacemit_i2c);
+
+       time_left = wait_for_completion_timeout(&spacemit_i2c->complete,
+                                       spacemit_i2c->timeout);
+       if (unlikely(time_left == 0)) {
+               dev_alert(spacemit_i2c->dev, "fifo transfer timeout\n");
+               spacemit_i2c_bus_reset(spacemit_i2c);
+               ret = -ETIMEDOUT;
+               goto err_out;
+       }
+
+       if (unlikely(spacemit_i2c->i2c_err)) {
+               ret = -1;
+               spacemit_i2c_flush_fifo_buffer(spacemit_i2c);
+               goto err_out;
+       }
+
+       spacemit_i2c_fifo_xfer_copy_buffer(spacemit_i2c);
+
+err_out:
+       return ret;
+}
+
+static void spacemit_i2c_dma_copy_buffer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int idx = 0, total = 0, i, cnt = 0;
+       struct i2c_msg *cur_msg;
+
+       /* calculate total rx bytes */
+       while (idx < spacemit_i2c->num) {
+               if ((spacemit_i2c->msgs + idx)->flags & I2C_M_RD)
+                       total += (spacemit_i2c->msgs + idx)->len;
+               idx++;
+       }
+
+       idx = 0;
+       total -= total % SPACEMIT_I2C_RX_FIFO_DEPTH;
+       while (idx < spacemit_i2c->num) {
+               cur_msg = spacemit_i2c->msgs + idx;
+               if (cur_msg->flags & I2C_M_RD) {
+                       for (i = 0; i < cur_msg->len; i++) {
+                               if (cnt < total) {
+                                       *(cur_msg->buf + i) = spacemit_i2c->rx_dma_buf[cnt];
+                               } else {
+                                       /* copy the rest bytes from FIFO  */
+                                       *(cur_msg->buf + i) =
+                                       spacemit_i2c_read_reg(spacemit_i2c, REG_RFIFO) &
+                                       0xff;
+                               }
+                               cnt++;
+                       }
+               }
+               idx++;
+       }
+}
+
+static void spacemit_i2c_dma_callback(void *data)
+{
+       return;
+}
+
+static int
+spacemit_i2c_map_rx_sg(struct spacemit_i2c_dev *spacemit_i2c, int rx_nents, int *rx_total)
+{
+       int len;
+       int rx_buf_start = *rx_total;
+
+       *rx_total += spacemit_i2c->cur_msg->len;
+       if (*rx_total < spacemit_i2c->rx_total) {
+               len = spacemit_i2c->cur_msg->len;
+       } else {
+               len = spacemit_i2c->cur_msg->len - *rx_total + spacemit_i2c->rx_total;
+               spacemit_i2c->rx_total = 0;
+       }
+       sg_set_buf(spacemit_i2c->rx_sg + rx_nents, &(spacemit_i2c->rx_dma_buf[rx_buf_start]), len);
+
+       return dma_map_sg(spacemit_i2c->dev, spacemit_i2c->rx_sg + rx_nents,
+                       1, DMA_FROM_DEVICE);
+}
+
+static int spacemit_i2c_dma_xfer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       struct dma_async_tx_descriptor *tx_des = NULL, *rx_des = NULL;
+       dma_cookie_t rx_ck = 0, tx_ck;
+       u32 rx_nents = 0, tx_nents = 0, data;
+       int ret = 0, idx = 0, count = 0, start = 0, i;
+       unsigned long time_left;
+       int rx_total = 0;
+       int comp_timeout = 1000000; /* (us) */
+
+       spacemit_i2c->rx_total -= spacemit_i2c->rx_total % SPACEMIT_I2C_RX_FIFO_DEPTH;
+       while (idx < spacemit_i2c->num) {
+               spacemit_i2c->msg_idx = idx;
+               spacemit_i2c->cur_msg = spacemit_i2c->msgs + idx;
+               spacemit_i2c_mark_rw_flag(spacemit_i2c);
+
+               if (idx == 0 && spacemit_i2c->high_mode) {
+                       /* fill master code */
+                       data = (spacemit_i2c->master_code & 0xff) |
+                               WFIFO_CTRL_TB | WFIFO_CTRL_START;
+                       *(spacemit_i2c->tx_dma_buf + count) = data;
+                       count++;
+               }
+               /* fill slave address */
+               data = spacemit_i2c->slave_addr_rw |
+                               WFIFO_CTRL_TB | WFIFO_CTRL_START;
+               *(spacemit_i2c->tx_dma_buf + count) = data;
+               count++;
+
+               if (spacemit_i2c->is_rx) {
+                       if (spacemit_i2c->rx_total) {
+                               ret = spacemit_i2c_map_rx_sg(spacemit_i2c,
+                                               rx_nents, &rx_total);
+                               if (!ret) {
+                                       dev_err(spacemit_i2c->dev,
+                                               "failed to map scatterlist\n");
+                                       ret = -EINVAL;
+                                       goto err_map;
+                               }
+
+                               rx_nents++;
+                       }
+
+                       for (i = 0; i < spacemit_i2c->cur_msg->len - 1; i++) {
+                               data = spacemit_i2c->slave_addr_rw | WFIFO_CTRL_TB;
+                               *(spacemit_i2c->tx_dma_buf + count) = data;
+                               count++;
+                       }
+                       data = spacemit_i2c->slave_addr_rw | WFIFO_CTRL_TB;
+
+                       /* send nak and stop pulse for last msg */
+                       if (idx == spacemit_i2c->num - 1)
+                               data |= WFIFO_CTRL_ACKNAK | WFIFO_CTRL_STOP;
+                       *(spacemit_i2c->tx_dma_buf + count++) = data;
+                       start += spacemit_i2c->cur_msg->len;
+               } else {
+                       for (i = 0; i < spacemit_i2c->cur_msg->len - 1; i++) {
+                               data = *(spacemit_i2c->cur_msg->buf + i) |
+                                       WFIFO_CTRL_TB;
+                               *(spacemit_i2c->tx_dma_buf + count) = data;
+                               count++;
+                       }
+                       data = *(spacemit_i2c->cur_msg->buf + i) | WFIFO_CTRL_TB;
+
+                       /* send stop pulse for last msg */
+                       if (idx == spacemit_i2c->num - 1)
+                               data |= WFIFO_CTRL_STOP;
+                       *(spacemit_i2c->tx_dma_buf + count++) = data;
+               }
+               idx++;
+       }
+
+       sg_set_buf(spacemit_i2c->tx_sg, spacemit_i2c->tx_dma_buf,
+                       count * sizeof(spacemit_i2c->tx_dma_buf[0]));
+       ret = dma_map_sg(spacemit_i2c->dev, spacemit_i2c->tx_sg, 1, DMA_TO_DEVICE);
+       if (unlikely(!ret)) {
+               dev_err(spacemit_i2c->dev, "failed to map scatterlist\n");
+               ret = -EINVAL;
+               goto err_map;
+       }
+
+       tx_nents++;
+       tx_des = dmaengine_prep_slave_sg(spacemit_i2c->tx_dma, spacemit_i2c->tx_sg, 1,
+                                     DMA_MEM_TO_DEV,
+                                     DMA_PREP_INTERRUPT | DMA_PREP_FENCE);
+       if (unlikely(!tx_des)) {
+               dev_err(spacemit_i2c->dev, "failed to get dma tx descriptor\n");
+               ret = -EINVAL;
+               goto err_desc;
+       }
+
+       tx_des->callback = spacemit_i2c_dma_callback;
+       tx_des->callback_param = spacemit_i2c;
+
+       tx_ck = dmaengine_submit(tx_des);
+       if (unlikely(dma_submit_error(tx_ck))) {
+               ret = -EINVAL;
+               goto err_desc;
+       }
+
+       if (likely(rx_nents)) {
+               rx_des = dmaengine_prep_slave_sg(spacemit_i2c->rx_dma,
+                                               spacemit_i2c->rx_sg,
+                                               rx_nents, DMA_DEV_TO_MEM,
+                                               DMA_PREP_INTERRUPT);
+               if (unlikely(!rx_des)) {
+                       dev_err(spacemit_i2c->dev,
+                               "failed to get dma rx descriptor\n");
+                       ret = -EINVAL;
+                       goto err_desc;
+               }
+
+               rx_des->callback = spacemit_i2c_dma_callback;
+               rx_des->callback_param = spacemit_i2c;
+               rx_ck = dmaengine_submit(rx_des);
+               if (unlikely(dma_submit_error(rx_ck))) {
+                       dev_err(spacemit_i2c->dev,
+                               "failed to submit rx channel\n");
+                       ret = -EINVAL;
+                       goto err_desc;
+               }
+
+               dma_async_issue_pending(spacemit_i2c->rx_dma);
+       }
+
+       dma_async_issue_pending(spacemit_i2c->tx_dma);
+
+       time_left = wait_for_completion_timeout(&spacemit_i2c->complete,
+                                               spacemit_i2c->timeout);
+       if (unlikely(time_left == 0)) {
+               dev_alert(spacemit_i2c->dev, "dma transfer timeout\n");
+               spacemit_i2c_bus_reset(spacemit_i2c);
+               spacemit_i2c_reset(spacemit_i2c);
+               ret = -ETIMEDOUT;
+               comp_timeout = 0;
+               goto finish;
+       }
+
+       if (unlikely(spacemit_i2c->i2c_err)) {
+               ret = -1;
+               spacemit_i2c_flush_fifo_buffer(spacemit_i2c);
+               comp_timeout = 0;
+       }
+
+finish:
+       /*
+        * wait for the rx DMA to complete, for tx, we use the i2c
+        * TXDONE/STOP interrupt, here we already receive the
+        * TXDONE/STOP signal.
+        */
+       if (unlikely(rx_nents && dma_async_is_tx_complete(spacemit_i2c->rx_dma,
+                               rx_ck, NULL, NULL) != DMA_COMPLETE)) {
+               int timeout = comp_timeout;
+
+               while (timeout > 0) {
+                       if (dma_async_is_tx_complete(spacemit_i2c->rx_dma,
+                               rx_ck, NULL, NULL) != DMA_COMPLETE) {
+                               usleep_range(2, 4);
+                               timeout -= 4;
+                       } else
+                               break;
+               }
+               if (timeout <= 0) {
+                       dmaengine_pause(spacemit_i2c->rx_dma);
+                       if (ret >= 0) {
+                               ret = -1;
+                               dev_err(spacemit_i2c->dev,
+                                       "dma rx channel timeout\n");
+                       }
+               }
+       }
+
+       if (likely(ret >= 0))
+               spacemit_i2c_dma_copy_buffer(spacemit_i2c);
+
+err_desc:
+       dma_unmap_sg(spacemit_i2c->dev, spacemit_i2c->tx_sg, tx_nents, DMA_TO_DEVICE);
+err_map:
+       if (likely(rx_nents))
+               dma_unmap_sg(spacemit_i2c->dev, spacemit_i2c->rx_sg,
+                               rx_nents, DMA_FROM_DEVICE);
+
+       /* make sure terminate transfers and free descriptors */
+       if (tx_des)
+               dmaengine_terminate_all(spacemit_i2c->tx_dma);
+
+       if (rx_des)
+               dmaengine_terminate_all(spacemit_i2c->rx_dma);
+
+       return ret < 0 ? ret : 0;
+}
+
+static int spacemit_i2c_byte_xfer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int ret = 0;
+
+       /* i2c error occurs */
+       if (unlikely(spacemit_i2c->i2c_err))
+               return -1;
+
+       if (spacemit_i2c->phase == SPACEMIT_I2C_XFER_IDLE) {
+               if (spacemit_i2c->high_mode && spacemit_i2c->is_xfer_start)
+                       spacemit_i2c_byte_xfer_send_master_code(spacemit_i2c);
+               else
+                       spacemit_i2c_byte_xfer_send_slave_addr(spacemit_i2c);
+
+               spacemit_i2c->is_xfer_start = false;
+       } else if (spacemit_i2c->phase == SPACEMIT_I2C_XFER_MASTER_CODE) {
+               spacemit_i2c_byte_xfer_send_slave_addr(spacemit_i2c);
+       } else {
+               ret = spacemit_i2c_byte_xfer_body(spacemit_i2c);
+       }
+
+       return ret;
+}
+
+static void spacemit_i2c_print_msg_info(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int i, j, idx;
+       char printbuf[512];
+
+       idx = sprintf(printbuf, "msgs: %d, mode: %d", spacemit_i2c->num,
+                                       spacemit_i2c->xfer_mode);
+       for (i = 0; i < spacemit_i2c->num && i < sizeof(printbuf) / 128; i++) {
+               u16 len = spacemit_i2c->msgs[i].len & 0xffff;
+
+               idx += sprintf(printbuf + idx, ", addr: %02x",
+                       spacemit_i2c->msgs[i].addr);
+               idx += sprintf(printbuf + idx, ", flag: %c, len: %d",
+                       spacemit_i2c->msgs[i].flags & I2C_M_RD ? 'R' : 'W', len);
+               if (!(spacemit_i2c->msgs[i].flags & I2C_M_RD)) {
+                       idx += sprintf(printbuf + idx, ", data:");
+                       /* print at most ten bytes of data */
+                       for (j = 0; j < len && j < 10; j++)
+                               idx += sprintf(printbuf + idx, " %02x",
+                                       spacemit_i2c->msgs[i].buf[j]);
+               }
+       }
+
+}
+
+static int spacemit_i2c_handle_err(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       if (unlikely(spacemit_i2c->i2c_err)) {
+               dev_dbg(spacemit_i2c->dev, "i2c error status: 0x%08x\n",
+                               spacemit_i2c->i2c_status);
+               if (spacemit_i2c->i2c_err & (SR_BED  | SR_ALD))
+                       spacemit_i2c_reset(spacemit_i2c);
+
+               /* try transfer again */
+               if (spacemit_i2c->i2c_err & (SR_RXOV | SR_ALD)) {
+                       spacemit_i2c_flush_fifo_buffer(spacemit_i2c);
+                       return -EAGAIN;
+               }
+               return (spacemit_i2c->i2c_status & SR_ACKNAK) ? -ENXIO : -EIO;
+       }
+
+       return 0;
+}
+
+#ifdef CONFIG_I2C_SLAVE
+static void spacemit_i2c_slave_handler(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       u32 status = spacemit_i2c->i2c_status;
+       u8 value;
+
+       /* clear interrupt status bits[31:18]*/
+       spacemit_i2c_clear_int_status(spacemit_i2c, status);
+
+       if (unlikely(status & (SR_EBB | SR_BED))) {
+               dev_err(spacemit_i2c->dev,"i2c slave bus error status = 0x%x, reset controller\n", status);
+               /* controller reset */
+               spacemit_i2c_controller_reset(spacemit_i2c);
+
+               /* reinit spacemit i2c slave */
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, SPACEMIT_I2C_SLAVE_CRINIT);
+               return;
+       }
+
+       /* slave address detected */
+       if (status & SR_SAD) {
+               /* read or write request */
+               if (status & SR_RWM) {
+                       i2c_slave_event(spacemit_i2c->slave, I2C_SLAVE_READ_REQUESTED, &value);
+                       spacemit_i2c_write_reg(spacemit_i2c, REG_DBR, value & 0xff);
+               } else {
+                       i2c_slave_event(spacemit_i2c->slave, I2C_SLAVE_WRITE_REQUESTED, &value);
+               }
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, CR_TB | spacemit_i2c_read_reg(spacemit_i2c, REG_CR));
+       } else if (status & SR_SSD) { /* stop detect */
+               i2c_slave_event(spacemit_i2c->slave, I2C_SLAVE_STOP, &value);
+               spacemit_i2c_write_reg(spacemit_i2c, REG_SR, SR_SSD);
+       } else if (status & SR_IRF) { /* master write to us */
+               spacemit_i2c_write_reg(spacemit_i2c, REG_SR, SR_IRF);
+
+               value = spacemit_i2c_read_reg(spacemit_i2c, REG_DBR);
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, CR_TB | spacemit_i2c_read_reg(spacemit_i2c, REG_CR));
+
+               i2c_slave_event(spacemit_i2c->slave, I2C_SLAVE_WRITE_RECEIVED, &value);
+       } else if (status & SR_ITE) { /* ITE tx empty */
+               spacemit_i2c_write_reg(spacemit_i2c, REG_SR, SR_ITE);
+
+               i2c_slave_event(spacemit_i2c->slave, I2C_SLAVE_READ_PROCESSED, &value);
+               spacemit_i2c_write_reg(spacemit_i2c, REG_DBR, value & 0xff);
+
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, CR_TB | spacemit_i2c_read_reg(spacemit_i2c, REG_CR));
+       } else
+               dev_err(spacemit_i2c->dev,"unknown slave status 0x%x\n", status);
+
+       return;
+}
+#endif
+
+static irqreturn_t spacemit_i2c_int_handler(int irq, void *devid)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = devid;
+       u32 status, ctrl;
+       int ret = 0;
+
+       /* record i2c status */
+       status = spacemit_i2c_read_reg(spacemit_i2c, REG_SR);
+       spacemit_i2c->i2c_status = status;
+
+       /* check if a valid interrupt status */
+       if(!status) {
+               /* nothing need be done */
+               return IRQ_HANDLED;
+       }
+
+#ifdef CONFIG_I2C_SLAVE
+       if (spacemit_i2c->slave) {
+               spacemit_i2c_slave_handler(spacemit_i2c);
+               return IRQ_HANDLED;
+       }
+#endif
+
+       /* bus error, rx overrun, arbitration lost */
+       spacemit_i2c->i2c_err = status & (SR_BED | SR_RXOV | SR_ALD);
+
+       /* clear interrupt status bits[31:18]*/
+       spacemit_i2c_clear_int_status(spacemit_i2c, status);
+
+       /* i2c error happens */
+       if (unlikely(spacemit_i2c->i2c_err))
+               goto err_out;
+
+       /* process interrupt mode */
+       if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT))
+               ret = spacemit_i2c_byte_xfer(spacemit_i2c);
+
+err_out:
+       /*
+        * send transaction complete signal:
+        * error happens, detect master stop
+        */
+       if (likely(spacemit_i2c->i2c_err || (ret < 0) || (status & SR_MSD))) {
+               /*
+                * Here the transaction is already done, we don't need any
+                * other interrupt signals from now, in case any interrupt
+                * happens before spacemit_i2c_xfer to disable irq and i2c unit,
+                * we mask all the interrupt signals and clear the interrupt
+                * status.
+               */
+               ctrl = spacemit_i2c_read_reg(spacemit_i2c, REG_CR);
+               ctrl &= ~SPACEMIT_I2C_INT_CTRL_MASK;
+               spacemit_i2c_write_reg(spacemit_i2c, REG_CR, ctrl);
+
+               spacemit_i2c_clear_int_status(spacemit_i2c, SPACEMIT_I2C_INT_STATUS_MASK);
+
+               complete(&spacemit_i2c->complete);
+       }
+
+       return IRQ_HANDLED;
+}
+
+static void spacemit_i2c_choose_xfer_mode(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       unsigned long timeout;
+       int idx = 0, cnt = 0, freq;
+       bool block = false;
+
+       /* scan msgs */
+       if (spacemit_i2c->high_mode)
+               cnt++;
+       spacemit_i2c->rx_total = 0;
+       while (idx < spacemit_i2c->num) {
+               cnt += (spacemit_i2c->msgs + idx)->len + 1;
+               if ((spacemit_i2c->msgs + idx)->flags & I2C_M_RD)
+                       spacemit_i2c->rx_total += (spacemit_i2c->msgs + idx)->len;
+
+               /*
+                * Some SMBus transactions require that
+                * we receive the transacttion length as the first read byte.
+                * force to use I2C_MODE_INTERRUPT
+                */
+               if ((spacemit_i2c->msgs + idx)->flags & I2C_M_RECV_LEN) {
+                       block = true;
+                       cnt += I2C_SMBUS_BLOCK_MAX + 2;
+               }
+               idx++;
+       }
+
+       if (likely(spacemit_i2c->dma_disable) || block) {
+               spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_INTERRUPT;
+
+#ifdef CONFIG_DEBUG_FS
+       } else if (unlikely(spacemit_i2c->dbgfs_mode != SPACEMIT_I2C_MODE_INVALID)) {
+               spacemit_i2c->xfer_mode = spacemit_i2c->dbgfs_mode;
+               if (cnt > SPACEMIT_I2C_TX_FIFO_DEPTH &&
+                               spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_FIFO)
+                       spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_DMA;
+
+               /* flush fifo buffer */
+               spacemit_i2c_flush_fifo_buffer(spacemit_i2c);
+#endif
+       } else {
+               if (likely(cnt <= SPACEMIT_I2C_TX_FIFO_DEPTH))
+                       spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_FIFO;
+               else
+                       spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_DMA;
+
+               /* flush fifo buffer */
+               spacemit_i2c_flush_fifo_buffer(spacemit_i2c);
+       }
+
+       /*
+        * if total message length is too large to over the allocated MDA
+        * total buf length, use interrupt mode. This may happens in the
+        * syzkaller test.
+        */
+       if (unlikely(cnt > (SPACEMIT_I2C_MAX_MSG_LEN * SPACEMIT_I2C_SCATTERLIST_SIZE) ||
+               spacemit_i2c->rx_total > SPACEMIT_I2C_DMA_RX_BUF_LEN))
+               spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_INTERRUPT;
+
+       /* calculate timeout */
+       if (likely(spacemit_i2c->high_mode))
+               freq = 1500000;
+       else if (likely(spacemit_i2c->fast_mode))
+               freq = 400000;
+       else
+               freq = 100000;
+
+       timeout = cnt * 9 * USEC_PER_SEC / freq;
+
+       if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT ||
+               spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_PIO))
+               timeout += (cnt - 1) * 220;
+
+       if (spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT)
+               spacemit_i2c->timeout = usecs_to_jiffies(timeout + 500000);
+       else
+               spacemit_i2c->timeout = usecs_to_jiffies(timeout + 100000);
+}
+
+static void spacemit_i2c_init_xfer_params(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       /* initialize transfer parameters */
+       spacemit_i2c->msg_idx = 0;
+       spacemit_i2c->cur_msg = spacemit_i2c->msgs;
+       spacemit_i2c->msg_buf = spacemit_i2c->cur_msg->buf;
+       spacemit_i2c->rx_cnt = 0;
+       spacemit_i2c->tx_cnt = 0;
+       spacemit_i2c->i2c_err = 0;
+       spacemit_i2c->i2c_status = 0;
+       spacemit_i2c->phase = SPACEMIT_I2C_XFER_IDLE;
+
+       /* only send master code once for high speed mode */
+       spacemit_i2c->is_xfer_start = true;
+}
+
+static int spacemit_i2c_pio_xfer(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int ret = 0, xfer_try = 0;
+       u32 status;
+       signed long timeout;
+
+xfer_retry:
+       /* calculate timeout */
+       spacemit_i2c_choose_xfer_mode(spacemit_i2c);
+       spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_PIO;
+       timeout = jiffies_to_usecs(spacemit_i2c->timeout);
+
+       if (!spacemit_i2c->clk_always_on)
+               clk_enable(spacemit_i2c->clk);
+
+       spacemit_i2c_controller_reset(spacemit_i2c);
+       udelay(2);
+
+       spacemit_i2c_unit_init(spacemit_i2c);
+
+       spacemit_i2c_clear_int_status(spacemit_i2c, SPACEMIT_I2C_INT_STATUS_MASK);
+
+       spacemit_i2c_init_xfer_params(spacemit_i2c);
+
+       spacemit_i2c_mark_rw_flag(spacemit_i2c);
+
+       spacemit_i2c_enable(spacemit_i2c);
+
+       ret = spacemit_i2c_byte_xfer(spacemit_i2c);
+       if (unlikely(ret < 0)) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       while (spacemit_i2c->num > 0 && timeout > 0) {
+               status = spacemit_i2c_read_reg(spacemit_i2c, REG_SR);
+               spacemit_i2c_clear_int_status(spacemit_i2c, status);
+               spacemit_i2c->i2c_status = status;
+
+               /* bus error, arbitration lost */
+               spacemit_i2c->i2c_err = status & (SR_BED | SR_ALD);
+               if (unlikely(spacemit_i2c->i2c_err)) {
+                       ret = -1;
+                       break;
+               }
+
+               /* receive full */
+               if (likely(status & SR_IRF)) {
+                       ret = spacemit_i2c_byte_xfer(spacemit_i2c);
+                       if (unlikely(ret < 0))
+                               break;
+               }
+
+               /* transmit empty */
+               if (likely(status & SR_ITE)) {
+                       ret = spacemit_i2c_byte_xfer(spacemit_i2c);
+                       if (unlikely(ret < 0))
+                               break;
+               }
+
+               /* transaction done */
+               if (likely(status & SR_MSD))
+                       break;
+
+               udelay(10);
+               timeout -= 10;
+       }
+
+       spacemit_i2c_disable(spacemit_i2c);
+
+       if (!spacemit_i2c->clk_always_on)
+               clk_disable(spacemit_i2c->clk);
+
+       if (unlikely(timeout <= 0)) {
+               dev_alert(spacemit_i2c->dev, "i2c pio transfer timeout\n");
+               spacemit_i2c_print_msg_info(spacemit_i2c);
+               spacemit_i2c_bus_reset(spacemit_i2c);
+               udelay(100);
+               ret = -ETIMEDOUT;
+               goto out;
+       }
+
+       /* process i2c error */
+       if (unlikely(spacemit_i2c->i2c_err)) {
+               dev_dbg(spacemit_i2c->dev, "i2c pio error status: 0x%08x\n",
+                       spacemit_i2c->i2c_status);
+               spacemit_i2c_print_msg_info(spacemit_i2c);
+
+               /* try transfer again */
+               if (spacemit_i2c->i2c_err & SR_ALD)
+                       ret = -EAGAIN;
+               else
+                       ret = (spacemit_i2c->i2c_status & SR_ACKNAK) ? -ENXIO : -EIO;
+       }
+
+out:
+       xfer_try++;
+       /* retry i2c transfer 3 times for timeout and bus busy */
+       if (unlikely((ret == -ETIMEDOUT || ret == -EAGAIN) &&
+               xfer_try <= spacemit_i2c->drv_retries)) {
+               dev_alert(spacemit_i2c->dev, "i2c pio retry %d, ret %d err 0x%x\n",
+                               xfer_try, ret, spacemit_i2c->i2c_err);
+               udelay(150);
+               ret = 0;
+               goto xfer_retry;
+       }
+
+       return ret < 0 ? ret : spacemit_i2c->num;
+}
+
+static bool spacemit_i2c_restart_notify = false;
+
+static int
+spacemit_i2c_notifier_call(struct notifier_block *nb, unsigned long action, void *data)
+{
+       spacemit_i2c_restart_notify = true;
+       return 0;
+}
+
+static struct notifier_block spacemit_i2c_sys_nb = {
+       .notifier_call  = spacemit_i2c_notifier_call,
+       .priority   = 0,
+};
+
+static int
+spacemit_i2c_xfer(struct i2c_adapter *adapt, struct i2c_msg msgs[], int num)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = i2c_get_adapdata(adapt);
+       int ret = 0, xfer_try = 0;
+       unsigned long time_left;
+       bool clk_directly = false;
+
+#ifdef CONFIG_I2C_SLAVE
+       if (spacemit_i2c->slave) {
+               dev_err(spacemit_i2c->dev, "working as slave mode here\n");
+               return -EBUSY;
+       }
+#endif
+
+       /*
+        * at the end of system power off sequence, system will send
+        * software power down command to pmic via i2c interface
+        * with local irq disabled, so just enter PIO mode at once
+       */
+       if (unlikely(irqs_disabled()
+#ifdef CONFIG_DEBUG_FS
+               || spacemit_i2c->dbgfs_mode == SPACEMIT_I2C_MODE_PIO
+#endif
+               )) {
+
+               if(!spacemit_i2c_restart_notify)
+                       dev_warn(spacemit_i2c->dev, "%s: i2c transfer called with irq off!\n", __func__);
+
+               spacemit_i2c->msgs = msgs;
+               spacemit_i2c->num = num;
+
+               return spacemit_i2c_pio_xfer(spacemit_i2c);
+       }
+
+       mutex_lock(&spacemit_i2c->mtx);
+       spacemit_i2c->msgs = msgs;
+       spacemit_i2c->num = num;
+
+       if (spacemit_i2c->shutdown) {
+               mutex_unlock(&spacemit_i2c->mtx);
+               return -ENXIO;
+       }
+
+       if (!spacemit_i2c->clk_always_on) {
+               ret = pm_runtime_get_sync(spacemit_i2c->dev);
+               if (unlikely(ret < 0)) {
+                       /*
+                        * during system suspend_late to system resume_early stage,
+                        * if PM runtime is suspended, we will get -EACCES return
+                        * value, so we need to enable clock directly, and disable after
+                        * i2c transfer is finished, if PM runtime is active, it will
+                        * work normally. During this stage, pmic onkey ISR that
+                        * invoked in an irq thread may use i2c interface if we have
+                        * onkey press action
+                        */
+                       if (likely(ret == -EACCES)) {
+                               clk_directly = true;
+                               clk_enable(spacemit_i2c->clk);
+                       } else {
+                               dev_err(spacemit_i2c->dev, "pm runtime sync error: %d\n",
+                                       ret);
+                               goto err_runtime;
+                       }
+               }
+       }
+
+xfer_retry:
+       /* if unit keeps the last control status, don't need to do reset */
+       if (unlikely(spacemit_i2c_read_reg(spacemit_i2c, REG_CR) != spacemit_i2c->i2c_ctrl_reg_value))
+               /* i2c controller & bus reset */
+               spacemit_i2c_reset(spacemit_i2c);
+
+       /* choose transfer mode */
+       spacemit_i2c_choose_xfer_mode(spacemit_i2c);
+
+       /* i2c unit init */
+       spacemit_i2c_unit_init(spacemit_i2c);
+
+       /* clear all interrupt status */
+       spacemit_i2c_clear_int_status(spacemit_i2c, SPACEMIT_I2C_INT_STATUS_MASK);
+
+       spacemit_i2c_init_xfer_params(spacemit_i2c);
+
+       spacemit_i2c_mark_rw_flag(spacemit_i2c);
+
+       reinit_completion(&spacemit_i2c->complete);
+
+       spacemit_i2c_enable(spacemit_i2c);
+       enable_irq(spacemit_i2c->irq);
+
+       /* i2c wait for bus busy */
+       ret = spacemit_i2c_recover_bus_busy(spacemit_i2c);
+       if (unlikely(ret))
+               goto err_recover;
+
+       /* i2c msg transmit */
+       if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT))
+               ret = spacemit_i2c_byte_xfer(spacemit_i2c);
+       else if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_FIFO))
+               ret = spacemit_i2c_fifo_xfer(spacemit_i2c);
+       else
+               ret = spacemit_i2c_dma_xfer(spacemit_i2c);
+
+       if (unlikely(ret < 0)) {
+               dev_dbg(spacemit_i2c->dev, "i2c transfer error\n");
+               /* timeout error should not be overrided, and the transfer
+                * error will be confirmed by err handle function latter,
+                * the reset should be invalid argument error. */
+               if (ret != -ETIMEDOUT)
+                       ret = -EINVAL;
+               goto err_xfer;
+       }
+
+       if (likely(spacemit_i2c->xfer_mode == SPACEMIT_I2C_MODE_INTERRUPT)) {
+               time_left = wait_for_completion_timeout(&spacemit_i2c->complete,
+                                                       spacemit_i2c->timeout);
+               if (unlikely(time_left == 0)) {
+                       dev_alert(spacemit_i2c->dev, "msg completion timeout\n");
+                       spacemit_i2c_bus_reset(spacemit_i2c);
+                       spacemit_i2c_reset(spacemit_i2c);
+                       ret = -ETIMEDOUT;
+                       goto err_xfer;
+               }
+       }
+
+err_xfer:
+       if (likely(!ret))
+               spacemit_i2c_check_bus_release(spacemit_i2c);
+
+err_recover:
+       disable_irq(spacemit_i2c->irq);
+
+       /* disable spacemit i2c */
+       spacemit_i2c_disable(spacemit_i2c);
+
+       /* print more message info when error or timeout happens */
+       if (unlikely(ret < 0 || spacemit_i2c->i2c_err))
+               spacemit_i2c_print_msg_info(spacemit_i2c);
+
+       /* process i2c error */
+       if (unlikely(spacemit_i2c->i2c_err))
+               ret = spacemit_i2c_handle_err(spacemit_i2c);
+
+       xfer_try++;
+       /* retry i2c transfer 3 times for timeout and bus busy */
+       if (unlikely((ret == -ETIMEDOUT || ret == -EAGAIN) &&
+               xfer_try <= spacemit_i2c->drv_retries)) {
+               dev_alert(spacemit_i2c->dev, "i2c transfer retry %d, ret %d mode %d err 0x%x\n",
+                               xfer_try, ret, spacemit_i2c->xfer_mode, spacemit_i2c->i2c_err);
+               usleep_range(150, 200);
+               ret = 0;
+               goto xfer_retry;
+       }
+
+err_runtime:
+       if (unlikely(clk_directly)) {
+               /* if clock is enabled directly, here disable it */
+               clk_disable(spacemit_i2c->clk);
+       }
+
+       if (!spacemit_i2c->clk_always_on) {
+               pm_runtime_mark_last_busy(spacemit_i2c->dev);
+               pm_runtime_put_autosuspend(spacemit_i2c->dev);
+       }
+
+       mutex_unlock(&spacemit_i2c->mtx);
+
+       return ret < 0 ? ret : num;
+}
+
+static int spacemit_i2c_prepare_dma(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       int ret = 0;
+       struct dma_slave_config *rx_cfg = &spacemit_i2c->rx_dma_cfg;
+       struct dma_slave_config *tx_cfg = &spacemit_i2c->tx_dma_cfg;
+
+       if (spacemit_i2c->dma_disable)
+               return 0;
+
+       /* request dma channels */
+       spacemit_i2c->rx_dma = dma_request_slave_channel(spacemit_i2c->dev, "rx");
+       if (IS_ERR_OR_NULL(spacemit_i2c->rx_dma)) {
+               ret = -1;
+               dev_err(spacemit_i2c->dev, "failed to request rx dma channel\n");
+               return ret;
+       }
+
+       spacemit_i2c->tx_dma = dma_request_slave_channel(spacemit_i2c->dev, "tx");
+       if (IS_ERR_OR_NULL(spacemit_i2c->tx_dma)) {
+               ret = -1;
+               dev_err(spacemit_i2c->dev, "failed to request tx dma channel\n");
+               goto err_rxch;
+       }
+
+       rx_cfg->direction = DMA_DEV_TO_MEM;
+       rx_cfg->src_addr = spacemit_i2c->resrc.start + REG_RFIFO;
+       rx_cfg->device_fc = true;
+       rx_cfg->src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
+       rx_cfg->src_maxburst = SPACEMIT_I2C_RX_FIFO_DEPTH * 1;
+
+       ret = dmaengine_slave_config(spacemit_i2c->rx_dma, rx_cfg);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to config rx channel\n");
+               goto err_txch;
+       }
+
+       tx_cfg->direction = DMA_MEM_TO_DEV;
+       tx_cfg->dst_addr = spacemit_i2c->resrc.start + REG_WFIFO;
+       tx_cfg->device_fc = true;
+       tx_cfg->dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+       tx_cfg->dst_maxburst = SPACEMIT_I2C_TX_FIFO_DEPTH * 1;
+
+       ret = dmaengine_slave_config(spacemit_i2c->tx_dma, tx_cfg);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to config tx channel\n");
+               goto err_txch;
+       }
+
+       /* allocate scatter lists */
+       spacemit_i2c->rx_sg = devm_kmalloc(spacemit_i2c->dev,
+                       sizeof(*spacemit_i2c->rx_sg) * SPACEMIT_I2C_SCATTERLIST_SIZE,
+                       GFP_KERNEL);
+       if (!spacemit_i2c->rx_sg) {
+               ret = -ENOMEM;
+               dev_err(spacemit_i2c->dev,
+                       "failed to allocate memory for rx scatterlist\n");
+               goto err_txch;
+       }
+       sg_init_table(spacemit_i2c->rx_sg, SPACEMIT_I2C_SCATTERLIST_SIZE);
+
+       spacemit_i2c->tx_sg = devm_kmalloc(spacemit_i2c->dev,
+                               sizeof(*spacemit_i2c->tx_sg),
+                               GFP_KERNEL);
+       if (!spacemit_i2c->tx_sg) {
+               ret = -ENOMEM;
+               dev_err(spacemit_i2c->dev,
+                       "failed to allocate memory for tx scatterlist\n");
+               goto err_txch;
+       }
+       sg_init_table(spacemit_i2c->tx_sg, 1);
+
+       /* allocate memory for tx */
+       spacemit_i2c->tx_dma_buf = devm_kzalloc(spacemit_i2c->dev,
+                       sizeof(spacemit_i2c->tx_dma_buf[0]) * SPACEMIT_I2C_DMA_TX_BUF_LEN,
+                       GFP_KERNEL);
+       if (!spacemit_i2c->tx_dma_buf) {
+               ret = -ENOMEM;
+               dev_err(spacemit_i2c->dev,
+                       "failed to allocate memory for tx dma buffer\n");
+               goto err_txch;
+       }
+
+       /* allocate memory for rx */
+       spacemit_i2c->rx_dma_buf = devm_kzalloc(spacemit_i2c->dev,
+                       sizeof(spacemit_i2c->rx_dma_buf[0]) * SPACEMIT_I2C_DMA_RX_BUF_LEN,
+                       GFP_KERNEL);
+       if (!spacemit_i2c->rx_dma_buf) {
+               ret = -ENOMEM;
+               dev_err(spacemit_i2c->dev,
+                       "failed to allocate memory for rx dma buffer\n");
+               goto err_txch;
+       }
+
+       /*
+        * DMA controller can access all 4G or higher 4G address space, set
+        * dma mask will avoid to use swiotlb, that will improve performance
+        * and also avoid panic if swiotlb is not initialized.
+        * Besides, device's coherent_dma_mask is set as DMA_BIT_MASK(32)
+        * in initialization, see of_dma_configure().
+        */
+#ifdef CONFIG_ARCH_DMA_ADDR_T_64BIT
+       dma_set_mask(spacemit_i2c->dev, DMA_BIT_MASK(64));
+#else
+       dma_set_mask(spacemit_i2c->dev, spacemit_i2c->dev->coherent_dma_mask);
+#endif
+
+       return 0;
+
+err_txch:
+       dma_release_channel(spacemit_i2c->tx_dma);
+err_rxch:
+       dma_release_channel(spacemit_i2c->rx_dma);
+       return ret;
+}
+
+static int spacemit_i2c_release_dma(struct spacemit_i2c_dev *spacemit_i2c)
+{
+       if (spacemit_i2c->dma_disable)
+               return 0;
+
+       if (!IS_ERR_OR_NULL(spacemit_i2c->rx_dma))
+               dma_release_channel(spacemit_i2c->rx_dma);
+
+       if (!IS_ERR_OR_NULL(spacemit_i2c->tx_dma))
+               dma_release_channel(spacemit_i2c->tx_dma);
+
+       return 0;
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static ssize_t
+spacemit_i2c_dbgfs_read(struct file *filp, char __user *user_buf,
+       size_t size, loff_t *ppos)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = filp->private_data;
+       char buf[64];
+       int ret, n, copy;
+
+       n = min(sizeof(buf) - 1, size);
+       switch (spacemit_i2c->xfer_mode) {
+       case SPACEMIT_I2C_MODE_INTERRUPT:
+               copy = sprintf(buf, "%s: interrupt mode\n",
+                               spacemit_i2c->dbgfs_name);
+               break;
+       case SPACEMIT_I2C_MODE_FIFO:
+               copy = sprintf(buf, "%s: fifo mode\n", spacemit_i2c->dbgfs_name);
+               break;
+       case SPACEMIT_I2C_MODE_DMA:
+               copy = sprintf(buf, "%s: dma mode\n", spacemit_i2c->dbgfs_name);
+               break;
+       case SPACEMIT_I2C_MODE_PIO:
+               copy = sprintf(buf, "%s: pio mode\n", spacemit_i2c->dbgfs_name);
+               break;
+       default:
+               copy = sprintf(buf, "%s: mode is invalid\n",
+                               spacemit_i2c->dbgfs_name);
+               break;
+       }
+
+       copy  = min(n, copy);
+       ret = simple_read_from_buffer(user_buf, size, ppos, buf, copy);
+
+       return ret;
+}
+
+static ssize_t
+spacemit_i2c_dbgfs_write(struct file *filp, const char __user *user_buf,
+       size_t size, loff_t *ppos)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = filp->private_data;
+       char buf[32];
+       int buf_size, i = 0;
+
+       buf_size = min(size, sizeof(buf) - 1);
+       if (copy_from_user(buf, user_buf, buf_size))
+               return -EFAULT;
+
+       *(buf + buf_size) = '\0';
+       while (*(buf + i) != '\n' && *(buf + i) != '\0')
+               i++;
+       *(buf + i) = '\0';
+
+       i = 0;
+       while (*(buf + i) == ' ')
+               i++;
+
+       if (!strncmp(buf + i, "pio", 3)) {
+               spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_PIO;
+       } else if (!strncmp(buf + i, "interrupt", 9)) {
+               spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_INTERRUPT;
+       } else if (!strncmp(buf + i, "fifo", 4)) {
+               if (!spacemit_i2c->dma_disable)
+                       spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_FIFO;
+               else
+                       goto err_out;
+       } else if (!strncmp(buf + i, "dma", 3)) {
+               if (!spacemit_i2c->dma_disable)
+                       spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_DMA;
+               else
+                       goto err_out;
+       } else {
+               if (!spacemit_i2c->dma_disable)
+                       dev_err(spacemit_i2c->dev,
+                               "only accept: interrupt, fifo, dma, pio\n");
+               else
+                       goto err_out;
+       }
+
+       return size;
+
+err_out:
+       spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_INTERRUPT;
+       dev_err(spacemit_i2c->dev,
+               "dma is disabled, only accept: interrupt, pio\n");
+       return size;
+}
+
+static const struct file_operations spacemit_i2c_dbgfs_ops = {
+       .open   = simple_open,
+       .read   = spacemit_i2c_dbgfs_read,
+       .write  = spacemit_i2c_dbgfs_write,
+};
+#endif /* CONFIG_DEBUG_FS */
+
+#ifdef CONFIG_PM_SLEEP
+/** static int spacemit_i2c_suspend(struct device *dev)
+ * {
+ *     struct spacemit_i2c_dev *spacemit_i2c = dev_get_drvdata(dev);
+ *
+ *     dev_dbg(spacemit_i2c->dev, "system suspend\n");
+ *
+ *     if (spacemit_i2c->clk_always_on)
+ *             return 0;
+ *
+ *     // grab mutex to make sure the i2c transaction is over
+ *     mutex_lock(&spacemit_i2c->mtx);
+ *     if (!pm_runtime_status_suspended(dev)) {
+ *              // sync runtime pm and system pm states:
+ *              // prevent runtime pm suspend callback from being re-invoked
+ *             pm_runtime_disable(dev);
+ *             pm_runtime_set_suspended(dev);
+ *             pm_runtime_enable(dev);
+ *     }
+ *     mutex_unlock(&spacemit_i2c->mtx);
+ *
+ *     return 0;
+ * }
+ *
+ * static int spacemit_i2c_resume(struct device *dev)
+ * {
+ *     struct spacemit_i2c_dev *spacemit_i2c = dev_get_drvdata(dev);
+ *
+ *     dev_dbg(spacemit_i2c->dev, "system resume\n");
+ *
+ *     return 0;
+ *}
+ */
+#endif /* CONFIG_PM_SLEEP */
+
+/**
+ * static const struct dev_pm_ops spacemit_i2c_pm_ops = {
+ *     SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(spacemit_i2c_suspend,
+ *                     spacemit_i2c_resume)
+ *};
+ */
+
+static u32 spacemit_i2c_func(struct i2c_adapter *adap)
+{
+#ifdef CONFIG_I2C_SLAVE
+       return I2C_FUNC_I2C | I2C_FUNC_SLAVE |
+               (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+#else
+       return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
+#endif
+}
+
+#ifdef CONFIG_I2C_SLAVE
+static int spacemit_i2c_reg_slave(struct i2c_client *slave)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = i2c_get_adapdata(slave->adapter);
+       int ret = 0;
+
+       if (spacemit_i2c->slave)
+               return -EBUSY;
+
+       if (slave->flags & I2C_CLIENT_TEN)
+               return -EAFNOSUPPORT;
+
+       if(!slave->addr) {
+               dev_err(spacemit_i2c->dev, "have no slave address\n");
+               return -EAFNOSUPPORT;
+       }
+
+       /* Keep device active for slave address detection logic */
+       if (!spacemit_i2c->clk_always_on) {
+               ret = pm_runtime_get_sync(spacemit_i2c->dev);
+               if(unlikely(ret < 0)) {
+                       return ret;
+               }
+       }
+
+       spacemit_i2c->slave = slave;
+
+       spacemit_i2c_write_reg(spacemit_i2c, REG_SAR, slave->addr);
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, SPACEMIT_I2C_SLAVE_CRINIT);
+       enable_irq(spacemit_i2c->irq);
+
+       return 0;
+}
+
+static int spacemit_i2c_unreg_slave(struct i2c_client *slave)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = i2c_get_adapdata(slave->adapter);
+
+       WARN_ON(!spacemit_i2c->slave);
+
+       disable_irq(spacemit_i2c->irq);
+
+       spacemit_i2c_write_reg(spacemit_i2c, REG_CR, 0);
+       /* clear slave address */
+       spacemit_i2c_write_reg(spacemit_i2c, REG_SAR, 0);
+
+       if (!spacemit_i2c->clk_always_on)
+               pm_runtime_put(spacemit_i2c->dev);
+
+       spacemit_i2c->slave = NULL;
+
+       return 0;
+}
+#endif
+
+static const struct i2c_algorithm spacemit_i2c_algrtm = {
+       .master_xfer    = spacemit_i2c_xfer,
+       .functionality  = spacemit_i2c_func,
+#ifdef CONFIG_I2C_SLAVE
+       .reg_slave      = spacemit_i2c_reg_slave,
+       .unreg_slave    = spacemit_i2c_unreg_slave,
+#endif
+};
+
+/* i2c message limitation for DMA mode */
+static struct i2c_adapter_quirks spacemit_i2c_quirks = {
+       .max_num_msgs   = SPACEMIT_I2C_SCATTERLIST_SIZE,
+       .max_write_len  = SPACEMIT_I2C_MAX_MSG_LEN,
+       .max_read_len   = SPACEMIT_I2C_MAX_MSG_LEN,
+};
+
+static int
+spacemit_i2c_parse_dt(struct platform_device *pdev, struct spacemit_i2c_dev *spacemit_i2c)
+{
+       struct device_node *dnode = pdev->dev.of_node;
+       int ret;
+
+       /* enable fast speed mode */
+       spacemit_i2c->fast_mode = of_property_read_bool(dnode, "spacemit,i2c-fast-mode");
+
+       /* enable high speed mode */
+       spacemit_i2c->high_mode = of_property_read_bool(dnode, "spacemit,i2c-high-mode");
+       if (spacemit_i2c->high_mode) {
+               /* get master code for high speed mode */
+               ret = of_property_read_u8(dnode, "spacemit,i2c-master-code",
+                               &spacemit_i2c->master_code);
+               if (ret) {
+                       spacemit_i2c->master_code = 0x0e;
+                       dev_warn(spacemit_i2c->dev,
+                       "failed to get i2c master code, use default: 0x0e\n");
+               }
+
+               ret = of_property_read_u32(dnode, "spacemit,i2c-clk-rate",
+                               &spacemit_i2c->clk_rate);
+               if (ret) {
+                       dev_err(spacemit_i2c->dev,
+                               "failed to get i2c high mode clock rate\n");
+                       return ret;
+               }
+       }
+
+       ret = of_property_read_u32(dnode, "spacemit,i2c-lcr", &spacemit_i2c->i2c_lcr);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to get i2c lcr\n");
+               return ret;
+       }
+
+       ret = of_property_read_u32(dnode, "spacemit,i2c-wcr", &spacemit_i2c->i2c_wcr);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to get i2c wcr\n");
+               return ret;
+       }
+
+       /*
+        * adapter device id:
+        * assigned in dt node or alias name, or automatically allocated
+        * in i2c_add_numbered_adapter()
+        */
+       ret = of_property_read_u32(dnode, "spacemit,adapter-id", &pdev->id);
+       if (ret)
+               pdev->id = -1;
+
+       /* disable DMA transfer mode */
+       spacemit_i2c->dma_disable = of_property_read_bool(dnode, "spacemit,dma-disable");
+
+       /* default: interrupt mode */
+       if (spacemit_i2c->dma_disable)
+               spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_INTERRUPT;
+       else
+               spacemit_i2c->xfer_mode = SPACEMIT_I2C_MODE_DMA;
+
+       /* true: the clock will always on and not use runtime mechanism */
+       spacemit_i2c->clk_always_on = of_property_read_bool(dnode, "spacemit,clk-always-on");
+
+       /* apb clock: 26MHz or 52MHz */
+       ret = of_property_read_u32(dnode, "spacemit,apb_clock", &spacemit_i2c->apb_clock);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to get apb clock\n");
+               return ret;
+       } else if ((spacemit_i2c->apb_clock != SPACEMIT_I2C_APB_CLOCK_26M) &&
+                       (spacemit_i2c->apb_clock != SPACEMIT_I2C_APB_CLOCK_52M)) {
+               dev_err(spacemit_i2c->dev, "the apb clock should be 26M or 52M\n");
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static int spacemit_i2c_probe(struct platform_device *pdev)
+{
+       struct spacemit_i2c_dev *spacemit_i2c;
+       struct device_node *dnode = pdev->dev.of_node;
+       int ret = 0;
+
+       /* allocate memory */
+       spacemit_i2c = devm_kzalloc(&pdev->dev,
+                               sizeof(struct spacemit_i2c_dev),
+                               GFP_KERNEL);
+       if (!spacemit_i2c) {
+               ret =  -ENOMEM;
+               goto err_out;
+       }
+
+       spacemit_i2c->dev = &pdev->dev;
+       platform_set_drvdata(pdev, spacemit_i2c);
+       mutex_init(&spacemit_i2c->mtx);
+
+       spacemit_i2c->resets = devm_reset_control_get_optional(&pdev->dev, NULL);
+       if(IS_ERR(spacemit_i2c->resets)) {
+               dev_err(&pdev->dev, "failed to get resets\n");
+               goto err_out;
+       }
+       /* reset the i2c controller */
+       reset_control_assert(spacemit_i2c->resets);
+       udelay(200);
+       reset_control_deassert(spacemit_i2c->resets);
+
+       ret = spacemit_i2c_parse_dt(pdev, spacemit_i2c);
+       if (ret)
+               goto err_out;
+
+       ret = of_address_to_resource(dnode, 0, &spacemit_i2c->resrc);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get resource\n");
+               ret =  -ENODEV;
+               goto err_out;
+       }
+
+       spacemit_i2c->mapbase = devm_ioremap_resource(spacemit_i2c->dev, &spacemit_i2c->resrc);
+       if (IS_ERR(spacemit_i2c->mapbase)) {
+               dev_err(&pdev->dev, "failed to do ioremap\n");
+               ret =  PTR_ERR(spacemit_i2c->mapbase);
+               goto err_out;
+       }
+
+       spacemit_i2c->irq = platform_get_irq(pdev, 0);
+       if (spacemit_i2c->irq < 0) {
+               dev_err(spacemit_i2c->dev, "failed to get irq resource\n");
+               ret = spacemit_i2c->irq;
+               goto err_out;
+       }
+
+       ret = devm_request_irq(spacemit_i2c->dev, spacemit_i2c->irq, spacemit_i2c_int_handler,
+                       IRQF_NO_SUSPEND | IRQF_ONESHOT,
+                       dev_name(spacemit_i2c->dev), spacemit_i2c);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to request irq\n");
+               goto err_out;
+       }
+       disable_irq(spacemit_i2c->irq);
+
+       ret = spacemit_i2c_prepare_dma(spacemit_i2c);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to request dma channels\n");
+               goto err_out;
+       }
+
+       spacemit_i2c->clk = devm_clk_get(spacemit_i2c->dev, NULL);
+       if (IS_ERR(spacemit_i2c->clk)) {
+               dev_err(spacemit_i2c->dev, "failed to get clock\n");
+               ret = PTR_ERR(spacemit_i2c->clk);
+               goto err_dma;
+       }
+       clk_prepare_enable(spacemit_i2c->clk);
+
+       i2c_set_adapdata(&spacemit_i2c->adapt, spacemit_i2c);
+       spacemit_i2c->adapt.owner = THIS_MODULE;
+       spacemit_i2c->adapt.algo = &spacemit_i2c_algrtm;
+       spacemit_i2c->adapt.dev.parent = spacemit_i2c->dev;
+       spacemit_i2c->adapt.nr = pdev->id;
+       /* retries used by i2c framework: 3 times */
+       spacemit_i2c->adapt.retries = 3;
+       /*
+        * retries used by i2c driver: 3 times
+        * this is for the very low occasionally PMIC i2c access failure.
+        */
+       spacemit_i2c->drv_retries = 3;
+       spacemit_i2c->adapt.dev.of_node = dnode;
+       spacemit_i2c->adapt.algo_data = spacemit_i2c;
+       strlcpy(spacemit_i2c->adapt.name, "spacemit-i2c-adapter",
+               sizeof(spacemit_i2c->adapt.name));
+
+       if (!spacemit_i2c->dma_disable)
+               spacemit_i2c->adapt.quirks = &spacemit_i2c_quirks;
+
+       init_completion(&spacemit_i2c->complete);
+       spin_lock_init(&spacemit_i2c->fifo_lock);
+
+       if (!spacemit_i2c->clk_always_on) {
+               pm_runtime_set_autosuspend_delay(spacemit_i2c->dev, MSEC_PER_SEC);
+               pm_runtime_use_autosuspend(spacemit_i2c->dev);
+               pm_runtime_set_active(spacemit_i2c->dev);
+               pm_suspend_ignore_children(&pdev->dev, 1);
+               pm_runtime_enable(spacemit_i2c->dev);
+       } else
+               dev_dbg(spacemit_i2c->dev, "clock keeps always on\n");
+
+       spacemit_i2c->dbgfs_mode = SPACEMIT_I2C_MODE_INVALID;
+       spacemit_i2c->shutdown = false;
+       ret = i2c_add_numbered_adapter(&spacemit_i2c->adapt);
+       if (ret) {
+               dev_err(spacemit_i2c->dev, "failed to add i2c adapter\n");
+               goto err_clk;
+       }
+
+#ifdef CONFIG_DEBUG_FS
+       snprintf(spacemit_i2c->dbgfs_name, sizeof(spacemit_i2c->dbgfs_name),
+                       "spacemit-i2c-%d", spacemit_i2c->adapt.nr);
+       spacemit_i2c->dbgfs = debugfs_create_file(spacemit_i2c->dbgfs_name, 0644,
+                                       NULL, spacemit_i2c, &spacemit_i2c_dbgfs_ops);
+       if (!spacemit_i2c->dbgfs) {
+               dev_err(spacemit_i2c->dev, "failed to create debugfs\n");
+               ret = -ENOMEM;
+               goto err_adapt;
+       }
+#endif
+
+       dev_dbg(spacemit_i2c->dev, "driver probe success with dma %s\n",
+               spacemit_i2c->dma_disable ? "disabled" : "enabled");
+
+       return 0;
+
+#ifdef CONFIG_DEBUG_FS
+err_adapt:
+       i2c_del_adapter(&spacemit_i2c->adapt);
+#endif
+err_clk:
+       if (!spacemit_i2c->clk_always_on) {
+               pm_runtime_disable(spacemit_i2c->dev);
+               pm_runtime_set_suspended(spacemit_i2c->dev);
+       }
+       clk_disable_unprepare(spacemit_i2c->clk);
+err_dma:
+       spacemit_i2c_release_dma(spacemit_i2c);
+err_out:
+       return ret;
+}
+
+static int spacemit_i2c_remove(struct platform_device *pdev)
+{
+       struct spacemit_i2c_dev *spacemit_i2c = platform_get_drvdata(pdev);
+
+       if (!spacemit_i2c->clk_always_on) {
+               pm_runtime_disable(spacemit_i2c->dev);
+               pm_runtime_set_suspended(spacemit_i2c->dev);
+       }
+
+       debugfs_remove_recursive(spacemit_i2c->dbgfs);
+       i2c_del_adapter(&spacemit_i2c->adapt);
+
+       mutex_destroy(&spacemit_i2c->mtx);
+
+       reset_control_assert(spacemit_i2c->resets);
+
+       spacemit_i2c_release_dma(spacemit_i2c);
+
+       clk_disable_unprepare(spacemit_i2c->clk);
+
+       dev_dbg(spacemit_i2c->dev, "driver removed\n");
+       return 0;
+}
+
+static void spacemit_i2c_shutdown(struct platform_device *pdev)
+{
+       /**
+        * we should using i2c to communicate with pmic to shutdown the system
+        * so we should not shutdown i2c
+        */
+/**
+ *     struct spacemit_i2c_dev *spacemit_i2c = platform_get_drvdata(pdev);
+ *
+ *     mutex_lock(&spacemit_i2c->mtx);
+ *     spacemit_i2c->shutdown = true;
+ *     mutex_unlock(&spacemit_i2c->mtx);
+ */
+}
+
+static const struct of_device_id spacemit_i2c_dt_match[] = {
+       {
+               .compatible = "spacemit,k1x-i2c",
+       },
+       {}
+};
+
+MODULE_DEVICE_TABLE(of, spacemit_i2c_dt_match);
+
+static struct platform_driver spacemit_i2c_driver = {
+       .probe  = spacemit_i2c_probe,
+       .remove = spacemit_i2c_remove,
+       .shutdown = spacemit_i2c_shutdown,
+       .driver = {
+               .name           = "i2c-spacemit-k1x",
+               /* .pm             = &spacemit_i2c_pm_ops, */
+               .of_match_table = spacemit_i2c_dt_match,
+       },
+};
+
+static int __init spacemit_i2c_init(void)
+{
+       register_restart_handler(&spacemit_i2c_sys_nb);
+       return platform_driver_register(&spacemit_i2c_driver);
+}
+
+static void __exit spacemit_i2c_exit(void)
+{
+       platform_driver_unregister(&spacemit_i2c_driver);
+       unregister_restart_handler(&spacemit_i2c_sys_nb);
+}
+
+subsys_initcall(spacemit_i2c_init);
+module_exit(spacemit_i2c_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:i2c-spacemit-k1x");
diff --git a/drivers/i2c/busses/i2c-k1x.h b/drivers/i2c/busses/i2c-k1x.h
new file mode 100644 (file)
index 0000000..c426c6f
--- /dev/null
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Spacemit i2c driver header file
+ */
+
+#ifndef _I2C_SPACEMIT_K1X_H
+#define _I2C_SPACEMIT_K1X_H
+#include <linux/bitops.h>
+#include <linux/i2c.h>
+#include <linux/reset.h>
+#include <linux/i2c-dev.h>
+#include <linux/pm_qos.h>
+
+/* spacemit i2c registers */
+enum {
+       REG_CR          = 0x0,          /* Control Register */
+       REG_SR          = 0x4,          /* Status Register */
+       REG_SAR         = 0x8,          /* Slave Address Register */
+       REG_DBR         = 0xc,          /* Data Buffer Register */
+       REG_LCR         = 0x10,         /* Load Count Register */
+       REG_WCR         = 0x14,         /* Wait Count Register */
+       REG_RST_CYC     = 0x18,         /* Bus reset cycle counter */
+       REG_BMR         = 0x1c,         /* Bus monitor register */
+       REG_WFIFO       = 0x20,         /* Write FIFO Register */
+       REG_WFIFO_WPTR  = 0x24,         /* Write FIFO Write Pointer Register */
+       REG_WFIFO_RPTR  = 0x28,         /* Write FIFO Read Pointer Register */
+       REG_RFIFO       = 0x2c,         /* Read FIFO Register */
+       REG_RFIFO_WPTR  = 0x30,         /* Read FIFO Write Pointer Register */
+       REG_RFIFO_RPTR  = 0x34,         /* Read FIFO Read Pointer Register */
+};
+
+/* register REG_CR fields */
+enum {
+       CR_START        = BIT(0),       /* start bit */
+       CR_STOP         = BIT(1),       /* stop bit */
+       CR_ACKNAK       = BIT(2),       /* send ACK(0) or NAK(1) */
+       CR_TB           = BIT(3),       /* transfer byte bit */
+       CR_TXBEGIN      = BIT(4),       /* transaction begin */
+       CR_FIFOEN       = BIT(5),       /* enable FIFO mode */
+       CR_GPIOEN       = BIT(6),       /* enable GPIO mode for SCL in HS */
+       CR_DMAEN        = BIT(7),       /* enable DMA for TX and RX FIFOs */
+       CR_MODE_FAST    = BIT(8),       /* bus mode (master operation) */
+       CR_MODE_HIGH    = BIT(9),       /* bus mode (master operation) */
+       CR_UR           = BIT(10),      /* unit reset */
+       CR_RSTREQ       = BIT(11),      /* i2c bus reset request */
+       CR_MA           = BIT(12),      /* master abort */
+       CR_SCLE         = BIT(13),      /* master clock enable */
+       CR_IUE          = BIT(14),      /* unit enable */
+       CR_HS_STRETCH   = BIT(16),      /* I2C hs stretch */
+       CR_ALDIE        = BIT(18),      /* enable arbitration interrupt */
+       CR_DTEIE        = BIT(19),      /* enable tx interrupts */
+       CR_DRFIE        = BIT(20),      /* enable rx interrupts */
+       CR_GCD          = BIT(21),      /* general call disable */
+       CR_BEIE         = BIT(22),      /* enable bus error ints */
+       CR_SADIE        = BIT(23),      /* slave address detected int enable */
+       CR_SSDIE        = BIT(24),      /* slave STOP detected int enable */
+       CR_MSDIE        = BIT(25),      /* master STOP detected int enable */
+       CR_MSDE         = BIT(26),      /* master STOP detected enable */
+       CR_TXDONEIE     = BIT(27),      /* transaction done int enable */
+       CR_TXEIE        = BIT(28),      /* transmit FIFO empty int enable */
+       CR_RXHFIE       = BIT(29),      /* receive FIFO half-full int enable */
+       CR_RXFIE        = BIT(30),      /* receive FIFO full int enable */
+       CR_RXOVIE       = BIT(31),      /* receive FIFO overrun int enable */
+};
+
+/* register REG_SR fields */
+enum {
+       SR_RWM          = BIT(13),      /* read/write mode */
+       SR_ACKNAK       = BIT(14),      /* ACK/NACK status */
+       SR_UB           = BIT(15),      /* unit busy */
+       SR_IBB          = BIT(16),      /* i2c bus busy */
+       SR_EBB          = BIT(17),      /* early bus busy */
+       SR_ALD          = BIT(18),      /* arbitration loss detected */
+       SR_ITE          = BIT(19),      /* tx buffer empty */
+       SR_IRF          = BIT(20),      /* rx buffer full */
+       SR_GCAD         = BIT(21),      /* general call address detected */
+       SR_BED          = BIT(22),      /* bus error no ACK/NAK */
+       SR_SAD          = BIT(23),      /* slave address detected */
+       SR_SSD          = BIT(24),      /* slave stop detected */
+       SR_MSD          = BIT(26),      /* master stop detected */
+       SR_TXDONE       = BIT(27),      /* transaction done */
+       SR_TXE          = BIT(28),      /* tx FIFO empty */
+       SR_RXHF         = BIT(29),      /* rx FIFO half-full */
+       SR_RXF          = BIT(30),      /* rx FIFO full */
+       SR_RXOV         = BIT(31),      /* RX FIFO overrun */
+};
+
+/* register REG_LCR fields */
+enum {
+       LCR_SLV         = 0x000001FF,   /* SLV: bit[8:0] */
+       LCR_FLV         = 0x0003FE00,   /* FLV: bit[17:9] */
+       LCR_HLVH        = 0x07FC0000,   /* HLVH: bit[26:18] */
+       LCR_HLVL        = 0xF8000000,   /* HLVL: bit[31:27] */
+};
+
+/* register REG_WCR fields */
+enum {
+       WCR_COUNT       = 0x0000001F,   /* COUNT: bit[4:0] */
+       WCR_COUNT1      = 0x000003E0,   /* HS_COUNT1: bit[9:5] */
+       WCR_COUNT2      = 0x00007C00,   /* HS_COUNT2: bit[14:10] */
+};
+
+/* register REG_BMR fields */
+enum {
+       BMR_SDA         = BIT(0),       /* SDA line level */
+       BMR_SCL         = BIT(1),       /* SCL line level */
+};
+
+/* register REG_WFIFO fields */
+enum {
+       WFIFO_DATA_MSK          = 0x000000FF,   /* data: bit[7:0] */
+       WFIFO_CTRL_MSK          = 0x000003E0,   /* control: bit[11:8] */
+       WFIFO_CTRL_START        = BIT(8),       /* start bit */
+       WFIFO_CTRL_STOP         = BIT(9),       /* stop bit */
+       WFIFO_CTRL_ACKNAK       = BIT(10),      /* send ACK(0) or NAK(1) */
+       WFIFO_CTRL_TB           = BIT(11),      /* transfer byte bit */
+};
+
+/* status register init value */
+enum {
+       SPACEMIT_I2C_INT_STATUS_MASK    = 0xfffc0000,   /* SR bits[31:18] */
+       SPACEMIT_I2C_INT_CTRL_MASK      = (CR_ALDIE | CR_DTEIE | CR_DRFIE |
+                               CR_BEIE | CR_TXDONEIE | CR_TXEIE |
+                               CR_RXHFIE | CR_RXFIE | CR_RXOVIE |
+                               CR_MSDIE),
+};
+
+/* i2c transfer mode */
+enum spacemit_i2c_xfer_mode {
+       SPACEMIT_I2C_MODE_INTERRUPT,
+       SPACEMIT_I2C_MODE_FIFO,
+       SPACEMIT_I2C_MODE_DMA,
+       SPACEMIT_I2C_MODE_PIO,
+       SPACEMIT_I2C_MODE_INVALID,
+};
+
+/* i2c transfer phase during transaction */
+enum spacemit_i2c_xfer_phase {
+       SPACEMIT_I2C_XFER_MASTER_CODE,
+       SPACEMIT_I2C_XFER_SLAVE_ADDR,
+       SPACEMIT_I2C_XFER_BODY,
+       SPACEMIT_I2C_XFER_IDLE,
+};
+
+#ifdef CONFIG_I2C_SLAVE
+/* Initialize the control register for slave
+ * [24]=1: Slave stop interrupt enable
+ * [23]=1: Slave address detected interrupt enable
+ * [22]=1: Bus Error interrupt enable
+ * [21]=1: Disable TWSI response to general call messages as a slave
+ * [20]=1: DRFIE, DBR Receive full interrupt enable
+ * [19]=1: ITEIE, IDBR Transmit Empty Interrupt enable
+ * [18]=1: Arbitration Loss Detected Interrupt Enable
+ * [16]=1: I2C hs stretch
+ * [14]=1: TWSI Unit enable
+ * [13]=0: SCL disable, since it's in Slave mode, master drive it
+ * [9]=1: bus mode (master operation)
+ * [3:0]=0: No TB, START, STOP, ACKNAK since in Slave mode,
+ *                     it should detect them from the bus data sended by Master
+*/
+#define SPACEMIT_I2C_SLAVE_CRINIT      (CR_IUE | CR_ALDIE | CR_DTEIE | CR_DRFIE | CR_GCD | CR_BEIE \
+                               | CR_SADIE | CR_SSDIE | CR_MODE_HIGH | CR_HS_STRETCH)
+#endif
+
+/* i2c controller FIFO depth */
+#define SPACEMIT_I2C_RX_FIFO_DEPTH             (8)
+#define SPACEMIT_I2C_TX_FIFO_DEPTH             (8)
+
+/* i2c bus recover timeout: us */
+#define SPACEMIT_I2C_BUS_RECOVER_TIMEOUT       (100000)
+
+/* i2c bus active timeout: us */
+#define SPACEMIT_I2C_BUS_ACTIVE_TIMEOUT        (100000)
+
+/* scatter list size for DMA mode, equals to max number of i2c messages */
+#define SPACEMIT_I2C_SCATTERLIST_SIZE  I2C_RDRW_IOCTL_MAX_MSGS
+
+/* for DMA mode, limit one message's length less than 512 bytes */
+#define SPACEMIT_I2C_MAX_MSG_LEN               (512)
+#define SPACEMIT_I2C_DMA_TX_BUF_LEN            ((SPACEMIT_I2C_MAX_MSG_LEN + 2) *\
+                                               SPACEMIT_I2C_SCATTERLIST_SIZE)
+#define SPACEMIT_I2C_DMA_RX_BUF_LEN            (SPACEMIT_I2C_MAX_MSG_LEN *\
+                                               SPACEMIT_I2C_SCATTERLIST_SIZE)
+
+#define SPACEMIT_I2C_APB_CLOCK_26M             (26000000)
+#define SPACEMIT_I2C_APB_CLOCK_52M             (52000000)
+
+/* i2c-spacemit driver's main struct */
+struct spacemit_i2c_dev {
+       struct device           *dev;
+       struct i2c_adapter      adapt;
+       struct i2c_msg          *msgs;
+       int                     num;
+       struct resource         resrc;
+       struct mutex            mtx;
+       spinlock_t              fifo_lock;
+       int                     drv_retries;
+
+       /* virtual base address mapped for register */
+       void __iomem            *mapbase;
+
+       struct reset_control    *resets;
+       struct clk              *clk;
+       int                     irq;
+       int                     clk_freq_in;
+       int                     clk_freq_out;
+       bool                    clk_always_on;
+
+       /* i2c speed mode selection */
+       bool                    fast_mode;
+       bool                    high_mode;
+
+       /* master code for high-speed mode */
+       u8                      master_code;
+       u32                     clk_rate;
+       u32                     i2c_lcr;
+       u32                     i2c_wcr;
+
+       bool                    dma_disable;
+       bool                    shutdown;
+
+       /* DMA parameters */
+       struct dma_chan         *rx_dma;
+       struct dma_chan         *tx_dma;
+       struct dma_slave_config rx_dma_cfg;
+       struct dma_slave_config tx_dma_cfg;
+       struct scatterlist      *rx_sg;
+       struct scatterlist      *tx_sg;
+       u16                     *tx_dma_buf;
+       u8                      *rx_dma_buf;
+
+       struct pinctrl          *pinctrl;
+       struct pinctrl_state    *pin_i2c_ap;
+       struct pinctrl_state    *pin_i2c_cp;
+       struct pinctrl_state    *pin_gpio;
+
+       /* slave address with read/write flag */
+       u32                     slave_addr_rw;
+
+       struct i2c_msg          *cur_msg;
+       int                     msg_idx;
+       u8                      *msg_buf;
+       bool                    is_rx;
+       size_t                  rx_cnt;
+       size_t                  tx_cnt;
+       bool                    is_xfer_start;
+       int                     rx_total;
+       bool                    smbus_rcv_len;
+
+       struct completion       complete;
+       u32                     timeout;
+       enum spacemit_i2c_xfer_mode     xfer_mode;
+       enum spacemit_i2c_xfer_phase    phase;
+       u32                     i2c_ctrl_reg_value;
+       u32                     i2c_status;
+       u32                     i2c_err;
+
+#ifdef CONFIG_I2C_SLAVE
+       /* spacemitve functions */
+       struct i2c_client       *slave;
+#endif
+
+       /* debugfs interface for user-space */
+       struct dentry           *dbgfs;
+       char                    dbgfs_name[32];
+       enum spacemit_i2c_xfer_mode     dbgfs_mode;
+
+       /* hwlock address */
+       void __iomem            *hwlock_addr;
+
+       /*  apb clock */
+       u32                     apb_clock;
+};
+
+#endif /* _I2C_SPACEMIT_K1X_H */