From 8f911f9285db6596e30d516a153aa0b24614eb5d Mon Sep 17 00:00:00 2001 From: Vincent Palatin Date: Fri, 21 Mar 2014 15:49:14 -0700 Subject: stm32: new I2C driver for STM32F0xx family The STM32F0xx family has a new I2C peripheral, let's add a new driver for it. Signed-off-by: Vincent Palatin BRANCH=none BUG=none TEST=On FruitPie, read TSU6721 registers and read the smart battery information. Change-Id: Ic49832205957559b4b6b647948b03f650a27ab04 Reviewed-on: https://chromium-review.googlesource.com/191210 Tested-by: Vincent Palatin Reviewed-by: Randall Spangler Commit-Queue: Vincent Palatin --- chip/stm32/build.mk | 4 +- chip/stm32/i2c-stm32f0.c | 259 +++++++++++++++++++++++++++++++++++++++++++++++ chip/stm32/registers.h | 29 ++++++ 3 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 chip/stm32/i2c-stm32f0.c diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index a4ce0f4bda..d878d8d0ad 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -20,8 +20,6 @@ endif # STM32F0xx and STM32F1xx are using the same flash controller FLASH_FAMILY=$(subst stm32f0,stm32f,$(CHIP_FAMILY)) -# STM32F0xx chips will re-use STM32L I2C code -I2C_FAMILY=$(subst stm32f0,stm32l,$(CHIP_FAMILY)) # Select between 16-bit and 32-bit timer for clock source TIMER_TYPE=$(if $(CONFIG_STM_HWTIMER32),32,) @@ -30,7 +28,7 @@ chip-y+=jtag-$(CHIP_FAMILY).o clock-$(CHIP_FAMILY).o chip-$(CONFIG_SPI)+=spi.o chip-$(CONFIG_COMMON_GPIO)+=gpio-$(CHIP_FAMILY).o chip-$(CONFIG_COMMON_TIMER)+=hwtimer$(TIMER_TYPE).o -chip-$(CONFIG_I2C)+=i2c-$(I2C_FAMILY).o +chip-$(CONFIG_I2C)+=i2c-$(CHIP_FAMILY).o chip-$(CONFIG_WATCHDOG)+=watchdog.o chip-$(HAS_TASK_CONSOLE)+=uart.o chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o diff --git a/chip/stm32/i2c-stm32f0.c b/chip/stm32/i2c-stm32f0.c new file mode 100644 index 0000000000..1cc0c04412 --- /dev/null +++ b/chip/stm32/i2c-stm32f0.c @@ -0,0 +1,259 @@ +/* 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 "gpio.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 CPRINTF(format, args...) cprintf(CC_I2C, format, ## args) + +/* Maximum transfer of a SMBUS block transfer */ +#define SMBUS_MAX_BLOCK 32 + +/* Transmit timeout in microseconds */ +#define I2C_TX_TIMEOUT_MASTER (10 * MSEC) + +/** + * 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) +{ + uint64_t timeout = get_time().val + I2C_TX_TIMEOUT_MASTER; + + while (get_time().val < timeout) { + int isr = STM32_I2C_ISR(port); + + /* Check for desired mask */ + if ((isr & mask) == mask) + return EC_SUCCESS; + + /* Check for errors */ + if (isr & (STM32_I2C_ISR_ARLO | STM32_I2C_ISR_BERR)) + return EC_ERROR_UNKNOWN; + + /* I2C is slow, so let other things run while we wait */ + usleep(100); + } + + return EC_ERROR_TIMEOUT; +} + +static void i2c_set_freq_port(const struct i2c_port_t *p) +{ + int port = p->port; + + /* Disable port */ + STM32_I2C_CR1(port) = 0; + STM32_I2C_CR2(port) = 0; + /* Set clock frequency */ + switch (p->kbps) { + case 1000: + STM32_I2C_TIMINGR(port) = 0x50110103; + break; + case 400: + STM32_I2C_TIMINGR(port) = 0x50330309; + break; + case 100: + STM32_I2C_TIMINGR(port) = 0xB0420F13; + break; + default: /* unknown speed, defaults to 100kBps */ + CPRINTF("Unsupported speed %d kBps\n", p->kbps); + STM32_I2C_TIMINGR(port) = 0xB0420F13; + } + /* 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; + + /* Enable clocks to I2C modules if necessary */ + if (!(STM32_RCC_APB1ENR & (1 << (21 + port)))) + STM32_RCC_APB1ENR |= 1 << (21 + port); + + /* Configure GPIOs */ + gpio_config_module(MODULE_I2C, 1); + + /* Set up initial bus frequencies */ + i2c_set_freq_port(p); +} + +/*****************************************************************************/ +/* 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 rv = EC_SUCCESS; + int i; + + ASSERT(out || !out_bytes); + ASSERT(in || !in_bytes); + + /* Clear status */ + STM32_I2C_ICR(port) = 0x3F38; + STM32_I2C_CR2(port) = 0; + + if (out_bytes || !in_bytes) { + /* Configure the write transfer */ + STM32_I2C_CR2(port) = ((out_bytes & 0xFF) << 16) + | slave_addr + | (in_bytes == 0 ? STM32_I2C_CR2_AUTOEND : 0); + /* let's go ... */ + STM32_I2C_CR2(port) |= STM32_I2C_CR2_START; + + 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 */ + STM32_I2C_CR2(port) = ((in_bytes & 0xFF) << 16) + | STM32_I2C_CR2_RD_WRN | slave_addr + | STM32_I2C_CR2_AUTOEND; + /* START or repeated start */ + STM32_I2C_CR2(port) |= STM32_I2C_CR2_START; + + 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); + } + } + rv = wait_isr(port, STM32_I2C_ISR_STOP); + if (rv) + goto xfer_exit; + +xfer_exit: + /* clear status */ + STM32_I2C_ICR(port) = 0x3F38; + /* 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) == 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 SCL 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); +} + +int i2c_read_string(int port, int slave_addr, int offset, uint8_t *data, + int len) +{ + int rv; + uint8_t reg, block_length; + + /* + * TODO(crosbug.com/p/23569): when i2c_xfer() supports start/stop bits, + * merge this with the LM4 implementation and move to i2c_common.c. + */ + + if ((len <= 0) || (len > SMBUS_MAX_BLOCK)) + return EC_ERROR_INVAL; + + i2c_lock(port, 1); + + /* Read the counted string into the output buffer */ + reg = offset; + rv = i2c_xfer(port, slave_addr, ®, 1, data, len, I2C_XFER_SINGLE); + if (rv == EC_SUCCESS) { + /* Block length is the first byte of the returned buffer */ + block_length = MIN(data[0], len - 1); + + /* Move data down, then null-terminate it */ + memmove(data, data + 1, block_length); + data[block_length] = 0; + } + + i2c_lock(port, 0); + return rv; +} + +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_DEFAULT); diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index 9a26a3020e..4507d68d71 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -390,6 +390,34 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define stm32_i2c_reg(port, offset) \ ((uint16_t *)((STM32_I2C1_BASE + ((port) * 0x400)) + (offset))) +#ifdef CHIP_FAMILY_STM32F0 +#define STM32_I2C_CR1(n) REG32(stm32_i2c_reg(n, 0x00)) +#define STM32_I2C_CR1_PE (1 << 0) +#define STM32_I2C_CR2(n) REG32(stm32_i2c_reg(n, 0x04)) +#define STM32_I2C_CR2_RD_WRN (1 << 10) +#define STM32_I2C_CR2_START (1 << 13) +#define STM32_I2C_CR2_STOP (1 << 14) +#define STM32_I2C_CR2_NACK (1 << 15) +#define STM32_I2C_CR2_RELOAD (1 << 24) +#define STM32_I2C_CR2_AUTOEND (1 << 25) +#define STM32_I2C_OAR1(n) REG32(stm32_i2c_reg(n, 0x08)) +#define STM32_I2C_OAR2(n) REG32(stm32_i2c_reg(n, 0x0C)) +#define STM32_I2C_TIMINGR(n) REG32(stm32_i2c_reg(n, 0x10)) +#define STM32_I2C_TIMEOUTR(n) REG32(stm32_i2c_reg(n, 0x14)) +#define STM32_I2C_ISR(n) REG32(stm32_i2c_reg(n, 0x18)) +#define STM32_I2C_ISR_TXIS (1 << 1) +#define STM32_I2C_ISR_RXNE (1 << 2) +#define STM32_I2C_ISR_NACK (1 << 4) +#define STM32_I2C_ISR_STOP (1 << 5) +#define STM32_I2C_ISR_TC (1 << 6) +#define STM32_I2C_ISR_BERR (1 << 8) +#define STM32_I2C_ISR_ARLO (1 << 9) +#define STM32_I2C_ISR_BUSY (1 << 15) +#define STM32_I2C_ICR(n) REG32(stm32_i2c_reg(n, 0x1C)) +#define STM32_I2C_PECR(n) REG32(stm32_i2c_reg(n, 0x20)) +#define STM32_I2C_RXDR(n) REG32(stm32_i2c_reg(n, 0x24)) +#define STM32_I2C_TXDR(n) REG32(stm32_i2c_reg(n, 0x28)) +#else /* !CHIP_FAMILY_STM32F0 */ #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) @@ -416,6 +444,7 @@ typedef volatile struct timer_ctlr timer_ctlr_t; #define STM32_I2C_CCR(n) REG16(stm32_i2c_reg(n, 0x1C)) #define STM32_I2C_TRISE(n) REG16(stm32_i2c_reg(n, 0x20)) +#endif /* !CHIP_FAMILY_STM32F0 */ /* --- Power / Reset / Clocks --- */ #define STM32_PWR_BASE 0x40007000 -- cgit v1.2.1