diff options
author | Michal Wilczynski <m.wilczynski@samsung.com> | 2024-08-22 08:59:41 +0200 |
---|---|---|
committer | Michal Wilczynski <m.wilczynski@samsung.com> | 2024-08-22 13:53:28 +0200 |
commit | f022935eaa99e87703a3a1a17c90262dc362e1c3 (patch) | |
tree | 2bbfb20f9e1c2496eed35885cb9bc1d442c2cbd5 | |
parent | dee5e3da105fc98c85543486a34d2103b7e521f7 (diff) | |
download | linux-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/Kconfig | 8 | ||||
-rw-r--r-- | drivers/i2c/busses/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-k1x.c | 2049 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-k1x.h | 275 |
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 */ |