diff options
author | Nick Sanders <nsanders@chromium.org> | 2016-08-11 16:28:03 -0700 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2016-08-21 04:11:10 -0700 |
commit | cc68693cda0d520229971b73c116d434c48bdd65 (patch) | |
tree | 29978c73b634ae6ebd505a361c96de9c697db083 | |
parent | 153c2cf49c6c225508e09a942eb90611c9d3cef9 (diff) | |
download | chrome-ec-cc68693cda0d520229971b73c116d434c48bdd65.tar.gz |
sweetberry: add i2c support
stm32f446 has two types of i2c blocks, the traditional
stm i2c, and "fast mode plus" i2c, which need different drivers.
This commit adds both, muxed in i2c-stm32f4, as the ec
codebase doesn't really support multiple types of the same interface.
BUG=chromium:608039
TEST=i2c works on all 4 channels
BRANCH=None
Change-Id: I6a9ac632f44142bd809ffee5782a192ae47af1f0
Signed-off-by: Nick Sanders <nsanders@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/368358
Reviewed-by: Aseda Aboagye <aaboagye@chromium.org>
-rw-r--r-- | board/stm32f446e-eval/board.c | 12 | ||||
-rw-r--r-- | board/stm32f446e-eval/board.h | 5 | ||||
-rw-r--r-- | board/stm32f446e-eval/gpio.inc | 2 | ||||
-rw-r--r-- | board/sweetberry/board.c | 21 | ||||
-rw-r--r-- | board/sweetberry/board.h | 7 | ||||
-rw-r--r-- | chip/stm32/i2c-stm32f4.c | 753 | ||||
-rw-r--r-- | common/i2c.c | 19 |
7 files changed, 814 insertions, 5 deletions
diff --git a/board/stm32f446e-eval/board.c b/board/stm32f446e-eval/board.c index 0b8691278c..e7af5dad6c 100644 --- a/board/stm32f446e-eval/board.c +++ b/board/stm32f446e-eval/board.c @@ -9,9 +9,21 @@ #include "gpio.h" #include "gpio_list.h" #include "hooks.h" +#include "i2c.h" #include "registers.h" #include "stm32-dma.h" + +/* I2C ports */ +const struct i2c_port_t i2c_ports[] = { + {"i2c1", I2C_PORT_0, 100, + GPIO_I2C1_SCL, GPIO_I2C1_SDA}, + {"fmpi2c4", FMPI2C_PORT_3, 100, + GPIO_FMPI2C_SCL, GPIO_FMPI2C_SDA}, +}; +const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); + + #define GPIO_SET_HS(bank, number) \ (STM32_GPIO_OSPEEDR(GPIO_##bank) |= (0x3 << (number * 2))) diff --git a/board/stm32f446e-eval/board.h b/board/stm32f446e-eval/board.h index 24735a2dc7..f70ddd5bb5 100644 --- a/board/stm32f446e-eval/board.h +++ b/board/stm32f446e-eval/board.h @@ -27,6 +27,11 @@ #define CONFIG_UART_TX_REQ_CH 4 #define CONFIG_UART_RX_REQ_CH 4 +#define CONFIG_I2C +#define CONFIG_I2C_MASTER +#define I2C_PORT_0 0 +#define FMPI2C_PORT_3 3 + /* This is not actually an EC so disable some features. */ #undef CONFIG_WATCHDOG_HELP #undef CONFIG_LID_SWITCH diff --git a/board/stm32f446e-eval/gpio.inc b/board/stm32f446e-eval/gpio.inc index 3d1753d43b..afc8d1e486 100644 --- a/board/stm32f446e-eval/gpio.inc +++ b/board/stm32f446e-eval/gpio.inc @@ -52,7 +52,7 @@ ALTERNATE(PIN_MASK(A, 0x0100), 0, MODULE_MCO, 0) /* MCO1: PA8 */ ALTERNATE(PIN_MASK(C, 0x0200), 0, MODULE_MCO, 0) /* MCO2: PC9 */ ALTERNATE(PIN_MASK(B, 0x0300), 4, MODULE_I2C, GPIO_ODR_HIGH | GPIO_PULL_UP) /* I2C1: PB8-9 */ -ALTERNATE(PIN_MASK(C, 0x00c0), 4, MODULE_I2C, GPIO_ODR_HIGH | GPIO_PULL_UP) /* FMPI2C MASTER:PC6/7 */ +ALTERNATE(PIN_MASK(D, 0x3000), 4, MODULE_I2C, GPIO_ODR_HIGH | GPIO_PULL_UP) /* FMPI2C MASTER:PD12/13 */ ALTERNATE(PIN_MASK(A, 0x1800), 10, MODULE_USB, 0) /* DWC USB OTG: PA11/12 */ diff --git a/board/sweetberry/board.c b/board/sweetberry/board.c index 902917001a..21c595826f 100644 --- a/board/sweetberry/board.c +++ b/board/sweetberry/board.c @@ -16,6 +16,18 @@ #include "task.h" #include "util.h" +/* I2C ports */ +const struct i2c_port_t i2c_ports[] = { + {"i2c1", I2C_PORT_0, 800, + GPIO_I2C1_SCL, GPIO_I2C1_SDA}, + {"i2c2", I2C_PORT_1, 800, + GPIO_I2C2_SCL, GPIO_I2C2_SDA}, + {"i2c3", I2C_PORT_2, 800, + GPIO_I2C3_SCL, GPIO_I2C3_SDA}, + {"fmpi2c4", FMPI2C_PORT_3, 800, + GPIO_FMPI2C_SCL, GPIO_FMPI2C_SDA}, +}; +const unsigned int i2c_ports_used = ARRAY_SIZE(i2c_ports); #define GPIO_SET_HS(bank, number) \ (STM32_GPIO_OSPEEDR(GPIO_##bank) |= (0x3 << ((number) * 2))) @@ -55,3 +67,12 @@ void board_config_post_gpio_init(void) GPIO_SET_HS(C, 6); GPIO_SET_HS(C, 7); } + +static void board_init(void) +{ + uint8_t tmp; + + /* i2c 0 has a tendancy to get wedged. TODO(nsanders): why? */ + i2c_xfer(0, 0, NULL, 0, &tmp, 1, I2C_XFER_SINGLE); +} +DECLARE_HOOK(HOOK_INIT, board_init, HOOK_PRIO_DEFAULT); diff --git a/board/sweetberry/board.h b/board/sweetberry/board.h index 1b0eddb263..407d30e442 100644 --- a/board/sweetberry/board.h +++ b/board/sweetberry/board.h @@ -29,6 +29,13 @@ #define CONFIG_UART_TX_REQ_CH 4 #define CONFIG_UART_RX_REQ_CH 4 +#define CONFIG_I2C +#define CONFIG_I2C_MASTER +#define I2C_PORT_0 0 +#define I2C_PORT_1 1 +#define I2C_PORT_2 2 +#define FMPI2C_PORT_3 3 + /* This is not actually a Chromium EC so disable some features. */ #undef CONFIG_WATCHDOG_HELP #undef CONFIG_LID_SWITCH diff --git a/chip/stm32/i2c-stm32f4.c b/chip/stm32/i2c-stm32f4.c new file mode 100644 index 0000000000..64295e3bb5 --- /dev/null +++ b/chip/stm32/i2c-stm32f4.c @@ -0,0 +1,753 @@ +/* Copyright 2016 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "chipset.h" +#include "clock.h" +#include "common.h" +#include "console.h" +#include "dma.h" +#include "hooks.h" +#include "i2c.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" + +/* Console output macros */ +#define CPUTS(outstr) cputs(CC_I2C, outstr) +#define CPRINTS(format, args...) cprints(CC_I2C, format, ## args) + +#define I2C_ERROR_FAILED_START EC_ERROR_INTERNAL_FIRST + +/* Transmit timeout in microseconds */ +#define I2C_TX_TIMEOUT_MASTER (10 * MSEC) + +/* Define I2C blocks available in stm32f4: + * We have standard ST I2C blocks and a "fast mode plus" I2C block, + * which do not share the same registers or functionality. So we'll need + * two sets of functions to handle this for stm32f4. In stm32f446, we + * only have one FMP block so we'll hardcode its port number. + */ +#define STM32F4_FMPI2C_PORT 3 + + +static const struct dma_option dma_tx_option[I2C_PORT_COUNT] = { + {STM32_DMAC_I2C1_TX, (void *)&STM32_I2C_DR(STM32_I2C1_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C1_TX_REQ_CH)}, + {STM32_DMAC_I2C2_TX, (void *)&STM32_I2C_DR(STM32_I2C2_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C2_TX_REQ_CH)}, + {STM32_DMAC_I2C3_TX, (void *)&STM32_I2C_DR(STM32_I2C3_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C3_TX_REQ_CH)}, + {STM32_DMAC_FMPI2C4_TX, (void *)&STM32_FMPI2C_TXDR(STM32_FMPI2C4_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_FMPI2C4_TX_REQ_CH)}, +}; + +static const struct dma_option dma_rx_option[I2C_PORT_COUNT] = { + {STM32_DMAC_I2C1_RX, (void *)&STM32_I2C_DR(STM32_I2C1_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C1_RX_REQ_CH)}, + {STM32_DMAC_I2C2_RX, (void *)&STM32_I2C_DR(STM32_I2C2_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C2_RX_REQ_CH)}, + {STM32_DMAC_I2C3_RX, (void *)&STM32_I2C_DR(STM32_I2C3_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_I2C3_RX_REQ_CH)}, + {STM32_DMAC_FMPI2C4_RX, (void *)&STM32_FMPI2C_RXDR(STM32_FMPI2C4_PORT), + STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT | + STM32_DMA_CCR_CHANNEL(STM32_FMPI2C4_RX_REQ_CH)}, +}; + +/* Callabck for ISR to wake task on DMA complete. */ +static inline void _i2c_dma_wake_callback(void *cb_data, int port) +{ + task_id_t id = (task_id_t)(int)cb_data; + + if (id != TASK_ID_INVALID) + task_set_event(id, TASK_EVENT_I2C_COMPLETION(port), 0); +} + +/* Each callback is hardcoded to an I2C channel. */ +static void _i2c_dma_wake_callback_0(void *cb_data) +{ + _i2c_dma_wake_callback(cb_data, 0); +} + +static void _i2c_dma_wake_callback_1(void *cb_data) +{ + _i2c_dma_wake_callback(cb_data, 1); +} + +static void _i2c_dma_wake_callback_2(void *cb_data) +{ + _i2c_dma_wake_callback(cb_data, 2); +} + +static void _i2c_dma_wake_callback_3(void *cb_data) +{ + _i2c_dma_wake_callback(cb_data, 3); +} + +/* void (*callback)(void *) */ +static void (*i2c_callbacks[I2C_PORT_COUNT])(void *) = { + _i2c_dma_wake_callback_0, + _i2c_dma_wake_callback_1, + _i2c_dma_wake_callback_2, + _i2c_dma_wake_callback_3, +}; + +/* Enable the I2C interrupt callback for this port. */ +void i2c_dma_enable_tc_interrupt(enum dma_channel stream, int port) +{ + dma_enable_tc_interrupt_callback(stream, i2c_callbacks[port], + (void *)(int)task_get_current()); +} + +/** + * Wait for SR1 register to contain the specified mask of 0 or 1. + * + * @param port I2C port + * @param mask mask of bits of interest + * @param val desired value of bits of interest + * @param poll uS poll frequency + * + * @return EC_SUCCESS, EC_ERROR_TIMEOUT if timed out waiting, or + * EC_ERROR_UNKNOWN if an error bit appeared in the status register. + */ +#define SET 0xffffffff +#define UNSET 0 +static int wait_sr1_poll(int port, int mask, int val, int poll) +{ + uint64_t timeout = get_time().val + I2C_TX_TIMEOUT_MASTER; + + while (get_time().val < timeout) { + int sr1 = STM32_I2C_SR1(port); + + /* Check for errors */ + if (sr1 & (STM32_I2C_SR1_ARLO | STM32_I2C_SR1_BERR | + STM32_I2C_SR1_AF)) { + return EC_ERROR_UNKNOWN; + } + + /* Check for desired mask */ + if ((sr1 & mask) == (val & mask)) + return EC_SUCCESS; + + /* I2C is slow, so let other things run while we wait */ + usleep(poll); + } + + CPRINTS("I2C timeout: p:%d m:%x", port, mask); + return EC_ERROR_TIMEOUT; +} + +/* Wait for SR1 register to contain the specified mask of ones */ +static int wait_sr1(int port, int mask) +{ + return wait_sr1_poll(port, mask, SET, 100); +} + + +/** + * Send a start condition and slave address on the specified port. + * + * @param port I2C port + * @param slave_addr Slave address, with LSB set for receive-mode + * + * @return Non-zero if error. + */ +static int send_start(int port, int slave_addr) +{ + int rv; + + /* Send start bit */ + STM32_I2C_CR1(port) |= STM32_I2C_CR1_START; + rv = wait_sr1_poll(port, STM32_I2C_SR1_SB, SET, 1); + if (rv) + return I2C_ERROR_FAILED_START; + + /* Write slave address */ + STM32_I2C_DR(port) = slave_addr & 0xff; + rv = wait_sr1_poll(port, STM32_I2C_SR1_ADDR, SET, 1); + if (rv) + return rv; + + /* Read SR2 to clear ADDR bit */ + rv = STM32_I2C_SR2(port); + + return EC_SUCCESS; +} + +/** + * Find the i2c port structure associated with the port. + * + * @return i2c_port_t * associated with this port number. + */ +static const struct i2c_port_t *find_port(int port) +{ + const struct i2c_port_t *p = i2c_ports; + int i; + + for (i = 0; i < i2c_ports_used; i++, p++) { + if (p->port == port) + return p; + } + CPRINTS("I2C port %d invalid! Crashing now.", port); + return NULL; +} + +/** + * Wait for ISR register to contain the specified mask. + * + * @param port I2C port + * @param mask mask of bits of interest + * @param val desired value of bits of interest + * @param poll uS poll frequency + * + * @return EC_SUCCESS, EC_ERROR_TIMEOUT if timed out waiting, or + * EC_ERROR_UNKNOWN if an error bit appeared in the status register. + */ +static int wait_fmpi2c_isr_poll(int port, int mask, int val, int poll) +{ + uint64_t timeout = get_time().val + I2C_TX_TIMEOUT_MASTER; + + while (get_time().val < timeout) { + int isr = STM32_FMPI2C_ISR(port); + + /* Check for errors */ + if (isr & (FMPI2C_ISR_ARLO | FMPI2C_ISR_BERR | + FMPI2C_ISR_NACKF)) { + return EC_ERROR_UNKNOWN; + } + + /* Check for desired mask */ + if ((isr & mask) == (val & mask)) + return EC_SUCCESS; + + /* I2C is slow, so let other things run while we wait */ + usleep(poll); + } + + CPRINTS("FMPI2C timeout p:%d, m:0x%08x", port, mask); + return EC_ERROR_TIMEOUT; +} + +/* Wait for ISR register to contain the specified mask of ones */ +static int wait_fmpi2c_isr(int port, int mask) +{ + return wait_fmpi2c_isr_poll(port, mask, SET, 100); +} + +/** + * Send a start condition and slave address on the specified port. + * + * @param port I2C port + * @param slave_addr Slave address + * @param size bytes to transfer + * @param is_read read, or write? + * + * @return Non-zero if error. + */ +static int send_fmpi2c_start(int port, int slave_addr, int size, int is_read) +{ + uint32_t reg; + + /* Send start bit */ + reg = STM32_FMPI2C_CR2(port); + reg &= ~(FMPI2C_CR2_SADD_MASK | FMPI2C_CR2_SIZE_MASK | + FMPI2C_CR2_RELOAD | FMPI2C_CR2_AUTOEND | + FMPI2C_CR2_RD_WRN | FMPI2C_CR2_START | FMPI2C_CR2_STOP); + reg |= FMPI2C_CR2_START | FMPI2C_CR2_AUTOEND | + FMPI2C_CR2_SADD(slave_addr) | FMPI2C_CR2_SIZE(size) | + (is_read ? FMPI2C_CR2_RD_WRN : 0); + STM32_FMPI2C_CR2(port) = reg; + + return EC_SUCCESS; +} + +/** + * Set i2c clock rate.. + * + * @param p I2C port struct + */ +static void i2c_set_freq_port(const struct i2c_port_t *p) +{ + int port = p->port; + int freq = clock_get_freq(); + + if (p->port == STM32F4_FMPI2C_PORT) { + int prescalar; + int actual; + uint32_t reg; + + /* FMP I2C clock set. */ + STM32_FMPI2C_CR1(port) &= ~FMPI2C_CR1_PE; + prescalar = (freq / (p->kbps * 1000 * + (0x12 + 1 + 0xe + 1 + 1))) - 1; + actual = freq / ((prescalar + 1) * (0x12 + 1 + 0xe + 1 + 1)); + + reg = FMPI2C_TIMINGR_SCLL(0x12) | + FMPI2C_TIMINGR_SCLH(0xe) | + FMPI2C_TIMINGR_PRESC(prescalar); + STM32_FMPI2C_TIMINGR(port) = reg; + + CPRINTS("port %d target %d, pre %d, act %d, reg 0x%08x", + port, p->kbps, prescalar, actual, reg); + + STM32_FMPI2C_CR1(port) |= FMPI2C_CR1_PE; + udelay(10); + } else { + /* Force peripheral reset and disable port */ + STM32_I2C_CR1(port) = STM32_I2C_CR1_SWRST; + STM32_I2C_CR1(port) = 0; + + /* Set clock frequency */ + if (p->kbps > 100) { + STM32_I2C_CCR(port) = freq / (2 * MSEC * p->kbps); + } else { + STM32_I2C_CCR(port) = STM32_I2C_CCR_FM + | STM32_I2C_CCR_DUTY + | (freq / (16 + 9 * MSEC * p->kbps)); + } + STM32_I2C_CR2(port) = freq / SECOND; + STM32_I2C_TRISE(port) = freq / SECOND + 1; + + /* Enable port */ + STM32_I2C_CR1(port) |= STM32_I2C_CR1_PE; + } +} + +/** + * Initialize on the specified I2C port. + * + * @param p the I2c port + */ +static void i2c_init_port(const struct i2c_port_t *p) +{ + int port = p->port; + + /* Configure GPIOs, clocks */ + gpio_config_module(MODULE_I2C, 1); + clock_enable_module(MODULE_I2C, 1); + + if (p->port == STM32F4_FMPI2C_PORT) { + /* FMP I2C block */ + /* Set timing (?) */ + STM32_FMPI2C_TIMINGR(port) = TIMINGR_THE_RIGHT_VALUE; + udelay(10); + /* Device enable */ + STM32_FMPI2C_CR1(port) |= FMPI2C_CR1_PE; + /* Need to wait 3 APB cycles */ + udelay(10); + /* Device only. */ + STM32_FMPI2C_OAR1(port) = 0; + STM32_FMPI2C_CR2(port) |= FMPI2C_CR2_AUTOEND; + } else { + STM32_I2C_CR1(port) |= STM32_I2C_CR1_SWRST; + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_SWRST; + udelay(10); + } + + /* Set up initial bus frequencies */ + i2c_set_freq_port(p); +} + +/*****************************************************************************/ +/* Interface */ + +/** + * Clear status regs on the specified I2C port. + * + * @param port the I2c port + */ +static void fmpi2c_clear_regs(int port) +{ + /* Clear status */ + STM32_FMPI2C_ICR(port) = 0xffffffff; + + /* Clear start, stop, NACK, etc. bits to get us in a known state */ + STM32_FMPI2C_CR2(port) &= ~(FMPI2C_CR2_START | FMPI2C_CR2_STOP | + FMPI2C_CR2_RD_WRN | FMPI2C_CR2_NACK | + FMPI2C_CR2_AUTOEND | + FMPI2C_CR2_SADD_MASK | FMPI2C_CR2_SIZE_MASK); +} + +/** + * Perform an i2c transaction + * + * @param port i2c port to use + * @param slave_addr the i2c slave addr + * @param out source buffer for data + * @param out_bytes bytes of data to write + * @param in destination buffer for data + * @param in_bytes bytes of data to read + * @param flags user cached I2C state + * + * @return EC_SUCCESS on success. + */ +static int chip_fmpi2c_xfer(int port, int slave_addr, const uint8_t *out, + int out_bytes, uint8_t *in, int in_bytes, int flags) +{ + int started = (flags & I2C_XFER_START) ? 0 : 1; + int rv = EC_SUCCESS; + int i; + + ASSERT(out || !out_bytes); + ASSERT(in || !in_bytes); + ASSERT(!started); + + if (STM32_FMPI2C_ISR(port) & FMPI2C_ISR_BUSY) { + CPRINTS("fmpi2c port %d busy", port); + return EC_ERROR_BUSY; + } + + fmpi2c_clear_regs(port); + + /* No out bytes and no in bytes means just check for active */ + if (out_bytes || !in_bytes) { + rv = send_fmpi2c_start( + port, slave_addr, out_bytes, FMPI2C_WRITE); + if (rv) + goto xfer_exit; + + /* Write data, if any */ + for (i = 0; i < out_bytes; i++) { + rv = wait_fmpi2c_isr(port, FMPI2C_ISR_TXIS); + if (rv) + goto xfer_exit; + + /* Write next data byte */ + STM32_FMPI2C_TXDR(port) = out[i]; + } + + /* Wait for transaction STOP. */ + wait_fmpi2c_isr(port, FMPI2C_ISR_STOPF); + } + + if (in_bytes) { + int rv_start; + const struct dma_option *dma = dma_rx_option + port; + + dma_start_rx(dma, in_bytes, in); + i2c_dma_enable_tc_interrupt(dma->channel, port); + + rv_start = send_fmpi2c_start( + port, slave_addr, in_bytes, FMPI2C_READ); + if (rv_start) + goto xfer_exit; + + rv = wait_fmpi2c_isr(port, FMPI2C_ISR_RXNE); + if (rv) + goto xfer_exit; + STM32_FMPI2C_CR1(port) |= FMPI2C_CR1_RXDMAEN; + + if (!rv_start) { + rv = task_wait_event_mask( + TASK_EVENT_I2C_COMPLETION(port), + DMA_TRANSFER_TIMEOUT_US); + if (rv & TASK_EVENT_I2C_COMPLETION(port)) + rv = EC_SUCCESS; + else + rv = EC_ERROR_TIMEOUT; + } + + dma_disable(dma->channel); + dma_disable_tc_interrupt(dma->channel); + + /* Validate i2c is STOPped */ + if (!rv) + rv = wait_fmpi2c_isr(port, FMPI2C_ISR_STOPF); + + STM32_FMPI2C_CR1(port) &= ~FMPI2C_CR1_RXDMAEN; + + if (rv_start) + rv = rv_start; + } + + xfer_exit: + /* On error, queue a stop condition */ + if (rv) { + flags |= I2C_XFER_STOP; + STM32_FMPI2C_CR2(port) |= FMPI2C_CR2_STOP; + + /* + * If failed at sending start, try resetting the port + * to unwedge the bus. + */ + if (rv == I2C_ERROR_FAILED_START) { + const struct i2c_port_t *p; + + CPRINTS("chip_fmpi2c_xfer start error; " + "unwedging and resetting i2c %d", port); + + p = find_port(port); + i2c_unwedge(port); + i2c_init_port(p); + } + } + + /* If a stop condition is queued, wait for it to take effect */ + if (flags & I2C_XFER_STOP) { + /* Wait up to 100 us for bus idle */ + for (i = 0; i < 10; i++) { + if (!(STM32_FMPI2C_ISR(port) & FMPI2C_ISR_BUSY)) + break; + usleep(10); + } + + /* + * Allow bus to idle for at least one 100KHz clock = 10 us. + * This allows slaves on the bus to detect bus-idle before + * the next start condition. + */ + STM32_FMPI2C_CR1(port) &= ~FMPI2C_CR1_PE; + usleep(10); + STM32_FMPI2C_CR1(port) |= FMPI2C_CR1_PE; + } + + return rv; +} + + +/** + * Clear status regs on the specified I2C port. + * + * @param port the I2c port + */ +static void i2c_clear_regs(int port) +{ + /* + * Clear status + * + * TODO(crosbug.com/p/29314): should check for any leftover error + * status, and reset the port if present. + */ + STM32_I2C_SR1(port) = 0; + + /* Clear start, stop, POS, ACK bits to get us in a known state */ + STM32_I2C_CR1(port) &= ~(STM32_I2C_CR1_START | + STM32_I2C_CR1_STOP | + STM32_I2C_CR1_POS | + STM32_I2C_CR1_ACK); +} + +/***************************************************************************** + * Exported functions declared in i2c.h + */ + +/* Perform an i2c transaction. */ +int chip_i2c_xfer(int port, int slave_addr, const uint8_t *out, int out_bytes, + uint8_t *in, int in_bytes, int flags) +{ + int started = (flags & I2C_XFER_START) ? 0 : 1; + int rv = EC_SUCCESS; + int i; + const struct i2c_port_t *p = find_port(port); + + ASSERT(out || !out_bytes); + ASSERT(in || !in_bytes); + ASSERT(!started); + + if (p->port == STM32F4_FMPI2C_PORT) { + return chip_fmpi2c_xfer(port, slave_addr, out, out_bytes, + in, in_bytes, flags); + } + + i2c_clear_regs(port); + + /* No out bytes and no in bytes means just check for active */ + if (out_bytes || !in_bytes) { + rv = send_start(port, slave_addr); + if (rv) + goto xfer_exit; + + /* Write data, if any */ + for (i = 0; i < out_bytes; i++) { + /* Write next data byte */ + STM32_I2C_DR(port) = out[i]; + + rv = wait_sr1(port, STM32_I2C_SR1_BTF); + if (rv) + goto xfer_exit; + } + + /* If no input bytes, queue stop condition */ + if (!in_bytes && (flags & I2C_XFER_STOP)) + STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP; + } + + if (in_bytes) { + int rv_start; + + const struct dma_option *dma = dma_rx_option + port; + + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_POS; + dma_start_rx(dma, in_bytes, in); + i2c_dma_enable_tc_interrupt(dma->channel, port); + + /* Setup ACK/POS before sending start as per user manual */ + if (in_bytes == 2) + STM32_I2C_CR1(port) |= STM32_I2C_CR1_POS; + else if (in_bytes != 1) + STM32_I2C_CR1(port) |= STM32_I2C_CR1_ACK; + + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_STOP; + + STM32_I2C_CR2(port) |= STM32_I2C_CR2_LAST; + STM32_I2C_CR2(port) |= STM32_I2C_CR2_DMAEN; + + rv_start = send_start(port, slave_addr | 0x01); + + if ((in_bytes == 1) && (flags & I2C_XFER_STOP)) + STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP; + + if (!rv_start) { + rv = task_wait_event_mask( + TASK_EVENT_I2C_COMPLETION(port), + DMA_TRANSFER_TIMEOUT_US); + if (rv & TASK_EVENT_I2C_COMPLETION(port)) + rv = EC_SUCCESS; + else + rv = EC_ERROR_TIMEOUT; + } + + dma_disable(dma->channel); + dma_disable_tc_interrupt(dma->channel); + STM32_I2C_CR2(port) &= ~STM32_I2C_CR2_DMAEN; + /* Disable ack */ + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_ACK; + + if (rv_start) + rv = rv_start; + + /* Send stop. */ + STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_ACK; + STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP; + STM32_I2C_CR2(port) &= ~STM32_I2C_CR2_LAST; + STM32_I2C_CR2(port) &= ~STM32_I2C_CR2_DMAEN; + } + + xfer_exit: + /* On error, queue a stop condition */ + if (rv) { + flags |= I2C_XFER_STOP; + STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP; + + /* + * If failed at sending start, try resetting the port + * to unwedge the bus. + */ + if (rv == I2C_ERROR_FAILED_START) { + const struct i2c_port_t *p; + + CPRINTS("chip_i2c_xfer start error; " + "unwedging and resetting i2c %d", port); + + p = find_port(port); + i2c_unwedge(port); + i2c_init_port(p); + } + } + + /* If a stop condition is queued, wait for it to take effect */ + if (flags & I2C_XFER_STOP) { + /* Wait up to 100 us for bus idle */ + for (i = 0; i < 10; i++) { + if (!(STM32_I2C_SR2(port) & STM32_I2C_SR2_BUSY)) + break; + usleep(10); + } + + /* + * Allow bus to idle for at least one 100KHz clock = 10 us. + * This allows slaves on the bus to detect bus-idle before + * the next start condition. + */ + usleep(10); + } + + return rv; +} + +int i2c_raw_get_scl(int port) +{ + enum gpio_signal g; + + if (get_scl_from_i2c_port(port, &g) == EC_SUCCESS) + return gpio_get_level(g); + + /* If no SCL pin defined for this port, then return 1 to appear idle. */ + return 1; +} + +int i2c_raw_get_sda(int port) +{ + enum gpio_signal g; + + if (get_sda_from_i2c_port(port, &g) == EC_SUCCESS) + return gpio_get_level(g); + + /* If no SDA pin defined for this port, then return 1 to appear idle. */ + return 1; +} + +int i2c_get_line_levels(int port) +{ + return (i2c_raw_get_sda(port) ? I2C_LINE_SDA_HIGH : 0) | + (i2c_raw_get_scl(port) ? I2C_LINE_SCL_HIGH : 0); +} + +/*****************************************************************************/ +/* Hooks */ + +/* Handle CPU clock changing frequency */ +static void i2c_freq_change(void) +{ + const struct i2c_port_t *p = i2c_ports; + int i; + + for (i = 0; i < i2c_ports_used; i++, p++) + i2c_set_freq_port(p); +} + +/* Handle an upcoming frequency change. */ +static void i2c_pre_freq_change_hook(void) +{ + const struct i2c_port_t *p = i2c_ports; + int i; + + /* Lock I2C ports so freq change can't interrupt an I2C transaction */ + for (i = 0; i < i2c_ports_used; i++, p++) + i2c_lock(p->port, 1); +} +DECLARE_HOOK(HOOK_PRE_FREQ_CHANGE, i2c_pre_freq_change_hook, HOOK_PRIO_DEFAULT); + +/* Handle a frequency change */ +static void i2c_freq_change_hook(void) +{ + const struct i2c_port_t *p = i2c_ports; + int i; + + i2c_freq_change(); + + /* Unlock I2C ports we locked in pre-freq change hook */ + for (i = 0; i < i2c_ports_used; i++, p++) + i2c_lock(p->port, 0); +} +DECLARE_HOOK(HOOK_FREQ_CHANGE, i2c_freq_change_hook, HOOK_PRIO_DEFAULT); + +/* Init all available i2c ports */ +static void i2c_init(void) +{ + const struct i2c_port_t *p = i2c_ports; + int i; + + for (i = 0; i < i2c_ports_used; i++, p++) + i2c_init_port(p); +} +DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C); diff --git a/common/i2c.c b/common/i2c.c index 2540a325c7..bd2e780466 100644 --- a/common/i2c.c +++ b/common/i2c.c @@ -755,14 +755,25 @@ static void scan_bus(int port, const char *desc) static int command_scan(int argc, char **argv) { - int i; + int port; + char *e; + + if (argc == 1) { + for (port = 0; port < i2c_ports_used; port++) + scan_bus(i2c_ports[port].port, i2c_ports[port].name); + return EC_SUCCESS; + } + + + port = strtoi(argv[1], &e, 0); + if ((*e) || (port >= i2c_ports_used)) + return EC_ERROR_PARAM2; - for (i = 0; i < i2c_ports_used; i++) - scan_bus(i2c_ports[i].port, i2c_ports[i].name); + scan_bus(i2c_ports[port].port, i2c_ports[port].name); return EC_SUCCESS; } DECLARE_CONSOLE_COMMAND(i2cscan, command_scan, - NULL, + "i2cscan [port]", "Scan I2C ports for devices", NULL); #endif |