diff options
author | David Hendricks <dhendrix@chromium.org> | 2012-03-29 14:16:43 -0700 |
---|---|---|
committer | Simon Glass <sjg@chromium.org> | 2012-04-13 12:26:00 -0700 |
commit | 82609a3c726437c95e234f3f91a7c1b32a5d5493 (patch) | |
tree | c6fcf672ee7b66fb4186a72e1079f0ddd4c3fca5 | |
parent | 9fa4246a8d736d33b7ed0eee7084b952177ca6db (diff) | |
download | chrome-ec-82609a3c726437c95e234f3f91a7c1b32a5d5493.tar.gz |
stm32l: add i2c driver
(Derived from Vincent's original I2C slave CL)
This configures I2C port 2 as a slave device at 8-bit address 0xEC
(7-bit address is 0x76).
This CL only implements single-byte read and write transactions.
A master-initiated write will set EC mode, and a master-initiated read
will send bytes back depending on the mode.
BUG=chromium-os:28925
TEST=build on daisy and discovery; run on daisy
Change-Id: I1e9f28feb99e25bb7656b6e9ae8643d3ae285a28
Signed-off-by: David Hendricks <dhendrix@chromium.org>
Signed-off-by: Simon Glass <sjg@chromium.org>
-rw-r--r-- | chip/stm32l/build.mk | 1 | ||||
-rw-r--r-- | chip/stm32l/i2c.c | 232 | ||||
-rw-r--r-- | chip/stm32l/registers.h | 29 |
3 files changed, 250 insertions, 12 deletions
diff --git a/chip/stm32l/build.mk b/chip/stm32l/build.mk index 976f1565f2..cf2769480e 100644 --- a/chip/stm32l/build.mk +++ b/chip/stm32l/build.mk @@ -10,5 +10,6 @@ CORE:=cortex-m chip-y=clock.o dma.o gpio.o hwtimer.o jtag.o system.o uart.o chip-$(CONFIG_SPI)+=spi.o +chip-$(CONFIG_I2C)+=i2c.o chip-$(CONFIG_TASK_WATCHDOG)+=watchdog.o chip-$(CONFIG_TASK_KEYSCAN)+=keyboard_scan.o diff --git a/chip/stm32l/i2c.c b/chip/stm32l/i2c.c new file mode 100644 index 0000000000..98d15525fa --- /dev/null +++ b/chip/stm32l/i2c.c @@ -0,0 +1,232 @@ +/* Copyright (c) 2012 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 "board.h" +#include "common.h" +#include "console.h" +#include "gpio.h" +#include "i2c.h" +#include <message.h> +#include "registers.h" +#include "task.h" +#include "uart.h" + +/* 8-bit I2C slave address */ +#define I2C_ADDRESS 0xec + +/* I2C bus frequency */ +#define I2C_FREQ 100000 /* Hz */ + +/* Clock divider for I2C controller */ +#define I2C_CCR (CPU_CLOCK/(2 * I2C_FREQ)) + +#define NUM_PORTS 2 +#define I2C1 STM32L_I2C1_PORT +#define I2C2 STM32L_I2C2_PORT + +static task_id_t task_waiting_on_port[NUM_PORTS]; +static struct mutex port_mutex[NUM_PORTS]; + +static uint16_t i2c_sr1[NUM_PORTS]; + +/* per-transaction counters */ +static unsigned int tx_byte_count; +static unsigned int rx_byte_count; + +/* i2c_xmit_mode determines what EC sends when AP initiates a + read transaction */ +static enum message_cmd_t i2c_xmit_mode[NUM_PORTS]; + +/* + * Our output buffers. These must be large enough for our largest message, + * including protocol overhead. + */ +static uint8_t out_msg[32]; + + +static void wait_rx(int port) +{ + /* TODO: Add timeouts and error checking for safety */ + while (!(STM32L_I2C_SR1(port) & (1 << 6))) + ; +} + +static void wait_tx(int port) +{ + /* TODO: Add timeouts and error checking for safety */ + while (!(STM32L_I2C_SR1(port) & (1 << 7))) + ; +} + +static int i2c_read_raw(int port, void *buf, int len) +{ + int i; + uint8_t *data = buf; + + mutex_lock(&port_mutex[port]); + rx_byte_count = 0; + for (i = 0; i < len; i++) { + wait_rx(port); + data[i] = STM32L_I2C_DR(port); + rx_byte_count++; + } + mutex_unlock(&port_mutex[port]); + + return len; +} + +static int i2c_write_raw(int port, void *buf, int len) +{ + int i; + uint8_t *data = buf; + + mutex_lock(&port_mutex[port]); + tx_byte_count = 0; + for (i = 0; i < len; i++) { + tx_byte_count++; + STM32L_I2C_DR(port) = data[i]; + wait_tx(port); + } + mutex_unlock(&port_mutex[port]); + + return len; +} + +void i2c2_work_task(void) +{ + int msg_len; + uint16_t tmp16; + task_waiting_on_port[1] = task_get_current(); + + while (1) { + task_wait_event(-1); + tmp16 = i2c_sr1[I2C2]; + if (tmp16 & (1 << 6)) { + /* RxNE; AP issued write command */ + i2c_read_raw(I2C2, &i2c_xmit_mode[I2C2], 1); +#ifdef CONFIG_DEBUG + uart_printf("%s: i2c2_xmit_mode: %02x\n", + __func__, i2c_xmit_mode[I2C2]); +#endif + } else if (tmp16 & (1 << 7)) { + /* RxE; AP is waiting for EC response */ + msg_len = message_process_cmd(i2c_xmit_mode[I2C2], + out_msg, sizeof(out_msg)); + if (msg_len > 0) { + i2c_write_raw(I2C2, out_msg, sizeof(out_msg)); + } else { + uart_printf("%s: unexpected mode %u\n", + __func__, i2c_xmit_mode[I2C2]); + } + } + } +} + +static void i2c_event_handler(int port) +{ + + /* save and clear status */ + i2c_sr1[port] = STM32L_I2C_SR1(port); + STM32L_I2C_SR1(port) = 0; + + /* transfer matched our slave address */ + if (i2c_sr1[port] & (1 << 1)) { + /* cleared by reading SR1 followed by reading SR2 */ + STM32L_I2C_SR1(port); + STM32L_I2C_SR2(port); +#ifdef CONFIG_DEBUG + uart_printf("%s: ADDR\n", __func__); +#endif + } else if (i2c_sr1[port] & (1 << 2)) { + ; +#ifdef CONFIG_DEBUG + uart_printf("%s: BTF\n", __func__); +#endif + } else if (i2c_sr1[port] & (1 << 4)) { + /* clear STOPF bit by reading SR1 and then writing CR1 */ + STM32L_I2C_SR1(port); + STM32L_I2C_CR1(port) = STM32L_I2C_CR1(port); +#ifdef CONFIG_DEBUG + uart_printf("%s: STOPF\n", __func__); +#endif + } else { + ; +#ifdef CONFIG_DEBUG + uart_printf("%s: unknown event\n", __func__); +#endif + } + + /* RxNE or TxE, wake the worker task */ + if (i2c_sr1[port] & ((1 << 6) | (1 << 7))) { + if (port == I2C2) + task_wake(TASK_ID_I2C2_WORK); + } +} +static void i2c2_event_interrupt(void) { i2c_event_handler(I2C2); } +DECLARE_IRQ(STM32L_IRQ_I2C2_EV, i2c2_event_interrupt, 3); + +static void i2c_error_handler(int port) +{ + i2c_sr1[port] = STM32L_I2C_SR1(port); + +#ifdef CONFIG_DEBUG + if (i2c_sr1[port] & 1 << 10) { + /* ACK failed (NACK); expected when AP reads final byte. + * Software must clear AF bit. */ + uart_printf("%s: AF detected\n", __func__); + } + uart_printf("%s: tx byte count: %u, rx_byte_count: %u\n", + __func__, tx_byte_count, rx_byte_count); + uart_printf("%s: I2C_SR1(%s): 0x%04x\n", __func__, port, i2c_sr1[port]); + uart_printf("%s: I2C_SR2(%s): 0x%04x\n", + __func__, port, STM32L_I2C_SR2(port)); +#endif + + STM32L_I2C_SR1(port) &= ~0xdf00; +} +static void i2c2_error_interrupt(void) { i2c_error_handler(I2C2); } +DECLARE_IRQ(STM32L_IRQ_I2C2_ER, i2c2_error_interrupt, 2); + +static int i2c_init2(void) +{ + int i; + + /* enable I2C2 clock */ + STM32L_RCC_APB1ENR |= 1 << 22; + + /* set clock configuration : standard mode (100kHz) */ + STM32L_I2C_CCR(I2C2) = I2C_CCR; + + /* set slave address */ + STM32L_I2C_OAR1(I2C2) = I2C_ADDRESS; + + /* configuration : I2C mode / Periphal enabled, ACK enabled */ + STM32L_I2C_CR1(I2C2) = (1 << 10) | (1 << 0); + /* error and event interrupts enabled / input clock is 16Mhz */ + STM32L_I2C_CR2(I2C2) = (1 << 9) | (1 << 8) | 0x10; + + /* clear status */ + STM32L_I2C_SR1(I2C2) = 0; + + /* No tasks are waiting on ports */ + for (i = 0; i < NUM_PORTS; i++) + task_waiting_on_port[i] = TASK_ID_INVALID; + + /* enable event and error interrupts */ + task_enable_irq(STM32L_IRQ_I2C2_EV); + task_enable_irq(STM32L_IRQ_I2C2_ER); + + uart_printf("done\n"); + return EC_SUCCESS; +} + +int i2c_init(void) +{ + int rc = 0; + + /* FIXME: Add #defines to determine which channels to init */ + rc |= i2c_init2(); + return rc; +} diff --git a/chip/stm32l/registers.h b/chip/stm32l/registers.h index 634adeaa00..c27f4e3cc1 100644 --- a/chip/stm32l/registers.h +++ b/chip/stm32l/registers.h @@ -175,18 +175,23 @@ #define STM32L_I2C1_BASE 0x40005400 #define STM32L_I2C2_BASE 0x40005800 -#define STM32L_I2C_REG(n, offset) \ - REG16(STM32L_CAT(STM32L_I2C, n, _BASE) + (offset)) - -#define STM32L_I2C_CR1(n) STM32L_I2C_REG(n, 0x00) -#define STM32L_I2C_CR2(n) STM32L_I2C_REG(n, 0x04) -#define STM32L_I2C_OAR1(n) STM32L_I2C_REG(n, 0x08) -#define STM32L_I2C_OAR2(n) STM32L_I2C_REG(n, 0x0C) -#define STM32L_I2C_DR(n) STM32L_I2C_REG(n, 0x10) -#define STM32L_I2C_SR1(n) STM32L_I2C_REG(n, 0x14) -#define STM32L_I2C_SR2(n) STM32L_I2C_REG(n, 0x18) -#define STM32L_I2C_CCR(n) STM32L_I2C_REG(n, 0x1C) -#define STM32L_I2C_TRISE(n) STM32L_I2C_REG(n, 0x20) +#define STM32L_I2C1_PORT 0 +#define STM32L_I2C2_PORT 1 + +static inline uint16_t *stm32l_i2c_reg(int port, int offset) +{ + return (uint16_t *)((STM32L_I2C1_BASE + (port * 0x400)) + offset); +} + +#define STM32L_I2C_CR1(n) REG16(stm32l_i2c_reg(n, 0x00)) +#define STM32L_I2C_CR2(n) REG16(stm32l_i2c_reg(n, 0x04)) +#define STM32L_I2C_OAR1(n) REG16(stm32l_i2c_reg(n, 0x08)) +#define STM32L_I2C_OAR2(n) REG16(stm32l_i2c_reg(n, 0x0C)) +#define STM32L_I2C_DR(n) REG16(stm32l_i2c_reg(n, 0x10)) +#define STM32L_I2C_SR1(n) REG16(stm32l_i2c_reg(n, 0x14)) +#define STM32L_I2C_SR2(n) REG16(stm32l_i2c_reg(n, 0x18)) +#define STM32L_I2C_CCR(n) REG16(stm32l_i2c_reg(n, 0x1C)) +#define STM32L_I2C_TRISE(n) REG16(stm32l_i2c_reg(n, 0x20)) /* --- Power / Reset / Clocks --- */ #define STM32L_PWR_BASE 0x40007000 |