summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/stm32/i2c-stm32f0.c69
-rw-r--r--chip/stm32/registers.h2
2 files changed, 51 insertions, 20 deletions
diff --git a/chip/stm32/i2c-stm32f0.c b/chip/stm32/i2c-stm32f0.c
index a72233d462..9d43f9cb27 100644
--- a/chip/stm32/i2c-stm32f0.c
+++ b/chip/stm32/i2c-stm32f0.c
@@ -139,13 +139,13 @@ static uint8_t host_buffer[I2C_MAX_HOST_PACKET_SIZE + 2];
static uint8_t params_copy[I2C_MAX_HOST_PACKET_SIZE] __aligned(4);
static int host_i2c_resp_port;
static int tx_pending;
+static int tx_index, tx_end;
static struct host_packet i2c_packet;
static void i2c_send_response_packet(struct host_packet *pkt)
{
int size = pkt->response_size;
uint8_t *out = host_buffer;
- int i = 0;
/* Ignore host command in-progress */
if (pkt->driver_result == EC_RES_IN_PROGRESS)
@@ -155,14 +155,9 @@ static void i2c_send_response_packet(struct host_packet *pkt)
*out++ = pkt->driver_result;
*out++ = size;
- /* Transmit data when I2C tx buffer is empty until finished. */
- while ((i < size + 2) && tx_pending) {
- if (STM32_I2C_ISR(host_i2c_resp_port) & STM32_I2C_ISR_TXIS)
- STM32_I2C_TXDR(host_i2c_resp_port) = host_buffer[i++];
-
- /* I2C is slow, so let other things run while we wait */
- usleep(50);
- }
+ /* host_buffer data range */
+ tx_index = 0;
+ tx_end = size + 2;
/*
* Set the transmitter to be in 'not full' state to keep sending
@@ -267,14 +262,40 @@ static void i2c_event_handler(int port)
if (i2c_isr & STM32_I2C_ISR_RXNE)
host_buffer[buf_idx++] = STM32_I2C_RXDR(port);
+ /* Master requested STOP or RESTART */
+ if (i2c_isr & STM32_I2C_ISR_NACK) {
+ /* Make sure TXIS interrupt is disabled */
+ STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE;
+ /* Clear NACK */
+ STM32_I2C_ICR(port) |= STM32_I2C_ICR_NACKCF;
+ /* Resend last byte on RESTART */
+ if (port == I2C_PORT_EC && tx_index)
+ tx_index--;
+ }
+
/* Transmitter empty event */
if (i2c_isr & STM32_I2C_ISR_TXIS) {
if (port == I2C_PORT_EC) { /* host is waiting for PD response */
- if (rx_pending) {
+ if (tx_pending) {
+ if (tx_index < tx_end) {
+ STM32_I2C_TXDR(port) =
+ host_buffer[tx_index++];
+ } else {
+ STM32_I2C_TXDR(port) = 0xec;
+ /*
+ * Set tx_index = 0 to prevent NACK
+ * handler resending last buffer byte.
+ */
+ tx_index = 0;
+ tx_end = 0;
+ /* No pending data */
+ tx_pending = 0;
+ }
+ } else if (rx_pending) {
host_i2c_resp_port = port;
/*
* Disable TXIS interrupt, transmission will
- * be done by host command task.
+ * be prepared by host command task.
*/
STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE;
@@ -300,6 +321,8 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes,
{
int rv = EC_SUCCESS;
int i;
+ int xfer_start = flags & I2C_XFER_START;
+ int xfer_stop = flags & I2C_XFER_STOP;
#if defined(CONFIG_I2C_SCL_GATE_ADDR) && defined(CONFIG_I2C_SCL_GATE_PORT)
if (port == CONFIG_I2C_SCL_GATE_PORT &&
@@ -311,14 +334,17 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes,
ASSERT(in || !in_bytes);
/* Clear status */
- STM32_I2C_ICR(port) = 0x3F38;
- STM32_I2C_CR2(port) = 0;
+ if (xfer_start) {
+ STM32_I2C_ICR(port) = STM32_I2C_ICR_ALL;
+ STM32_I2C_CR2(port) = 0;
+ }
if (out_bytes || !in_bytes) {
/* Configure the write transfer */
STM32_I2C_CR2(port) = ((out_bytes & 0xFF) << 16)
| slave_addr
- | (in_bytes == 0 ? STM32_I2C_CR2_AUTOEND : 0);
+ | ((in_bytes == 0 && xfer_stop) ?
+ STM32_I2C_CR2_AUTOEND : 0);
/* let's go ... */
STM32_I2C_CR2(port) |= STM32_I2C_CR2_START;
@@ -338,9 +364,9 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes,
}
/* Configure the read transfer and (re)start */
STM32_I2C_CR2(port) = ((in_bytes & 0xFF) << 16)
- | STM32_I2C_CR2_RD_WRN | slave_addr
- | STM32_I2C_CR2_AUTOEND
- | STM32_I2C_CR2_START;
+ | STM32_I2C_CR2_RD_WRN | slave_addr
+ | (xfer_stop ? STM32_I2C_CR2_AUTOEND : 0)
+ | STM32_I2C_CR2_START;
for (i = 0; i < in_bytes; i++) {
/* Wait for receive buffer not empty */
@@ -351,13 +377,15 @@ int i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes,
in[i] = STM32_I2C_RXDR(port);
}
}
- rv = wait_isr(port, STM32_I2C_ISR_STOP);
+ rv = wait_isr(port, xfer_stop ? STM32_I2C_ISR_STOP : STM32_I2C_ISR_TC);
if (rv)
goto xfer_exit;
xfer_exit:
/* clear status */
- STM32_I2C_ICR(port) = 0x3F38;
+ if (xfer_stop)
+ STM32_I2C_ICR(port) = STM32_I2C_ICR_ALL;
+
/* On error, queue a stop condition */
if (rv) {
/* queue a STOP condition */
@@ -430,7 +458,8 @@ static void i2c_init(void)
#ifdef CONFIG_HOSTCMD_I2C_SLAVE_ADDR
STM32_I2C_CR1(I2C_PORT_EC) |= STM32_I2C_CR1_RXIE | STM32_I2C_CR1_ERRIE
- | STM32_I2C_CR1_ADDRIE | STM32_I2C_CR1_STOPIE;
+ | STM32_I2C_CR1_ADDRIE | STM32_I2C_CR1_STOPIE
+ | STM32_I2C_CR1_NACKIE;
#if defined(CONFIG_LOW_POWER_IDLE) && (I2C_PORT_EC == STM32_I2C1_PORT)
/*
* If using low power idle and EC port is I2C1, then set I2C1 to wake
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index 261e9fb26c..52c74ef64f 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -484,11 +484,13 @@ typedef volatile struct timer_ctlr timer_ctlr_t;
#define STM32_I2C_ISR_DIR (1 << 16)
#define STM32_I2C_ICR(n) REG32(stm32_i2c_reg(n, 0x1C))
#define STM32_I2C_ICR_ADDRCF (1 << 3)
+#define STM32_I2C_ICR_NACKCF (1 << 4)
#define STM32_I2C_ICR_STOPCF (1 << 5)
#define STM32_I2C_ICR_BERRCF (1 << 8)
#define STM32_I2C_ICR_ARLOCF (1 << 9)
#define STM32_I2C_ICR_OVRCF (1 << 10)
#define STM32_I2C_ICR_TIMEOUTCF (1 << 12)
+#define STM32_I2C_ICR_ALL 0x3F38
#define STM32_I2C_PECR(n) REG32(stm32_i2c_reg(n, 0x20))
#define STM32_I2C_RXDR(n) REG32(stm32_i2c_reg(n, 0x24))
#define STM32_I2C_TXDR(n) REG32(stm32_i2c_reg(n, 0x28))