summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-04-29 11:07:51 -0700
committerChromeBot <chrome-bot@google.com>2013-04-30 14:49:07 -0700
commit312b884c42f5f6a6177e416f5b812b1556ac195f (patch)
tree70aabd3227bcc84f5c90fe725cc9ef52797e10a7
parent980df549794785ebadd2f4c26323e3f1bf3d3b02 (diff)
downloadchrome-ec-312b884c42f5f6a6177e416f5b812b1556ac195f.tar.gz
Split STM32 i2c implementation for STM32F vs STM32L
STM32L doesn't need the DMA-based workarounds needed by STM32F, since the STM32L I2C block isn't broken. DMA adds a lot of code overhead when transferring 2-3 bytes, and is implemented differently on STM32F vs STM32L so it doesn't even work on STM32L Add a simple polled I2C implementation for STM32L. This is not the final implementation, which will use interrupts, but for now it works, unlike the DMA-based version. BUG=chrome-os-partner:18969 BRANCH=none TEST=i2cscan on pit finds a device at 0x90 Change-Id: Ie2a6c9ac6f62b7fd3c35e313b4015e080d9f937a Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/49555
-rw-r--r--chip/stm32/build.mk2
-rw-r--r--chip/stm32/i2c-stm32f100.c (renamed from chip/stm32/i2c.c)0
l---------chip/stm32/i2c-stm32f10x.c1
-rw-r--r--chip/stm32/i2c-stm32l15x.c389
-rw-r--r--chip/stm32/registers.h14
-rw-r--r--common/i2c_common.c8
6 files changed, 409 insertions, 5 deletions
diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk
index b929958a54..b45f6b6929 100644
--- a/chip/stm32/build.mk
+++ b/chip/stm32/build.mk
@@ -12,7 +12,7 @@ CORE:=cortex-m
chip-y=dma.o hwtimer.o system.o uart.o
chip-y+=jtag-$(CHIP_VARIANT).o clock-$(CHIP_VARIANT).o gpio-$(CHIP_VARIANT).o
chip-$(CONFIG_SPI)+=spi.o
-chip-$(CONFIG_I2C)+=i2c.o
+chip-$(CONFIG_I2C)+=i2c-$(CHIP_VARIANT).o
chip-$(CONFIG_WATCHDOG)+=watchdog.o
chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o
chip-$(HAS_TASK_POWERLED)+=power_led.o
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c-stm32f100.c
index 8680f59521..8680f59521 100644
--- a/chip/stm32/i2c.c
+++ b/chip/stm32/i2c-stm32f100.c
diff --git a/chip/stm32/i2c-stm32f10x.c b/chip/stm32/i2c-stm32f10x.c
new file mode 120000
index 0000000000..26f2e35b1b
--- /dev/null
+++ b/chip/stm32/i2c-stm32f10x.c
@@ -0,0 +1 @@
+i2c-stm32f100.c \ No newline at end of file
diff --git a/chip/stm32/i2c-stm32l15x.c b/chip/stm32/i2c-stm32l15x.c
new file mode 100644
index 0000000000..c96b4e76e1
--- /dev/null
+++ b/chip/stm32/i2c-stm32l15x.c
@@ -0,0 +1,389 @@
+/* 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 "chipset.h"
+#include "clock.h"
+#include "common.h"
+#include "console.h"
+#include "dma.h"
+#include "gpio.h"
+#include "hooks.h"
+#include "host_command.h"
+#include "i2c.h"
+#include "i2c_arbitration.h"
+#include "registers.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/* #define CONFIG_DEBUG_I2C */
+
+extern const struct i2c_port_t i2c_ports[I2C_PORTS_USED];
+
+/* Console output macros */
+#define CPUTS(outstr) cputs(CC_I2C, outstr)
+#define CPRINTF(format, args...) cprintf(CC_I2C, format, ## args)
+
+#define I2C1 STM32_I2C1_PORT
+#define I2C2 STM32_I2C2_PORT
+
+/* Maximum transfer of a SMBUS block transfer */
+#define SMBUS_MAX_BLOCK 32
+
+/*
+ * Transmit timeout in microseconds
+ *
+ * In theory we shouldn't have a timeout here (at least when we're in slave
+ * mode). The slave is supposed to wait forever for the master to read bytes.
+ * ...but we're going to keep the timeout to make sure we're robust. It may in
+ * fact be needed if the host resets itself mid-read.
+ */
+#define I2C_TX_TIMEOUT_MASTER (10 * MSEC)
+
+#ifdef CONFIG_DEBUG_I2C
+static void dump_i2c_reg(int port, const char *what)
+{
+ CPRINTF("[%T i2c CR1=%04x CR2=%04x SR1=%04x SR2=%04x %s]\n",
+ STM32_I2C_CR1(port),
+ STM32_I2C_CR2(port),
+ STM32_I2C_SR1(port),
+ STM32_I2C_SR2(port),
+ what);
+}
+#else
+static inline void dump_i2c_reg(int port, const char *what)
+{
+}
+#endif
+
+/**
+ * Wait for SR1 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_sr1(int port, int mask)
+{
+ uint64_t timeout = get_time().val + I2C_TX_TIMEOUT_MASTER;
+
+ while (get_time().val < timeout) {
+ int sr1 = STM32_I2C_SR1(port);
+
+ /* Check for desired mask */
+ if ((sr1 & mask) == mask)
+ return EC_SUCCESS;
+
+ /* Check for errors */
+ if (sr1 & (STM32_I2C_SR1_ARLO | STM32_I2C_SR1_BERR |
+ STM32_I2C_SR1_AF))
+ return EC_ERROR_UNKNOWN;
+
+ /* I2C is slow, so let other things run while we wait */
+ usleep(100);
+ }
+
+ /* TODO: on error or timeout, reset port */
+
+ return EC_ERROR_TIMEOUT;
+}
+
+/**
+ * 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;
+ dump_i2c_reg(port, "sent start");
+ rv = wait_sr1(port, STM32_I2C_SR1_SB);
+ if (rv)
+ return rv;
+
+ /* Write slave address */
+ STM32_I2C_DR(port) = slave_addr & 0xff;
+ dump_i2c_reg(port, "wrote addr");
+ rv = wait_sr1(port, STM32_I2C_SR1_ADDR);
+ if (rv)
+ return rv;
+
+ /* Read SR2 to clear ADDR bit */
+ rv = STM32_I2C_SR2(port);
+
+ return EC_SUCCESS;
+}
+
+/*****************************************************************************/
+/* Interface */
+
+int 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;
+
+ ASSERT(out || !out_bytes);
+ ASSERT(in || !in_bytes);
+
+ /* Clear status */
+ /*
+ * TODO: should check for any leftover error status, and reset the
+ * port if present.
+ *
+ * Also, may need to wait a bit if a previous STOP hasn't finished
+ * sending yet.
+ */
+ STM32_I2C_SR1(port) = 0;
+
+ /* Clear start and stop bits */
+ STM32_I2C_CR1(port) &= ~(STM32_I2C_CR1_START | STM32_I2C_CR1_STOP);
+
+ dump_i2c_reg(port, "xfer start");
+
+ if (out_bytes) {
+ if (!started) {
+ rv = send_start(port, slave_addr);
+ if (rv)
+ return rv;
+ }
+
+ /* Write data, if any */
+ for (i = 0; i < out_bytes; i++) {
+ /* Write next data byte */
+ STM32_I2C_DR(port) = out[i];
+ dump_i2c_reg(port, "wrote data");
+
+ rv = wait_sr1(port, STM32_I2C_SR1_BTF);
+ if (rv)
+ return rv;
+ }
+
+ /* Need repeated start condition before reading */
+ started = 0;
+
+ /* 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) {
+ if (!started) {
+ rv = send_start(port, slave_addr | 0x01);
+ if (rv)
+ return rv;
+ }
+
+ /* Read data, if any */
+ for (i = 0; i < in_bytes; i++) {
+ /* Wait for receive buffer not empty */
+ rv = wait_sr1(port, STM32_I2C_SR1_RXNE);
+ if (rv)
+ return rv;
+
+ dump_i2c_reg(port, "read data");
+
+ /* If this is the last byte, queue stop condition */
+ if (i == in_bytes - 1 && (flags & I2C_XFER_STOP))
+ STM32_I2C_CR1(port) |= STM32_I2C_CR1_STOP;
+
+ in[i] = STM32_I2C_DR(port);
+ dump_i2c_reg(port, "post read data");
+ }
+ }
+
+ /* Success */
+ return EC_SUCCESS;
+}
+
+int i2c_get_line_levels(int port)
+{
+ enum gpio_signal sda, scl;
+
+ ASSERT(port == I2C1 || port == I2C2);
+
+ if (port == I2C1) {
+ sda = GPIO_I2C1_SDA;
+ scl = GPIO_I2C1_SCL;
+ } else {
+ sda = GPIO_I2C2_SDA;
+ scl = GPIO_I2C2_SCL;
+ }
+
+ return (gpio_get_level(sda) ? I2C_LINE_SDA_HIGH : 0) |
+ (gpio_get_level(scl) ? I2C_LINE_SCL_HIGH : 0);
+}
+
+int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data,
+ int len)
+{
+ int rv;
+ uint8_t reg, block_length;
+
+ /*
+ * TODO: when i2c_xfer() supports start/stop bits, won't need a temp
+ * buffer, and this code can merge with the LM4 implementation and
+ * move to i2c_common.c.
+ */
+ uint8_t buffer[SMBUS_MAX_BLOCK + 1];
+
+ if ((len <= 0) || (len > SMBUS_MAX_BLOCK))
+ return EC_ERROR_INVAL;
+
+ i2c_lock(port, 1);
+
+ reg = offset;
+ rv = i2c_xfer(port, slave_addr, &reg, 1, buffer, SMBUS_MAX_BLOCK + 1,
+ I2C_XFER_SINGLE);
+ if (rv == EC_SUCCESS) {
+ /* Block length is the first byte of the returned buffer */
+ block_length = MIN(buffer[0], len - 1);
+ buffer[block_length + 1] = 0;
+
+ memcpy(data, buffer+1, block_length + 1);
+ }
+
+ i2c_lock(port, 0);
+ return rv;
+}
+
+/*****************************************************************************/
+/* Hooks */
+
+static void i2c_init(void)
+{
+ const struct i2c_port_t *p = i2c_ports;
+ int i;
+
+ for (i = 0; i < I2C_PORTS_USED; i++, p++) {
+ int port = p->port;
+
+ /* Enable clock if necessary */
+ if (!(STM32_RCC_APB1ENR & (1 << (21 + port)))) {
+ /* TODO: unwedge bus if necessary */
+ STM32_RCC_APB1ENR |= 1 << (21 + port);
+ }
+
+ /* Force peripheral reset and disable port */
+ STM32_I2C_CR1(port) = STM32_I2C_CR1_SWRST;
+ STM32_I2C_CR1(port) = 0;
+
+ /* Set clock frequency */
+ STM32_I2C_CCR(port) = CPU_CLOCK / (2 * 1000 * p->kbps);
+ STM32_I2C_CR2(port) = CPU_CLOCK / 1000000;
+ STM32_I2C_TRISE(port) = CPU_CLOCK / 1000000 + 1;
+
+ /* Enable port */
+ STM32_I2C_CR1(port) |= STM32_I2C_CR1_PE;
+
+ /* TODO: enable interrupts using I2C_CR2 bits 8,9 */
+ }
+}
+DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);
+
+/*****************************************************************************/
+/* Console commands */
+
+static int command_i2c(int argc, char **argv)
+{
+ int rw = 0;
+ int slave_addr, offset;
+ int value = 0;
+ char *e;
+ int rv = 0;
+
+ if (argc < 4) {
+ ccputs("Usage: i2c r/r16/w/w16 slave_addr offset [value]\n");
+ return EC_ERROR_UNKNOWN;
+ }
+
+ if (strcasecmp(argv[1], "r") == 0) {
+ rw = 0;
+ } else if (strcasecmp(argv[1], "r16") == 0) {
+ rw = 1;
+ } else if (strcasecmp(argv[1], "w") == 0) {
+ rw = 2;
+ } else if (strcasecmp(argv[1], "w16") == 0) {
+ rw = 3;
+ } else {
+ ccputs("Invalid rw mode : r / w / r16 / w16\n");
+ return EC_ERROR_INVAL;
+ }
+
+ slave_addr = strtoi(argv[2], &e, 0);
+ if (*e) {
+ ccputs("Invalid slave_addr\n");
+ return EC_ERROR_INVAL;
+ }
+
+ offset = strtoi(argv[3], &e, 0);
+ if (*e) {
+ ccputs("Invalid addr\n");
+ return EC_ERROR_INVAL;
+ }
+
+ if (rw > 1) {
+ if (argc < 5) {
+ ccputs("No write value\n");
+ return EC_ERROR_INVAL;
+ }
+ value = strtoi(argv[4], &e, 0);
+ if (*e) {
+ ccputs("Invalid write value\n");
+ return EC_ERROR_INVAL;
+ }
+ }
+
+
+ switch (rw) {
+ case 0:
+ rv = i2c_read8(I2C_PORT_HOST, slave_addr, offset, &value);
+ break;
+ case 1:
+ rv = i2c_read16(I2C_PORT_HOST, slave_addr, offset, &value);
+ break;
+ case 2:
+ rv = i2c_write8(I2C_PORT_HOST, slave_addr, offset, value);
+ break;
+ case 3:
+ rv = i2c_write16(I2C_PORT_HOST, slave_addr, offset, value);
+ break;
+ }
+
+
+ if (rv) {
+ ccprintf("i2c command failed\n", rv);
+ return rv;
+ }
+
+ if (rw == 0)
+ ccprintf("0x%02x [%d]\n", value);
+ else if (rw == 1)
+ ccprintf("0x%04x [%d]\n", value);
+
+ ccputs("ok\n");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(i2cxfer, command_i2c,
+ "r/r16/w/w16 slave_addr offset [value]",
+ "Read write I2C",
+ NULL);
+
+static int command_i2cdump(int argc, char **argv)
+{
+ dump_i2c_reg(I2C_PORT_HOST, "dump");
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(i2cdump, command_i2cdump,
+ NULL,
+ "Dump I2C regs",
+ NULL);
diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h
index b5ed731890..3155407200 100644
--- a/chip/stm32/registers.h
+++ b/chip/stm32/registers.h
@@ -277,11 +277,25 @@ struct timer_ctlr {
((uint16_t *)((STM32_I2C1_BASE + ((port) * 0x400)) + (offset)))
#define STM32_I2C_CR1(n) REG16(stm32_i2c_reg(n, 0x00))
+#define STM32_I2C_CR1_PE (1 << 0)
+#define STM32_I2C_CR1_START (1 << 8)
+#define STM32_I2C_CR1_STOP (1 << 9)
+#define STM32_I2C_CR1_ACK (1 << 10)
+#define STM32_I2C_CR1_SWRST (1 << 15)
#define STM32_I2C_CR2(n) REG16(stm32_i2c_reg(n, 0x04))
#define STM32_I2C_OAR1(n) REG16(stm32_i2c_reg(n, 0x08))
#define STM32_I2C_OAR2(n) REG16(stm32_i2c_reg(n, 0x0C))
#define STM32_I2C_DR(n) REG16(stm32_i2c_reg(n, 0x10))
#define STM32_I2C_SR1(n) REG16(stm32_i2c_reg(n, 0x14))
+#define STM32_I2C_SR1_SB (1 << 0)
+#define STM32_I2C_SR1_ADDR (1 << 1)
+#define STM32_I2C_SR1_BTF (1 << 2)
+#define STM32_I2C_SR1_RXNE (1 << 6)
+#define STM32_I2C_SR1_TXE (1 << 7)
+#define STM32_I2C_SR1_BERR (1 << 8)
+#define STM32_I2C_SR1_ARLO (1 << 9)
+#define STM32_I2C_SR1_AF (1 << 10)
+
#define STM32_I2C_SR2(n) REG16(stm32_i2c_reg(n, 0x18))
#define STM32_I2C_CCR(n) REG16(stm32_i2c_reg(n, 0x1C))
#define STM32_I2C_TRISE(n) REG16(stm32_i2c_reg(n, 0x20))
diff --git a/common/i2c_common.c b/common/i2c_common.c
index 24159c0b16..e3f4293a6a 100644
--- a/common/i2c_common.c
+++ b/common/i2c_common.c
@@ -185,10 +185,7 @@ static void scan_bus(int port, const char *desc)
watchdog_reload(); /* Otherwise a full scan trips watchdog */
ccputs(".");
-#ifdef CHIP_lm4
- /* Do a single read */
- if (!i2c_xfer(port, a, NULL, 0, &tmp, 1, I2C_XFER_SINGLE))
-#else
+#if defined(CHIP_VARIANT_stm32f100) || defined(CHIP_VARIANT_stm32f10x)
/*
* Hope that address 0 exists, because the i2c_xfer()
* implementation on STM32 can't read a byte without writing
@@ -198,6 +195,9 @@ static void scan_bus(int port, const char *desc)
*/
tmp = 0;
if (!i2c_xfer(port, a, &tmp, 1, &tmp, 1, I2C_XFER_SINGLE))
+#else
+ /* Do a single read */
+ if (!i2c_xfer(port, a, NULL, 0, &tmp, 1, I2C_XFER_SINGLE))
#endif
ccprintf("\n 0x%02x", a);
}