summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaisuke Nojiri <dnojiri@chromium.org>2016-10-04 13:54:48 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-10-05 20:58:17 -0700
commitc0c66cdd12c79fc0d11a505b12b25fc8c1860dfd (patch)
treedf1037f794409bc162c7a075f37521017f2eec19
parent9b57c61ff7525b2ec6e0ca855663319d7e480889 (diff)
downloadchrome-ec-c0c66cdd12c79fc0d11a505b12b25fc8c1860dfd.tar.gz
stm32l4: Add i2c driver
This patch adds master and slave drivers for stm32l4 family. Only slave functionality is tested. BUG=none BRANCH=none TEST=Run cts.py -m i2c. Make buildall. Change-Id: Ied77081ca0333ab3fec055cd4f0fcbdf8a79d388 Signed-off-by: Daisuke Nojiri <dnojiri@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/393329 Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r--chip/stm32/i2c-stm32l4.c462
-rw-r--r--chip/stm32/registers.h7
-rw-r--r--include/i2c.h17
3 files changed, 484 insertions, 2 deletions
diff --git a/chip/stm32/i2c-stm32l4.c b/chip/stm32/i2c-stm32l4.c
new file mode 100644
index 0000000000..0e943767b0
--- /dev/null
+++ b/chip/stm32/i2c-stm32l4.c
@@ -0,0 +1,462 @@
+/* Copyright (c) 2013 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 "printf.h"
+#include "chipset.h"
+#include "clock.h"
+#include "common.h"
+#include "console.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "hwtimer.h"
+#include "i2c.h"
+#include "registers.h"
+
+#include "system.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)
+
+/* Transmit timeout in microseconds */
+#define I2C_TX_TIMEOUT_MASTER (10 * MSEC)
+
+#ifdef CONFIG_I2C_SLAVE_ADDR
+#define I2C_SLAVE_ERROR_CODE 0xec
+#if (I2C_PORT_EC == STM32_I2C1_PORT)
+#define IRQ_SLAVE STM32_IRQ_I2C1
+#else
+#define IRQ_SLAVE STM32_IRQ_I2C2
+#endif
+#endif
+
+/* I2C port state data */
+struct i2c_port_data {
+ uint32_t timeout_us; /* Transaction timeout, or 0 to use default */
+ enum i2c_freq freq; /* Port clock speed */
+};
+static struct i2c_port_data pdata[I2C_PORT_COUNT];
+
+void i2c_set_timeout(int port, uint32_t timeout)
+{
+ pdata[port].timeout_us = timeout ? timeout : I2C_TX_TIMEOUT_MASTER;
+}
+
+/* timing register values for supported input clks / i2c clk rates */
+static const uint32_t busyloop_us[I2C_FREQ_COUNT] = {
+ [I2C_FREQ_1000KHZ] = 16, /* Enough for 2 bytes */
+ [I2C_FREQ_400KHZ] = 40, /* Enough for 2 bytes */
+ [I2C_FREQ_100KHZ] = 0, /* No busy looping at 100kHz (bus is slow) */
+};
+
+/**
+ * Wait for ISR register to contain the specified mask.
+ *
+ * Returns 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_isr(int port, int mask)
+{
+ uint32_t start = __hw_clock_source_read();
+ uint32_t delta = 0;
+
+ do {
+ int isr = STM32_I2C_ISR(port);
+
+ /* Check for errors */
+ if (isr & (STM32_I2C_ISR_ARLO | STM32_I2C_ISR_BERR |
+ STM32_I2C_ISR_NACK))
+ return EC_ERROR_UNKNOWN;
+
+ /* Check for desired mask */
+ if ((isr & mask) == mask)
+ return EC_SUCCESS;
+
+ delta = __hw_clock_source_read() - start;
+
+ /**
+ * Depending on the bus speed, busy loop for a while before
+ * sleeping and letting other things run.
+ */
+ if (delta >= busyloop_us[pdata[port].freq])
+ usleep(100);
+ } while (delta < pdata[port].timeout_us);
+
+ return EC_ERROR_TIMEOUT;
+}
+
+/* We are only using sysclk, which is 40MHZ */
+enum stm32_i2c_clk_src {
+ I2C_CLK_SRC_40MHZ = 0,
+ I2C_CLK_SRC_COUNT,
+};
+
+/* timing register values for supported input clks / i2c clk rates
+ *
+ * These values are calculated using ST's STM32cubeMX tool
+ */
+static const uint32_t timingr_regs[I2C_CLK_SRC_COUNT][I2C_FREQ_COUNT] = {
+ [I2C_CLK_SRC_40MHZ] = {
+ [I2C_FREQ_1000KHZ] = 0x00100618,
+ [I2C_FREQ_400KHZ] = 0x00301347,
+ [I2C_FREQ_100KHZ] = 0x003087FF,
+ },
+};
+
+static void i2c_set_freq_port(const struct i2c_port_t *p,
+ enum stm32_i2c_clk_src src,
+ enum i2c_freq freq)
+{
+ int port = p->port;
+ const uint32_t *regs = timingr_regs[src];
+
+ /* Disable port */
+ STM32_I2C_CR1(port) = 0;
+ STM32_I2C_CR2(port) = 0;
+ /* Set clock frequency */
+ STM32_I2C_TIMINGR(port) = regs[freq];
+ /* Enable port */
+ STM32_I2C_CR1(port) = STM32_I2C_CR1_PE;
+
+ pdata[port].freq = freq;
+}
+
+/**
+ * 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;
+ uint32_t mask;
+ uint8_t shift;
+ enum stm32_i2c_clk_src src = I2C_CLK_SRC_40MHZ;
+ enum i2c_freq freq;
+
+ /* Enable clocks to I2C modules if necessary */
+ if (!(STM32_RCC_APB1ENR & (1 << (21 + port))))
+ STM32_RCC_APB1ENR |= 1 << (21 + port);
+
+ /* Select sysclk as source */
+ mask = STM32_RCC_CCIPR_I2C1SEL_MASK << (port * 2);
+ shift = STM32_RCC_CCIPR_I2C1SEL_SHIFT + (port * 2);
+ STM32_RCC_CCIPR &= ~mask;
+ STM32_RCC_CCIPR |= STM32_RCC_CCIPR_I2C_SYSCLK << shift;
+
+ /* Configure GPIOs */
+ gpio_config_module(MODULE_I2C, 1);
+
+ /* Set clock frequency */
+ switch (p->kbps) {
+ case 1000:
+ freq = I2C_FREQ_1000KHZ;
+ break;
+ case 400:
+ freq = I2C_FREQ_400KHZ;
+ break;
+ case 100:
+ freq = I2C_FREQ_100KHZ;
+ break;
+ default: /* unknown speed, defaults to 100kBps */
+ CPRINTS("I2C bad speed %d kBps", p->kbps);
+ freq = I2C_FREQ_100KHZ;
+ }
+
+ /* Set up initial bus frequencies */
+ i2c_set_freq_port(p, src, freq);
+
+ /* Set up default timeout */
+ i2c_set_timeout(port, 0);
+}
+
+/*****************************************************************************/
+
+#ifdef CONFIG_I2C_SLAVE_ADDR
+
+static void i2c_event_handler(int port)
+{
+ /* Variables tracking the handler state.
+ * TODO: Should have as many sets of these variables as the number
+ * of slave ports.
+ */
+ static int rx_pending, rx_idx;
+ static int tx_pending, tx_idx, tx_end;
+ static uint8_t slave_buffer[I2C_MAX_HOST_PACKET_SIZE + 2];
+ int isr = STM32_I2C_ISR(port);
+
+ /*
+ * Check for error conditions. Note, arbitration loss and bus error
+ * are the only two errors we can get as a slave allowing clock
+ * stretching and in non-SMBus mode.
+ */
+ if (isr & (STM32_I2C_ISR_ARLO | STM32_I2C_ISR_BERR)) {
+ rx_pending = 0;
+ tx_pending = 0;
+
+ /* Make sure TXIS interrupt is disabled */
+ STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE;
+
+ /* Clear error status bits */
+ STM32_I2C_ICR(port) |= STM32_I2C_ICR_BERRCF
+ | STM32_I2C_ICR_ARLOCF;
+ }
+
+ /* Transfer matched our slave address */
+ if (isr & STM32_I2C_ISR_ADDR) {
+ if (isr & STM32_I2C_ISR_DIR) {
+ /* Transmitter slave */
+ /* Clear transmit buffer */
+ STM32_I2C_ISR(port) |= STM32_I2C_ISR_TXE;
+
+ if (rx_pending)
+ /* RESTART */
+ i2c_data_received(port, slave_buffer, rx_idx);
+ tx_end = i2c_set_response(port, slave_buffer, rx_idx);
+ tx_idx = 0;
+ rx_pending = 0;
+ tx_pending = 1;
+
+ /* Enable txis interrupt to start response */
+ STM32_I2C_CR1(port) |= STM32_I2C_CR1_TXIE;
+ } else {
+ /* Receiver slave */
+ rx_idx = 0;
+ rx_pending = 1;
+ tx_pending = 0;
+ }
+
+ /* Clear ADDR bit by writing to ADDRCF bit */
+ STM32_I2C_ICR(port) |= STM32_I2C_ICR_ADDRCF;
+ /* Inhibit stop mode when addressed until STOPF flag is set */
+ disable_sleep(SLEEP_MASK_I2C_SLAVE);
+ }
+
+ /*
+ * Receive buffer not empty
+ *
+ * When a master finishes sending data, it'll set STOP bit. It causes
+ * the slave to receive RXNE and STOP interrupt at the same time. So,
+ * we need to process RXNE first, then handle STOP.
+ */
+ if (isr & STM32_I2C_ISR_RXNE)
+ slave_buffer[rx_idx++] = STM32_I2C_RXDR(port);
+
+ /* Stop condition on bus */
+ if (isr & STM32_I2C_ISR_STOP) {
+ if (rx_pending)
+ i2c_data_received(port, slave_buffer, rx_idx);
+ tx_idx = 0;
+ tx_end = 0;
+ rx_pending = 0;
+ tx_pending = 0;
+ /* Make sure TXIS interrupt is disabled */
+ STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_TXIE;
+
+ /* Clear STOPF bit by writing to STOPCF bit */
+ STM32_I2C_ICR(port) |= STM32_I2C_ICR_STOPCF;
+
+ /* No longer inhibit deep sleep after stop condition */
+ enable_sleep(SLEEP_MASK_I2C_SLAVE);
+ }
+
+ if (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;
+ }
+
+ /* Transmitter empty event */
+ if (isr & STM32_I2C_ISR_TXIS) {
+ if (port == I2C_PORT_EC) {
+ if (tx_pending) {
+ if (tx_idx < tx_end) {
+ STM32_I2C_TXDR(port) =
+ slave_buffer[tx_idx++];
+ } else {
+ STM32_I2C_TXDR(port)
+ = I2C_SLAVE_ERROR_CODE;
+ tx_idx = 0;
+ tx_end = 0;
+ tx_pending = 0;
+ }
+ } else {
+ STM32_I2C_TXDR(port) = I2C_SLAVE_ERROR_CODE;
+ }
+ }
+ }
+}
+
+void i2c_event_interrupt(void)
+{
+ i2c_event_handler(I2C_PORT_EC);
+}
+DECLARE_IRQ(IRQ_SLAVE, i2c_event_interrupt, 2);
+#endif
+
+/*****************************************************************************/
+/* Interface */
+
+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 rv = EC_SUCCESS;
+ int i;
+ int xfer_start = flags & I2C_XFER_START;
+ int xfer_stop = flags & I2C_XFER_STOP;
+
+ ASSERT(out || !out_bytes);
+ ASSERT(in || !in_bytes);
+
+ /* Clear status */
+ 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: if we are stopping then set
+ * AUTOEND bit to automatically set STOP bit after NBYTES.
+ * if we are not stopping, set RELOAD bit so that we can load
+ * NBYTES again. if we are starting, then set START bit.
+ */
+ STM32_I2C_CR2(port) = ((out_bytes & 0xFF) << 16)
+ | slave_addr
+ | ((in_bytes == 0 && xfer_stop) ?
+ STM32_I2C_CR2_AUTOEND : 0)
+ | ((in_bytes == 0 && !xfer_stop) ?
+ STM32_I2C_CR2_RELOAD : 0)
+ | (xfer_start ? STM32_I2C_CR2_START : 0);
+
+ for (i = 0; i < out_bytes; i++) {
+ rv = wait_isr(port, STM32_I2C_ISR_TXIS);
+ if (rv)
+ goto xfer_exit;
+ /* Write next data byte */
+ STM32_I2C_TXDR(port) = out[i];
+ }
+ }
+ if (in_bytes) {
+ if (out_bytes) { /* wait for completion of the write */
+ rv = wait_isr(port, STM32_I2C_ISR_TC);
+ if (rv)
+ goto xfer_exit;
+ }
+ /*
+ * Configure the read transfer: if we are stopping then set
+ * AUTOEND bit to automatically set STOP bit after NBYTES.
+ * if we are not stopping, set RELOAD bit so that we can load
+ * NBYTES again. if we were just transmitting, we need to
+ * set START bit to send (re)start and begin read transaction.
+ */
+ STM32_I2C_CR2(port) = ((in_bytes & 0xFF) << 16)
+ | STM32_I2C_CR2_RD_WRN | slave_addr
+ | (xfer_stop ? STM32_I2C_CR2_AUTOEND : 0)
+ | (!xfer_stop ? STM32_I2C_CR2_RELOAD : 0)
+ | (out_bytes || xfer_start ? STM32_I2C_CR2_START : 0);
+
+ for (i = 0; i < in_bytes; i++) {
+ /* Wait for receive buffer not empty */
+ rv = wait_isr(port, STM32_I2C_ISR_RXNE);
+ if (rv)
+ goto xfer_exit;
+
+ in[i] = STM32_I2C_RXDR(port);
+ }
+ }
+
+ /*
+ * If we are stopping, then we already set AUTOEND and we should
+ * wait for the stop bit to be transmitted. Otherwise, we set
+ * the RELOAD bit and we should wait for transfer complete
+ * reload (TCR).
+ */
+ rv = wait_isr(port, xfer_stop ? STM32_I2C_ISR_STOP : STM32_I2C_ISR_TCR);
+ if (rv)
+ goto xfer_exit;
+
+xfer_exit:
+ /* clear status */
+ if (xfer_stop)
+ STM32_I2C_ICR(port) = STM32_I2C_ICR_ALL;
+
+ /* On error, queue a stop condition */
+ if (rv) {
+ /* queue a STOP condition */
+ STM32_I2C_CR2(port) |= STM32_I2C_CR2_STOP;
+ /* wait for it to take effect */
+ /* Wait up to 100 us for bus idle */
+ for (i = 0; i < 10; i++) {
+ if (!(STM32_I2C_ISR(port) & STM32_I2C_ISR_BUSY))
+ break;
+ udelay(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.
+ */
+ udelay(10);
+ /* re-initialize the controller */
+ STM32_I2C_CR2(port) = 0;
+ STM32_I2C_CR1(port) &= ~STM32_I2C_CR1_PE;
+ udelay(10);
+ STM32_I2C_CR1(port) |= STM32_I2C_CR1_PE;
+ }
+
+ return rv;
+}
+
+int i2c_raw_get_scl(int port)
+{
+ enum gpio_signal g;
+
+ if (get_scl_from_i2c_port(port, &g))
+ /* If no SCL pin is defined, return 1 to appear idle. */
+ return 1;
+
+ return gpio_get_level(g);
+}
+
+int i2c_raw_get_sda(int port)
+{
+ enum gpio_signal g;
+
+ if (get_sda_from_i2c_port(port, &g))
+ /* If no SDA pin is defined, return 1 to appear idle. */
+ return 1;
+
+ return gpio_get_level(g);
+}
+
+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);
+}
+
+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);
+
+#ifdef CONFIG_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_NACKIE;
+ STM32_I2C_OAR1(I2C_PORT_EC) = 0x8000 | CONFIG_I2C_SLAVE_ADDR;
+ task_enable_irq(IRQ_SLAVE);
+#endif
+}
+DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_INIT_I2C);
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index e0ece29939..9660e86273 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -490,7 +490,8 @@ typedef volatile struct timer_ctlr timer_ctlr_t;
#define stm32_i2c_reg(port, offset) \
((uint16_t *)((STM32_I2C1_BASE + ((port) * 0x400)) + (offset)))
-#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3)
+#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) \
+ || defined(CHIP_FAMILY_STM32L4)
#define STM32_I2C_CR1(n) REG32(stm32_i2c_reg(n, 0x00))
#define STM32_I2C_CR1_PE (1 << 0)
#define STM32_I2C_CR1_TXIE (1 << 1)
@@ -572,7 +573,9 @@ typedef volatile struct timer_ctlr timer_ctlr_t;
#define STM32_I2C_CCR_DUTY (1 << 14)
#define STM32_I2C_CCR_FM (1 << 15)
#define STM32_I2C_TRISE(n) REG16(stm32_i2c_reg(n, 0x20))
-#endif /* !CHIP_FAMILY_STM32F0 && !CHIP_FAMILY_STM32F3 */
+/* !CHIP_FAMILY_STM32F0 && !CHIP_FAMILY_STM32F3 && !CHIP_FAMILY_STM32L4 */
+#endif
+
#if defined(CHIP_FAMILY_STM32F4)
diff --git a/include/i2c.h b/include/i2c.h
index 3af738201b..ddb0508828 100644
--- a/include/i2c.h
+++ b/include/i2c.h
@@ -301,4 +301,21 @@ int i2c_port_to_controller(int port);
*/
int i2c_get_protocol_info(struct host_cmd_handler_args *args);
+/**
+ * Callbacks processing received data and response
+ *
+ * i2c_data_recived will be called when a slave finishes receiving data and
+ * i2c_set_response will be called when a slave is expected to send response.
+ *
+ * Using these, Chrome OS host command protocol should be separated from
+ * i2c slave drivers (e.g. i2c-stm32f0.c, i2c-stm32f3.c).
+ *
+ * @param port: I2C port number
+ * @param buf: Buffer containing received data on call and response on return
+ * @param len: Size of received data
+ * @return Size of response data
+ */
+void i2c_data_received(int port, uint8_t *buf, int len);
+int i2c_set_response(int port, uint8_t *buf, int len);
+
#endif /* __CROS_EC_I2C_H */