diff options
author | Randall Spangler <rspangler@chromium.org> | 2013-04-29 11:07:51 -0700 |
---|---|---|
committer | ChromeBot <chrome-bot@google.com> | 2013-04-30 14:49:07 -0700 |
commit | 312b884c42f5f6a6177e416f5b812b1556ac195f (patch) | |
tree | 70aabd3227bcc84f5c90fe725cc9ef52797e10a7 | |
parent | 980df549794785ebadd2f4c26323e3f1bf3d3b02 (diff) | |
download | chrome-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.mk | 2 | ||||
-rw-r--r-- | chip/stm32/i2c-stm32f100.c (renamed from chip/stm32/i2c.c) | 0 | ||||
l--------- | chip/stm32/i2c-stm32f10x.c | 1 | ||||
-rw-r--r-- | chip/stm32/i2c-stm32l15x.c | 389 | ||||
-rw-r--r-- | chip/stm32/registers.h | 14 | ||||
-rw-r--r-- | common/i2c_common.c | 8 |
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, ®, 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); } |