04c6d7569f
Adding command queue mode can reduce the time between each byte to improve the I2C bus clock stretching during I2C transaction. I2C command queue mode of it8xxx2 can support I2C APIs including: i2c_write(), i2c_read(), i2c_burst_read. Test: 1. tests\drivers\i2c\i2c_api --> pass 2. Reading 16 bytes of data through i2c_burst_read() can reduce 0.72ms(2.54ms->1.82ms) compared to the original pio mode when the frequency is 100KHz. 3. krabby platform can boot normally. Signed-off-by: Tim Lin <tim2.lin@ite.corp-partner.google.com>
1081 lines
29 KiB
C
1081 lines
29 KiB
C
/*
|
|
* Copyright (c) 2022 ITE Corporation. All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT ite_enhance_i2c
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/pinctrl.h>
|
|
#include <errno.h>
|
|
#include <soc.h>
|
|
#include <soc_dt.h>
|
|
#include <zephyr/sys/util.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(i2c_ite_enhance, CONFIG_I2C_LOG_LEVEL);
|
|
|
|
#include "i2c-priv.h"
|
|
|
|
/* Start smbus session from idle state */
|
|
#define I2C_MSG_START BIT(5)
|
|
|
|
#define I2C_LINE_SCL_HIGH BIT(0)
|
|
#define I2C_LINE_SDA_HIGH BIT(1)
|
|
#define I2C_LINE_IDLE (I2C_LINE_SCL_HIGH | I2C_LINE_SDA_HIGH)
|
|
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
/* Reserved 5 bytes for ID and CMD_x. */
|
|
#define I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE (CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE - 5)
|
|
|
|
/* Repeat Start. */
|
|
#define I2C_CQ_CMD_L_RS BIT(7)
|
|
/*
|
|
* R/W (Read/ Write) decides the I2C read or write direction.
|
|
* 1: read, 0: write
|
|
*/
|
|
#define I2C_CQ_CMD_L_RW BIT(6)
|
|
/* P (STOP) is the I2C STOP condition. */
|
|
#define I2C_CQ_CMD_L_P BIT(5)
|
|
/* E (End) is this device end flag. */
|
|
#define I2C_CQ_CMD_L_E BIT(4)
|
|
/* LA (Last ACK) is Last ACK in master receiver. */
|
|
#define I2C_CQ_CMD_L_LA BIT(3)
|
|
/* bit[2:0] are number of transfer out or receive data which depends on R/W. */
|
|
#define I2C_CQ_CMD_L_NUM_BIT_2_0 GENMASK(2, 0)
|
|
|
|
struct i2c_cq_packet {
|
|
uint8_t id;
|
|
uint8_t cmd_l;
|
|
uint8_t cmd_h;
|
|
uint8_t wdata[0];
|
|
};
|
|
#endif /* CONFIG_I2C_IT8XXX2_CQ_MODE */
|
|
|
|
struct i2c_enhance_config {
|
|
void (*irq_config_func)(void);
|
|
uint32_t bitrate;
|
|
uint8_t *base;
|
|
uint8_t i2c_irq_base;
|
|
uint8_t port;
|
|
/* SCL GPIO cells */
|
|
struct gpio_dt_spec scl_gpios;
|
|
/* SDA GPIO cells */
|
|
struct gpio_dt_spec sda_gpios;
|
|
/* I2C alternate configuration */
|
|
const struct pinctrl_dev_config *pcfg;
|
|
uint8_t prescale_scl_low;
|
|
uint32_t clock_gate_offset;
|
|
};
|
|
|
|
enum i2c_pin_fun {
|
|
SCL = 0,
|
|
SDA,
|
|
};
|
|
|
|
enum i2c_ch_status {
|
|
I2C_CH_NORMAL = 0,
|
|
I2C_CH_REPEAT_START,
|
|
I2C_CH_WAIT_READ,
|
|
I2C_CH_WAIT_NEXT_XFER,
|
|
};
|
|
|
|
struct i2c_enhance_data {
|
|
enum i2c_ch_status i2ccs;
|
|
struct i2c_msg *msgs;
|
|
struct k_mutex mutex;
|
|
struct k_sem device_sync_sem;
|
|
/* Index into output data */
|
|
size_t widx;
|
|
/* Index into input data */
|
|
size_t ridx;
|
|
/* operation freq of i2c */
|
|
uint32_t bus_freq;
|
|
/* Error code, if any */
|
|
uint32_t err;
|
|
/* address of device */
|
|
uint16_t addr_16bit;
|
|
/* wait for stop bit interrupt */
|
|
uint8_t stop;
|
|
/* Number of messages. */
|
|
uint8_t num_msgs;
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
/* Store command queue mode messages. */
|
|
struct i2c_msg *cq_msgs;
|
|
/* Command queue tx payload. */
|
|
uint8_t i2c_cq_mode_tx_dlm[CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE] __aligned(4);
|
|
/* Command queue rx payload. */
|
|
uint8_t i2c_cq_mode_rx_dlm[CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE] __aligned(4);
|
|
#endif
|
|
};
|
|
|
|
enum enhanced_i2c_transfer_direct {
|
|
TX_DIRECT,
|
|
RX_DIRECT,
|
|
};
|
|
|
|
enum enhanced_i2c_ctl {
|
|
/* Hardware reset */
|
|
E_HW_RST = 0x01,
|
|
/* Stop */
|
|
E_STOP = 0x02,
|
|
/* Start & Repeat start */
|
|
E_START = 0x04,
|
|
/* Acknowledge */
|
|
E_ACK = 0x08,
|
|
/* State reset */
|
|
E_STS_RST = 0x10,
|
|
/* Mode select */
|
|
E_MODE_SEL = 0x20,
|
|
/* I2C interrupt enable */
|
|
E_INT_EN = 0x40,
|
|
/* 0 : Standard mode , 1 : Receive mode */
|
|
E_RX_MODE = 0x80,
|
|
/* State reset and hardware reset */
|
|
E_STS_AND_HW_RST = (E_STS_RST | E_HW_RST),
|
|
/* Generate start condition and transmit slave address */
|
|
E_START_ID = (E_INT_EN | E_MODE_SEL | E_ACK | E_START | E_HW_RST),
|
|
/* Generate stop condition */
|
|
E_FINISH = (E_INT_EN | E_MODE_SEL | E_ACK | E_STOP | E_HW_RST),
|
|
/* Start with command queue mode */
|
|
E_START_CQ = (E_INT_EN | E_MODE_SEL | E_ACK | E_START),
|
|
};
|
|
|
|
enum enhanced_i2c_host_status {
|
|
/* ACK receive */
|
|
E_HOSTA_ACK = 0x01,
|
|
/* Interrupt pending */
|
|
E_HOSTA_INTP = 0x02,
|
|
/* Read/Write */
|
|
E_HOSTA_RW = 0x04,
|
|
/* Time out error */
|
|
E_HOSTA_TMOE = 0x08,
|
|
/* Arbitration lost */
|
|
E_HOSTA_ARB = 0x10,
|
|
/* Bus busy */
|
|
E_HOSTA_BB = 0x20,
|
|
/* Address match */
|
|
E_HOSTA_AM = 0x40,
|
|
/* Byte done status */
|
|
E_HOSTA_BDS = 0x80,
|
|
/* time out or lost arbitration */
|
|
E_HOSTA_ANY_ERROR = (E_HOSTA_TMOE | E_HOSTA_ARB),
|
|
/* Byte transfer done and ACK receive */
|
|
E_HOSTA_BDS_AND_ACK = (E_HOSTA_BDS | E_HOSTA_ACK),
|
|
};
|
|
|
|
enum i2c_reset_cause {
|
|
I2C_RC_NO_IDLE_FOR_START = 1,
|
|
I2C_RC_TIMEOUT,
|
|
};
|
|
|
|
static int i2c_parsing_return_value(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
|
|
if (!data->err) {
|
|
return 0;
|
|
}
|
|
|
|
/* Connection timed out */
|
|
if (data->err == ETIMEDOUT) {
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
/* The device does not respond ACK */
|
|
if (data->err == E_HOSTA_ACK) {
|
|
return -ENXIO;
|
|
} else {
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
static int i2c_get_line_levels(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
int pin_sts = 0;
|
|
|
|
if (IT8XXX2_I2C_TOS(base) & IT8XXX2_I2C_SCL_IN) {
|
|
pin_sts |= I2C_LINE_SCL_HIGH;
|
|
}
|
|
|
|
if (IT8XXX2_I2C_TOS(base) & IT8XXX2_I2C_SDA_IN) {
|
|
pin_sts |= I2C_LINE_SDA_HIGH;
|
|
}
|
|
|
|
return pin_sts;
|
|
}
|
|
|
|
static int i2c_is_busy(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
return (IT8XXX2_I2C_STR(base) & E_HOSTA_BB);
|
|
}
|
|
|
|
static int i2c_bus_not_available(const struct device *dev)
|
|
{
|
|
if (i2c_is_busy(dev) ||
|
|
(i2c_get_line_levels(dev) != I2C_LINE_IDLE)) {
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void i2c_reset(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
/* State reset and hardware reset */
|
|
IT8XXX2_I2C_CTR(base) = E_STS_AND_HW_RST;
|
|
}
|
|
|
|
/* Set clock frequency for i2c port D, E , or F */
|
|
static void i2c_enhanced_port_set_frequency(const struct device *dev,
|
|
int freq_hz)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint32_t clk_div, psr, pll_clock;
|
|
uint8_t *base = config->base;
|
|
|
|
pll_clock = chip_get_pll_freq();
|
|
/*
|
|
* Let psr(Prescale) = IT8XXX2_I2C_PSR(p_ch)
|
|
* Then, 1 SCL cycle = 2 x (psr + 2) x SMBus clock cycle
|
|
* SMBus clock = pll_clock / clk_div
|
|
* SMBus clock cycle = 1 / SMBus clock
|
|
* 1 SCL cycle = 1 / freq
|
|
* 1 / freq = 2 x (psr + 2) x (1 / (pll_clock / clk_div))
|
|
* psr = ((pll_clock / clk_div) x (1 / freq) x (1 / 2)) - 2
|
|
*/
|
|
if (freq_hz) {
|
|
/* Get SMBus clock divide value */
|
|
clk_div = (IT8XXX2_ECPM_SCDCR2 & 0x0F) + 1U;
|
|
/* Calculate PSR value */
|
|
psr = (pll_clock / (clk_div * (2U * freq_hz))) - 2U;
|
|
/* Set psr value under 0xFD */
|
|
if (psr > 0xFD) {
|
|
psr = 0xFD;
|
|
}
|
|
|
|
/* Adjust SCL low period prescale */
|
|
psr += config->prescale_scl_low;
|
|
|
|
/* Set I2C Speed */
|
|
IT8XXX2_I2C_PSR(base) = psr & 0xFF;
|
|
IT8XXX2_I2C_HSPR(base) = psr & 0xFF;
|
|
}
|
|
|
|
}
|
|
|
|
static int i2c_enhance_configure(const struct device *dev,
|
|
uint32_t dev_config_raw)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
struct i2c_enhance_data *const data = dev->data;
|
|
|
|
if (!(I2C_MODE_CONTROLLER & dev_config_raw)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (I2C_ADDR_10_BITS & dev_config_raw) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
data->bus_freq = I2C_SPEED_GET(dev_config_raw);
|
|
|
|
i2c_enhanced_port_set_frequency(dev, config->bitrate);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_enhance_get_config(const struct device *dev, uint32_t *dev_config)
|
|
{
|
|
struct i2c_enhance_data *const data = dev->data;
|
|
uint32_t speed;
|
|
|
|
if (!data->bus_freq) {
|
|
LOG_ERR("The bus frequency is not initially configured.");
|
|
return -EIO;
|
|
}
|
|
|
|
switch (data->bus_freq) {
|
|
case I2C_SPEED_DT:
|
|
case I2C_SPEED_STANDARD:
|
|
case I2C_SPEED_FAST:
|
|
case I2C_SPEED_FAST_PLUS:
|
|
speed = I2C_SPEED_SET(data->bus_freq);
|
|
break;
|
|
default:
|
|
return -ERANGE;
|
|
}
|
|
|
|
*dev_config = (I2C_MODE_CONTROLLER | speed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enhanced_i2c_error(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
uint32_t i2c_str = IT8XXX2_I2C_STR(base);
|
|
|
|
if (i2c_str & E_HOSTA_ANY_ERROR) {
|
|
data->err = i2c_str & E_HOSTA_ANY_ERROR;
|
|
/* device does not respond ACK */
|
|
} else if ((i2c_str & E_HOSTA_BDS_AND_ACK) == E_HOSTA_BDS) {
|
|
if (IT8XXX2_I2C_CTR(base) & E_ACK) {
|
|
data->err = E_HOSTA_ACK;
|
|
}
|
|
}
|
|
|
|
return data->err;
|
|
}
|
|
|
|
static void enhanced_i2c_start(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
/* reset i2c port */
|
|
i2c_reset(dev);
|
|
/* Set i2c frequency */
|
|
i2c_enhanced_port_set_frequency(dev, config->bitrate);
|
|
/*
|
|
* Set time out register.
|
|
* I2C D/E/F clock/data low timeout.
|
|
*/
|
|
IT8XXX2_I2C_TOR(base) = I2C_CLK_LOW_TIMEOUT;
|
|
/* bit1: Enable enhanced i2c module */
|
|
IT8XXX2_I2C_CTR1(base) = IT8XXX2_I2C_MDL_EN;
|
|
}
|
|
|
|
static void i2c_pio_trans_data(const struct device *dev,
|
|
enum enhanced_i2c_transfer_direct direct,
|
|
uint16_t trans_data, int first_byte)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
uint32_t nack = 0;
|
|
|
|
if (first_byte) {
|
|
/* First byte must be slave address. */
|
|
IT8XXX2_I2C_DTR(base) = trans_data |
|
|
(direct == RX_DIRECT ? BIT(0) : 0);
|
|
/* start or repeat start signal. */
|
|
IT8XXX2_I2C_CTR(base) = E_START_ID;
|
|
} else {
|
|
if (direct == TX_DIRECT) {
|
|
/* Transmit data */
|
|
IT8XXX2_I2C_DTR(base) = (uint8_t)trans_data;
|
|
} else {
|
|
/*
|
|
* Receive data.
|
|
* Last byte should be NACK in the end of read cycle
|
|
*/
|
|
if (((data->ridx + 1) == data->msgs->len) &&
|
|
(data->msgs->flags & I2C_MSG_STOP)) {
|
|
nack = 1;
|
|
}
|
|
}
|
|
/* Set hardware reset to start next transmission */
|
|
IT8XXX2_I2C_CTR(base) = E_INT_EN | E_MODE_SEL |
|
|
E_HW_RST | (nack ? 0 : E_ACK);
|
|
}
|
|
}
|
|
|
|
static int enhanced_i2c_tran_read(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
uint8_t in_data = 0;
|
|
|
|
if (data->msgs->flags & I2C_MSG_START) {
|
|
/* clear start flag */
|
|
data->msgs->flags &= ~I2C_MSG_START;
|
|
enhanced_i2c_start(dev);
|
|
/* Direct read */
|
|
data->i2ccs = I2C_CH_WAIT_READ;
|
|
/* Send ID */
|
|
i2c_pio_trans_data(dev, RX_DIRECT, data->addr_16bit << 1, 1);
|
|
} else {
|
|
if (data->i2ccs) {
|
|
if (data->i2ccs == I2C_CH_WAIT_READ) {
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
/* Receive data */
|
|
i2c_pio_trans_data(dev, RX_DIRECT, in_data, 0);
|
|
|
|
/* data->msgs->flags == I2C_MSG_RESTART */
|
|
} else {
|
|
/* Write to read */
|
|
data->i2ccs = I2C_CH_WAIT_READ;
|
|
/* Send ID */
|
|
i2c_pio_trans_data(dev, RX_DIRECT,
|
|
data->addr_16bit << 1, 1);
|
|
}
|
|
} else {
|
|
if (data->ridx < data->msgs->len) {
|
|
/* read data */
|
|
*(data->msgs->buf++) = IT8XXX2_I2C_DRR(base);
|
|
data->ridx++;
|
|
/* done */
|
|
if (data->ridx == data->msgs->len) {
|
|
data->msgs->len = 0;
|
|
if (data->msgs->flags & I2C_MSG_STOP) {
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
IT8XXX2_I2C_CTR(base) = E_FINISH;
|
|
/* wait for stop bit interrupt */
|
|
data->stop = 1;
|
|
return 1;
|
|
}
|
|
/* End the transaction */
|
|
data->i2ccs = I2C_CH_WAIT_READ;
|
|
return 0;
|
|
}
|
|
/* read next byte */
|
|
i2c_pio_trans_data(dev, RX_DIRECT, in_data, 0);
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int enhanced_i2c_tran_write(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
uint8_t out_data;
|
|
|
|
if (data->msgs->flags & I2C_MSG_START) {
|
|
/* Clear start bit */
|
|
data->msgs->flags &= ~I2C_MSG_START;
|
|
enhanced_i2c_start(dev);
|
|
/* Send ID */
|
|
i2c_pio_trans_data(dev, TX_DIRECT, data->addr_16bit << 1, 1);
|
|
} else {
|
|
/* Host has completed the transmission of a byte */
|
|
if (data->widx < data->msgs->len) {
|
|
out_data = *(data->msgs->buf++);
|
|
data->widx++;
|
|
|
|
/* Send Byte */
|
|
i2c_pio_trans_data(dev, TX_DIRECT, out_data, 0);
|
|
if (data->i2ccs == I2C_CH_WAIT_NEXT_XFER) {
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
}
|
|
} else {
|
|
/* done */
|
|
data->msgs->len = 0;
|
|
if (data->msgs->flags & I2C_MSG_STOP) {
|
|
IT8XXX2_I2C_CTR(base) = E_FINISH;
|
|
/* wait for stop bit interrupt */
|
|
data->stop = 1;
|
|
} else {
|
|
/* Direct write with direct read */
|
|
data->i2ccs = I2C_CH_WAIT_NEXT_XFER;
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int i2c_transaction(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
/* no error */
|
|
if (!(enhanced_i2c_error(dev))) {
|
|
if (!data->stop) {
|
|
/*
|
|
* The return value indicates if there is more data
|
|
* to be read or written. If the return value = 1,
|
|
* it means that the interrupt cannot be disable and
|
|
* continue to transmit data.
|
|
*/
|
|
if (data->msgs->flags & I2C_MSG_READ) {
|
|
return enhanced_i2c_tran_read(dev);
|
|
} else {
|
|
return enhanced_i2c_tran_write(dev);
|
|
}
|
|
}
|
|
}
|
|
/* reset i2c port */
|
|
i2c_reset(dev);
|
|
IT8XXX2_I2C_CTR1(base) = 0;
|
|
|
|
data->stop = 0;
|
|
/* done doing work */
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_enhance_pio_transfer(const struct device *dev,
|
|
struct i2c_msg *msgs)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
int res;
|
|
|
|
if (data->i2ccs == I2C_CH_NORMAL) {
|
|
msgs->flags |= I2C_MSG_START;
|
|
}
|
|
|
|
for (int i = 0; i < data->num_msgs; i++) {
|
|
|
|
data->widx = 0;
|
|
data->ridx = 0;
|
|
data->err = 0;
|
|
data->msgs = &(msgs[i]);
|
|
|
|
if (msgs->flags & I2C_MSG_START) {
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
}
|
|
|
|
/*
|
|
* Start transaction.
|
|
* The return value indicates if the initial configuration
|
|
* of I2C transaction for read or write has been completed.
|
|
*/
|
|
if (i2c_transaction(dev)) {
|
|
/* Enable I2C interrupt. */
|
|
irq_enable(config->i2c_irq_base);
|
|
}
|
|
/* Wait for the transfer to complete */
|
|
/* TODO: the timeout should be adjustable */
|
|
res = k_sem_take(&data->device_sync_sem, K_MSEC(100));
|
|
/*
|
|
* The irq will be enabled at the condition of start or
|
|
* repeat start of I2C. If timeout occurs without being
|
|
* wake up during suspend(ex: interrupt is not fired),
|
|
* the irq should be disabled immediately.
|
|
*/
|
|
irq_disable(config->i2c_irq_base);
|
|
/*
|
|
* The transaction is dropped on any error(timeout, NACK, fail,
|
|
* bus error, device error).
|
|
*/
|
|
if (data->err) {
|
|
break;
|
|
}
|
|
|
|
if (res != 0) {
|
|
data->err = ETIMEDOUT;
|
|
/* reset i2c port */
|
|
i2c_reset(dev);
|
|
LOG_ERR("I2C ch%d:0x%X reset cause %d",
|
|
config->port, data->addr_16bit, I2C_RC_TIMEOUT);
|
|
/* If this message is sent fail, drop the transaction. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* reset i2c channel status */
|
|
if (data->err || (msgs->flags & I2C_MSG_STOP)) {
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
}
|
|
|
|
return data->err;
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
static void enhanced_i2c_set_cmd_addr_regs(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
struct i2c_enhance_data *data = dev->data;
|
|
uint32_t dlm_base;
|
|
uint8_t *base = config->base;
|
|
|
|
/* Set "Address Register" to store the I2C data. */
|
|
dlm_base = (uint32_t)data->i2c_cq_mode_rx_dlm & 0xffffff;
|
|
IT8XXX2_I2C_RAMH2A(base) = (dlm_base >> 16) & 0xff;
|
|
IT8XXX2_I2C_RAMHA(base) = (dlm_base >> 8) & 0xff;
|
|
IT8XXX2_I2C_RAMLA(base) = dlm_base & 0xff;
|
|
|
|
/* Set "Command Address Register" to get commands. */
|
|
dlm_base = (uint32_t)data->i2c_cq_mode_tx_dlm & 0xffffff;
|
|
IT8XXX2_I2C_CMD_ADDH2(base) = (dlm_base >> 16) & 0xff;
|
|
IT8XXX2_I2C_CMD_ADDH(base) = (dlm_base >> 8) & 0xff;
|
|
IT8XXX2_I2C_CMD_ADDL(base) = dlm_base & 0xff;
|
|
}
|
|
|
|
static void enhanced_i2c_cq_write(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
struct i2c_cq_packet *i2c_cq_pckt;
|
|
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
|
|
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
|
|
|
|
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
|
|
/* Set commands in RAM. */
|
|
i2c_cq_pckt->id = data->addr_16bit << 1;
|
|
i2c_cq_pckt->cmd_l = I2C_CQ_CMD_L_P | I2C_CQ_CMD_L_E | num_bit_2_0;
|
|
i2c_cq_pckt->cmd_h = num_bit_10_3;
|
|
for (int i = 0; i < data->cq_msgs[0].len; i++) {
|
|
i2c_cq_pckt->wdata[i] = data->cq_msgs[0].buf[i];
|
|
}
|
|
}
|
|
|
|
static void enhanced_i2c_cq_read(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
struct i2c_cq_packet *i2c_cq_pckt;
|
|
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
|
|
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
|
|
|
|
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
|
|
/* Set commands in RAM. */
|
|
i2c_cq_pckt->id = data->addr_16bit << 1;
|
|
i2c_cq_pckt->cmd_l = I2C_CQ_CMD_L_RW | I2C_CQ_CMD_L_P |
|
|
I2C_CQ_CMD_L_E | num_bit_2_0;
|
|
i2c_cq_pckt->cmd_h = num_bit_10_3;
|
|
}
|
|
|
|
static void enhanced_i2c_cq_write_to_read(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
struct i2c_cq_packet *i2c_cq_pckt;
|
|
uint8_t num_bit_2_0 = (data->cq_msgs[0].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
|
|
uint8_t num_bit_10_3 = ((data->cq_msgs[0].len - 1) >> 3) & 0xff;
|
|
int i;
|
|
|
|
i2c_cq_pckt = (struct i2c_cq_packet *)data->i2c_cq_mode_tx_dlm;
|
|
/* Set commands in RAM. (command byte for write) */
|
|
i2c_cq_pckt->id = data->addr_16bit << 1;
|
|
i2c_cq_pckt->cmd_l = num_bit_2_0;
|
|
i2c_cq_pckt->cmd_h = num_bit_10_3;
|
|
for (i = 0; i < data->cq_msgs[0].len; i++) {
|
|
i2c_cq_pckt->wdata[i] = data->cq_msgs[0].buf[i];
|
|
}
|
|
|
|
/* Set commands in RAM. (command byte for read) */
|
|
num_bit_2_0 = (data->cq_msgs[1].len - 1) & I2C_CQ_CMD_L_NUM_BIT_2_0;
|
|
num_bit_10_3 = ((data->cq_msgs[1].len - 1) >> 3) & 0xff;
|
|
i2c_cq_pckt->wdata[i++] = I2C_CQ_CMD_L_RS | I2C_CQ_CMD_L_RW |
|
|
I2C_CQ_CMD_L_P | I2C_CQ_CMD_L_E | num_bit_2_0;
|
|
i2c_cq_pckt->wdata[i] = num_bit_10_3;
|
|
}
|
|
|
|
static int enhanced_i2c_cq_isr(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
/* Device 1 finish IRQ. */
|
|
if (IT8XXX2_I2C_FST(base) & IT8XXX2_I2C_FST_DEV1_IRQ) {
|
|
uint8_t msgs_idx = data->num_msgs - 1;
|
|
|
|
/* Get data if this is a read transaction. */
|
|
for (int i = 0; i < data->cq_msgs[msgs_idx].len; i++) {
|
|
data->cq_msgs[msgs_idx].buf[i] =
|
|
data->i2c_cq_mode_rx_dlm[i];
|
|
}
|
|
} else {
|
|
/* Device 1 error have occurred. eg. nack, timeout... */
|
|
if (IT8XXX2_I2C_NST(base) & IT8XXX2_I2C_NST_ID_NACK) {
|
|
data->err = E_HOSTA_ACK;
|
|
} else {
|
|
data->err = IT8XXX2_I2C_STR(base) &
|
|
E_HOSTA_ANY_ERROR;
|
|
}
|
|
}
|
|
/* Reset bus. */
|
|
IT8XXX2_I2C_CTR(base) = E_STS_AND_HW_RST;
|
|
IT8XXX2_I2C_CTR1(base) = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int enhanced_i2c_cmd_queue_trans(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
|
|
/* State reset and hardware reset. */
|
|
IT8XXX2_I2C_CTR(base) = E_STS_AND_HW_RST;
|
|
/* Set "PSR" registers to decide the i2c speed. */
|
|
i2c_enhanced_port_set_frequency(dev, config->bitrate);
|
|
/* Set time out register. port D, E, or F clock/data low timeout. */
|
|
IT8XXX2_I2C_TOR(base) = I2C_CLK_LOW_TIMEOUT;
|
|
|
|
if (data->num_msgs == 2) {
|
|
/* I2C write to read of command queue mode. */
|
|
enhanced_i2c_cq_write_to_read(dev);
|
|
} else {
|
|
/* I2C read of command queue mode. */
|
|
if (data->cq_msgs[0].flags & I2C_MSG_READ) {
|
|
enhanced_i2c_cq_read(dev);
|
|
/* I2C write of command queue mode. */
|
|
} else {
|
|
enhanced_i2c_cq_write(dev);
|
|
}
|
|
}
|
|
|
|
/* Enable i2c module with command queue mode. */
|
|
IT8XXX2_I2C_CTR1(base) = IT8XXX2_I2C_MDL_EN | IT8XXX2_I2C_COMQ_EN;
|
|
/* One shot on device 1. */
|
|
IT8XXX2_I2C_MODE_SEL(base) = 0;
|
|
IT8XXX2_I2C_CTR2(base) = 1;
|
|
/* Start */
|
|
chip_block_idle();
|
|
IT8XXX2_I2C_CTR(base) = E_START_CQ;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int i2c_enhance_cq_transfer(const struct device *dev,
|
|
struct i2c_msg *msgs)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
int res = 0;
|
|
|
|
data->err = 0;
|
|
data->cq_msgs = msgs;
|
|
|
|
/* Start transaction */
|
|
if (enhanced_i2c_cmd_queue_trans(dev)) {
|
|
/* Enable i2c interrupt */
|
|
irq_enable(config->i2c_irq_base);
|
|
}
|
|
/* Wait for the transfer to complete */
|
|
res = k_sem_take(&data->device_sync_sem, K_MSEC(100));
|
|
|
|
irq_disable(config->i2c_irq_base);
|
|
|
|
if (res != 0) {
|
|
data->err = ETIMEDOUT;
|
|
/* Reset i2c port. */
|
|
i2c_reset(dev);
|
|
LOG_ERR("I2C ch%d:0x%X reset cause %d",
|
|
config->port, data->addr_16bit, I2C_RC_TIMEOUT);
|
|
}
|
|
|
|
chip_permit_idle();
|
|
|
|
return data->err;
|
|
}
|
|
|
|
static bool cq_mode_allowed(const struct device *dev, struct i2c_msg *msgs)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
|
|
/*
|
|
* When there is only one message, use the command queue transfer
|
|
* directly.
|
|
*/
|
|
if (data->num_msgs == 1 && (msgs[0].flags & I2C_MSG_STOP)) {
|
|
/* Read transfer payload too long, use PIO mode */
|
|
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) &&
|
|
(msgs[0].len > CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE)) {
|
|
return false;
|
|
}
|
|
/* Write transfer payload too long, use PIO mode */
|
|
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
|
|
(msgs[0].len > I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE)) {
|
|
return false;
|
|
}
|
|
/*
|
|
* Write of I2C target address without writing data, used by
|
|
* cmd_i2c_scan. Use PIO mode.
|
|
*/
|
|
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
|
|
(msgs[0].len == 0)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
/*
|
|
* When there are two messages, we need to judge whether or not there
|
|
* is I2C_MSG_RESTART flag from the second message, and then decide to
|
|
* do the command queue or PIO mode transfer.
|
|
*/
|
|
if (data->num_msgs == 2) {
|
|
/*
|
|
* The first of two messages must be write.
|
|
* If the length of write to read transfer is greater than
|
|
* command queue payload size, there will execute PIO mode.
|
|
*/
|
|
if (((msgs[0].flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) &&
|
|
(msgs[0].len <= I2C_CQ_MODE_TX_MAX_PAYLOAD_SIZE)) {
|
|
/*
|
|
* The transfer is i2c_burst_read().
|
|
*
|
|
* e.g. msg[0].flags = I2C_MSG_WRITE;
|
|
* msg[1].flags = I2C_MSG_RESTART | I2C_MSG_READ |
|
|
* I2C_MSG_STOP;
|
|
*/
|
|
if ((msgs[1].flags & I2C_MSG_RESTART) &&
|
|
((msgs[1].flags & I2C_MSG_RW_MASK) == I2C_MSG_READ) &&
|
|
(msgs[1].flags & I2C_MSG_STOP) &&
|
|
(msgs[1].len <= CONFIG_I2C_CQ_MODE_MAX_PAYLOAD_SIZE)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
#endif /* CONFIG_I2C_IT8XXX2_CQ_MODE */
|
|
|
|
static int i2c_enhance_transfer(const struct device *dev,
|
|
struct i2c_msg *msgs,
|
|
uint8_t num_msgs, uint16_t addr)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
|
|
data->num_msgs = num_msgs;
|
|
data->addr_16bit = addr;
|
|
|
|
/* Lock mutex of i2c controller */
|
|
k_mutex_lock(&data->mutex, K_FOREVER);
|
|
|
|
/*
|
|
* If the transaction of write to read is divided into two
|
|
* transfers, the repeat start transfer uses this flag to
|
|
* exclude checking bus busy.
|
|
*/
|
|
if (data->i2ccs == I2C_CH_NORMAL) {
|
|
/* Make sure we're in a good state to start */
|
|
if (i2c_bus_not_available(dev)) {
|
|
/* Recovery I2C bus */
|
|
i2c_recover_bus(dev);
|
|
/*
|
|
* After resetting I2C bus, if I2C bus is not available
|
|
* (No external pull-up), drop the transaction.
|
|
*/
|
|
if (i2c_bus_not_available(dev)) {
|
|
return -EIO;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
if (cq_mode_allowed(dev, msgs)) {
|
|
data->err = i2c_enhance_cq_transfer(dev, msgs);
|
|
} else
|
|
#endif
|
|
{
|
|
data->err = i2c_enhance_pio_transfer(dev, msgs);
|
|
}
|
|
|
|
/* Unlock mutex of i2c controller */
|
|
k_mutex_unlock(&data->mutex);
|
|
|
|
return i2c_parsing_return_value(dev);
|
|
}
|
|
|
|
static void i2c_enhance_isr(void *arg)
|
|
{
|
|
struct device *dev = (struct device *)arg;
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
uint8_t *base = config->base;
|
|
|
|
/* If done doing work, wake up the task waiting for the transfer */
|
|
if (IT8XXX2_I2C_CTR1(base) & IT8XXX2_I2C_COMQ_EN) {
|
|
if (enhanced_i2c_cq_isr(dev)) {
|
|
return;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
if (i2c_transaction(dev)) {
|
|
return;
|
|
}
|
|
}
|
|
irq_disable(config->i2c_irq_base);
|
|
k_sem_give(&data->device_sync_sem);
|
|
}
|
|
|
|
static int i2c_enhance_init(const struct device *dev)
|
|
{
|
|
struct i2c_enhance_data *data = dev->data;
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
uint8_t *base = config->base;
|
|
uint32_t bitrate_cfg;
|
|
int error, status;
|
|
|
|
/* Initialize mutex and semaphore */
|
|
k_mutex_init(&data->mutex);
|
|
k_sem_init(&data->device_sync_sem, 0, K_SEM_MAX_LIMIT);
|
|
|
|
/* Enable clock to specified peripheral */
|
|
volatile uint8_t *reg = (volatile uint8_t *)
|
|
(IT8XXX2_ECPM_BASE + (config->clock_gate_offset >> 8));
|
|
uint8_t reg_mask = config->clock_gate_offset & 0xff;
|
|
*reg &= ~reg_mask;
|
|
|
|
/* Enable I2C function */
|
|
/* Software reset */
|
|
IT8XXX2_I2C_DHTR(base) |= IT8XXX2_I2C_SOFT_RST;
|
|
IT8XXX2_I2C_DHTR(base) &= ~IT8XXX2_I2C_SOFT_RST;
|
|
/* reset i2c port */
|
|
i2c_reset(dev);
|
|
/* bit1, Module enable */
|
|
IT8XXX2_I2C_CTR1(base) = 0;
|
|
|
|
#ifdef CONFIG_I2C_IT8XXX2_CQ_MODE
|
|
/* Set command address registers. */
|
|
enhanced_i2c_set_cmd_addr_regs(dev);
|
|
#endif
|
|
|
|
/* Set clock frequency for I2C ports */
|
|
if (config->bitrate == I2C_BITRATE_STANDARD ||
|
|
config->bitrate == I2C_BITRATE_FAST ||
|
|
config->bitrate == I2C_BITRATE_FAST_PLUS) {
|
|
bitrate_cfg = i2c_map_dt_bitrate(config->bitrate);
|
|
} else {
|
|
/* Device tree specified speed */
|
|
bitrate_cfg = I2C_SPEED_DT << I2C_SPEED_SHIFT;
|
|
}
|
|
|
|
error = i2c_enhance_configure(dev, I2C_MODE_CONTROLLER | bitrate_cfg);
|
|
data->i2ccs = I2C_CH_NORMAL;
|
|
|
|
if (error) {
|
|
LOG_ERR("i2c: failure initializing");
|
|
return error;
|
|
}
|
|
|
|
/* Set the pin to I2C alternate function. */
|
|
status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (status < 0) {
|
|
LOG_ERR("Failed to configure I2C pins");
|
|
return status;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int i2c_enhance_recover_bus(const struct device *dev)
|
|
{
|
|
const struct i2c_enhance_config *config = dev->config;
|
|
int i, status;
|
|
|
|
/* Set SCL of I2C as GPIO pin */
|
|
gpio_pin_configure_dt(&config->scl_gpios, GPIO_OUTPUT);
|
|
/* Set SDA of I2C as GPIO pin */
|
|
gpio_pin_configure_dt(&config->sda_gpios, GPIO_OUTPUT);
|
|
|
|
/*
|
|
* In I2C recovery bus, 1ms sleep interval for bitbanging i2c
|
|
* is mainly to ensure that gpio has enough time to go from
|
|
* low to high or high to low.
|
|
*/
|
|
/* Pull SCL and SDA pin to high */
|
|
gpio_pin_set_dt(&config->scl_gpios, 1);
|
|
gpio_pin_set_dt(&config->sda_gpios, 1);
|
|
k_msleep(1);
|
|
|
|
/* Start condition */
|
|
gpio_pin_set_dt(&config->sda_gpios, 0);
|
|
k_msleep(1);
|
|
gpio_pin_set_dt(&config->scl_gpios, 0);
|
|
k_msleep(1);
|
|
|
|
/* 9 cycles of SCL with SDA held high */
|
|
for (i = 0; i < 9; i++) {
|
|
/* SDA */
|
|
gpio_pin_set_dt(&config->sda_gpios, 1);
|
|
/* SCL */
|
|
gpio_pin_set_dt(&config->scl_gpios, 1);
|
|
k_msleep(1);
|
|
/* SCL */
|
|
gpio_pin_set_dt(&config->scl_gpios, 0);
|
|
k_msleep(1);
|
|
}
|
|
/* SDA */
|
|
gpio_pin_set_dt(&config->sda_gpios, 0);
|
|
k_msleep(1);
|
|
|
|
/* Stop condition */
|
|
gpio_pin_set_dt(&config->scl_gpios, 1);
|
|
k_msleep(1);
|
|
gpio_pin_set_dt(&config->sda_gpios, 1);
|
|
k_msleep(1);
|
|
|
|
/* Set GPIO back to I2C alternate function */
|
|
status = pinctrl_apply_state(config->pcfg, PINCTRL_STATE_DEFAULT);
|
|
if (status < 0) {
|
|
LOG_ERR("Failed to configure I2C pins");
|
|
return status;
|
|
}
|
|
|
|
/* reset i2c port */
|
|
i2c_reset(dev);
|
|
LOG_ERR("I2C ch%d reset cause %d", config->port,
|
|
I2C_RC_NO_IDLE_FOR_START);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_driver_api i2c_enhance_driver_api = {
|
|
.configure = i2c_enhance_configure,
|
|
.get_config = i2c_enhance_get_config,
|
|
.transfer = i2c_enhance_transfer,
|
|
.recover_bus = i2c_enhance_recover_bus,
|
|
};
|
|
|
|
#define I2C_ITE_ENHANCE_INIT(inst) \
|
|
PINCTRL_DT_INST_DEFINE(inst); \
|
|
BUILD_ASSERT((DT_INST_PROP(inst, clock_frequency) == \
|
|
50000) || \
|
|
(DT_INST_PROP(inst, clock_frequency) == \
|
|
I2C_BITRATE_STANDARD) || \
|
|
(DT_INST_PROP(inst, clock_frequency) == \
|
|
I2C_BITRATE_FAST) || \
|
|
(DT_INST_PROP(inst, clock_frequency) == \
|
|
I2C_BITRATE_FAST_PLUS), "Not support I2C bit rate value"); \
|
|
static void i2c_enhance_config_func_##inst(void); \
|
|
\
|
|
static const struct i2c_enhance_config i2c_enhance_cfg_##inst = { \
|
|
.base = (uint8_t *)(DT_INST_REG_ADDR(inst)), \
|
|
.irq_config_func = i2c_enhance_config_func_##inst, \
|
|
.bitrate = DT_INST_PROP(inst, clock_frequency), \
|
|
.i2c_irq_base = DT_INST_IRQN(inst), \
|
|
.port = DT_INST_PROP(inst, port_num), \
|
|
.scl_gpios = GPIO_DT_SPEC_INST_GET(inst, scl_gpios), \
|
|
.sda_gpios = GPIO_DT_SPEC_INST_GET(inst, sda_gpios), \
|
|
.prescale_scl_low = DT_INST_PROP_OR(inst, prescale_scl_low, 0), \
|
|
.clock_gate_offset = DT_INST_PROP(inst, clock_gate_offset), \
|
|
.pcfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
|
|
}; \
|
|
\
|
|
static struct i2c_enhance_data i2c_enhance_data_##inst; \
|
|
\
|
|
I2C_DEVICE_DT_INST_DEFINE(inst, i2c_enhance_init, \
|
|
NULL, \
|
|
&i2c_enhance_data_##inst, \
|
|
&i2c_enhance_cfg_##inst, \
|
|
POST_KERNEL, \
|
|
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \
|
|
&i2c_enhance_driver_api); \
|
|
\
|
|
static void i2c_enhance_config_func_##inst(void) \
|
|
{ \
|
|
IRQ_CONNECT(DT_INST_IRQN(inst), \
|
|
0, \
|
|
i2c_enhance_isr, \
|
|
DEVICE_DT_INST_GET(inst), 0); \
|
|
}
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(I2C_ITE_ENHANCE_INIT)
|