summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichal Wilczynski <m.wilczynski@samsung.com>2024-08-22 08:59:41 +0200
committerMichal Wilczynski <m.wilczynski@samsung.com>2024-08-22 13:53:28 +0200
commitf022935eaa99e87703a3a1a17c90262dc362e1c3 (patch)
tree2bbfb20f9e1c2496eed35885cb9bc1d442c2cbd5
parentdee5e3da105fc98c85543486a34d2103b7e521f7 (diff)
downloadlinux-riscv-f022935eaa99e87703a3a1a17c90262dc362e1c3.tar.gz
linux-riscv-f022935eaa99e87703a3a1a17c90262dc362e1c3.tar.bz2
linux-riscv-f022935eaa99e87703a3a1a17c90262dc362e1c3.zip
i2c: Add SpacemiT K1-X i2c driver
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>
-rw-r--r--drivers/i2c/busses/Kconfig8
-rw-r--r--drivers/i2c/busses/Makefile1
-rw-r--r--drivers/i2c/busses/i2c-k1x.c2049
-rw-r--r--drivers/i2c/busses/i2c-k1x.h275
4 files changed, 2333 insertions, 0 deletions
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index 97d27e01a6ee..5df7ecfc053d 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -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
diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile
index 9be9fdb07f3d..c276d596464a 100644
--- a/drivers/i2c/busses/Makefile
+++ b/drivers/i2c/busses/Makefile
@@ -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
index 000000000000..3991d78b531a
--- /dev/null
+++ b/drivers/i2c/busses/i2c-k1x.c
@@ -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
index 000000000000..c426c6f3d39f
--- /dev/null
+++ b/drivers/i2c/busses/i2c-k1x.h
@@ -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 */