mirror of
https://github.com/armbian/build.git
synced 2025-09-19 04:31:38 +02:00
2387 lines
69 KiB
Diff
2387 lines
69 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: Patrick Yavitz <pyavitz@armbian.com>
|
|
Date: Fri, 21 Jun 2024 11:54:06 -0400
|
|
Subject: add spacemit patch set
|
|
|
|
source: https://gitee.com/bianbu-linux/linux-6.1
|
|
|
|
Signed-off-by: Patrick Yavitz <pyavitz@armbian.com>
|
|
---
|
|
drivers/i2c/busses/Kconfig | 8 +
|
|
drivers/i2c/busses/Makefile | 1 +
|
|
drivers/i2c/busses/i2c-k1x.c | 2050 ++++++++++
|
|
drivers/i2c/busses/i2c-k1x.h | 275 ++
|
|
4 files changed, 2334 insertions(+)
|
|
|
|
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
|
|
index 111111111111..222222222222 100644
|
|
--- a/drivers/i2c/busses/Kconfig
|
|
+++ b/drivers/i2c/busses/Kconfig
|
|
@@ -300,6 +300,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 111111111111..222222222222 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..111111111111
|
|
--- /dev/null
|
|
+++ b/drivers/i2c/busses/i2c-k1x.c
|
|
@@ -0,0 +1,2050 @@
|
|
+// 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");
|
|
+ synchronize_irq(spacemit_i2c->irq);
|
|
+ disable_irq(spacemit_i2c->irq);
|
|
+ spacemit_i2c_bus_reset(spacemit_i2c);
|
|
+ spacemit_i2c_reset(spacemit_i2c);
|
|
+ ret = -ETIMEDOUT;
|
|
+ goto timeout_xfex;
|
|
+ }
|
|
+ }
|
|
+
|
|
+err_xfer:
|
|
+ if (likely(!ret))
|
|
+ spacemit_i2c_check_bus_release(spacemit_i2c);
|
|
+
|
|
+err_recover:
|
|
+ disable_irq(spacemit_i2c->irq);
|
|
+
|
|
+timeout_xfex:
|
|
+ /* 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_NO_AUTOEN,
|
|
+ dev_name(spacemit_i2c->dev), spacemit_i2c);
|
|
+ if (ret) {
|
|
+ dev_err(spacemit_i2c->dev, "failed to request irq\n");
|
|
+ goto err_out;
|
|
+ }
|
|
+
|
|
+ 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..111111111111
|
|
--- /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 */
|
|
--
|
|
Armbian
|
|
|