summaryrefslogtreecommitdiff
path: root/chip/stm32/i2c-stm32f.c
diff options
context:
space:
mode:
Diffstat (limited to 'chip/stm32/i2c-stm32f.c')
-rw-r--r--chip/stm32/i2c-stm32f.c980
1 files changed, 980 insertions, 0 deletions
diff --git a/chip/stm32/i2c-stm32f.c b/chip/stm32/i2c-stm32f.c
new file mode 100644
index 0000000000..3cdcbe84fd
--- /dev/null
+++ b/chip/stm32/i2c-stm32f.c
@@ -0,0 +1,980 @@
+/* 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"
+
+/* 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
+
+/* 8-bit I2C slave address */
+#define I2C_ADDRESS 0x3c
+
+/* I2C bus frequency */
+#define I2C_FREQ 100000 /* Hz */
+
+/* I2C bit period in microseconds */
+#define I2C_PERIOD_US (SECOND / I2C_FREQ)
+
+/* Clock divider for I2C controller */
+#define I2C_CCR (CPU_CLOCK / (2 * I2C_FREQ))
+
+/*
+ * 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_SLAVE (100 * MSEC)
+#define I2C_TX_TIMEOUT_MASTER (10 * MSEC)
+
+/*
+ * We delay 5us in bitbang mode. That gives us 5us low and 5us high or
+ * a frequency of 100kHz.
+ *
+ * Note that the code takes a little time to run so we don't actually get
+ * 100kHz, but that's OK.
+ */
+#define I2C_BITBANG_DELAY_US 5
+
+#define I2C1 STM32_I2C1_PORT
+#define I2C2 STM32_I2C2_PORT
+
+/* Select the DMA channels matching the board configuration */
+#define DMAC_SLAVE_TX \
+ ((I2C_PORT_SLAVE) ? STM32_DMAC_I2C2_TX : STM32_DMAC_I2C1_TX)
+#define DMAC_SLAVE_RX \
+ ((I2C_PORT_SLAVE) ? STM32_DMAC_I2C2_RX : STM32_DMAC_I2C1_RX)
+#define DMAC_HOST_TX \
+ ((I2C_PORT_HOST) ? STM32_DMAC_I2C2_TX : STM32_DMAC_I2C1_TX)
+#define DMAC_HOST_RX \
+ ((I2C_PORT_HOST) ? STM32_DMAC_I2C2_RX : STM32_DMAC_I2C1_RX)
+
+enum {
+ /*
+ * A stop condition should take 2 clocks, but the process may need more
+ * time to notice if it is preempted, so we poll repeatedly for 8
+ * clocks, before backing off and only check once every
+ * STOP_SENT_RETRY_US for up to TIMEOUT_STOP_SENT clocks before giving
+ * up.
+ */
+ SLOW_STOP_SENT_US = I2C_PERIOD_US * 8,
+ TIMEOUT_STOP_SENT_US = I2C_PERIOD_US * 200,
+ STOP_SENT_RETRY_US = 150,
+};
+
+static const struct dma_option dma_tx_option[I2C_PORT_COUNT] = {
+ {STM32_DMAC_I2C1_TX, (void *)&STM32_I2C_DR(I2C1),
+ STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT},
+ {STM32_DMAC_I2C2_TX, (void *)&STM32_I2C_DR(I2C2),
+ STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT},
+};
+
+static const struct dma_option dma_rx_option[I2C_PORT_COUNT] = {
+ {STM32_DMAC_I2C1_RX, (void *)&STM32_I2C_DR(I2C1),
+ STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT},
+ {STM32_DMAC_I2C2_RX, (void *)&STM32_I2C_DR(I2C2),
+ STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_16_BIT},
+};
+
+static uint16_t i2c_sr1[I2C_PORT_COUNT];
+
+/* Buffer for host commands (including version, error code and checksum) */
+static uint8_t host_buffer[EC_HOST_PARAM_SIZE + 4];
+static struct host_cmd_handler_args host_cmd_args;
+static uint8_t i2c_old_response; /* Send an old-style response */
+
+/* Flag indicating if a command is currently in the buffer */
+static uint8_t rx_pending;
+
+static inline void disable_i2c_interrupt(int port)
+{
+ STM32_I2C_CR2(port) &= ~(3 << 8);
+}
+
+static inline void enable_i2c_interrupt(int port)
+{
+ STM32_I2C_CR2(port) |= 3 << 8;
+}
+
+static inline void enable_ack(int port)
+{
+ STM32_I2C_CR1(port) |= (1 << 10);
+}
+
+static inline void disable_ack(int port)
+{
+ STM32_I2C_CR1(port) &= ~(1 << 10);
+}
+
+static void i2c_init_port(unsigned int port);
+
+static int i2c_write_raw_slave(int port, void *buf, int len)
+{
+ stm32_dma_chan_t *chan;
+ int rv;
+
+ /* we don't want to race with TxE interrupt event */
+ disable_i2c_interrupt(port);
+
+ /* Configuring DMA1 channel DMAC_SLAVE_TX */
+ enable_ack(port);
+ chan = dma_get_channel(DMAC_SLAVE_TX);
+ dma_prepare_tx(dma_tx_option + port, len, buf);
+
+ /* Start the DMA */
+ dma_go(chan);
+
+ /* Configuring i2c to use DMA */
+ STM32_I2C_CR2(port) |= (1 << 11);
+
+ if (in_interrupt_context()) {
+ /* Poll for the transmission complete flag */
+ dma_wait(DMAC_SLAVE_TX);
+ dma_clear_isr(DMAC_SLAVE_TX);
+ } else {
+ /* Wait for the transmission complete Interrupt */
+ dma_enable_tc_interrupt(DMAC_SLAVE_TX);
+ rv = task_wait_event(DMA_TRANSFER_TIMEOUT_US);
+ dma_disable_tc_interrupt(DMAC_SLAVE_TX);
+
+ if (!(rv & TASK_EVENT_WAKE)) {
+ CPRINTF("[%T Slave timeout, resetting i2c]\n");
+ i2c_init_port(port);
+ }
+ }
+
+ dma_disable(DMAC_SLAVE_TX);
+ STM32_I2C_CR2(port) &= ~(1 << 11);
+
+ enable_i2c_interrupt(port);
+
+ return len;
+}
+
+static void i2c_send_response(struct host_cmd_handler_args *args)
+{
+ const uint8_t *data = args->response;
+ int size = args->response_size;
+ uint8_t *out = host_buffer;
+ int sum = 0, i;
+
+ *out++ = args->result;
+ if (!i2c_old_response) {
+ *out++ = size;
+ sum = args->result + size;
+ }
+ for (i = 0; i < size; i++, data++, out++) {
+ if (data != out)
+ *out = *data;
+ sum += *data;
+ }
+ *out++ = sum & 0xff;
+
+ /* send the answer to the AP */
+ i2c_write_raw_slave(I2C2, host_buffer, out - host_buffer);
+}
+
+/* Process the command in the i2c host buffer */
+static void i2c_process_command(void)
+{
+ struct host_cmd_handler_args *args = &host_cmd_args;
+ char *buff = host_buffer;
+
+ args->command = *buff;
+ args->result = EC_RES_SUCCESS;
+ if (args->command >= EC_CMD_VERSION0) {
+ int csum, i;
+
+ /* Read version and data size */
+ args->version = args->command - EC_CMD_VERSION0;
+ args->command = buff[1];
+ args->params_size = buff[2];
+
+ /* Verify checksum */
+ for (csum = i = 0; i < args->params_size + 3; i++)
+ csum += buff[i];
+ if ((uint8_t)csum != buff[i])
+ args->result = EC_RES_INVALID_CHECKSUM;
+
+ buff += 3;
+ i2c_old_response = 0;
+ } else {
+ /* Old style command */
+ args->version = 0;
+ args->params_size = EC_HOST_PARAM_SIZE; /* unknown */
+ buff++;
+ i2c_old_response = 1;
+ }
+
+ /* we have an available command : execute it */
+ args->send_response = i2c_send_response;
+ args->params = buff;
+ /* skip room for error code, arglen */
+ args->response = host_buffer + 2;
+ args->response_max = EC_HOST_PARAM_SIZE;
+ args->response_size = 0;
+
+ host_command_received(args);
+}
+
+static void i2c_event_handler(int port)
+{
+ /* save and clear status */
+ i2c_sr1[port] = STM32_I2C_SR1(port);
+ STM32_I2C_SR1(port) = 0;
+
+ /* Confirm that you are not in master mode */
+ if (STM32_I2C_SR2(port) & (1 << 0)) {
+ CPRINTF("I2C slave ISR triggered in master mode, ignoring.\n");
+ return;
+ }
+
+ /* transfer matched our slave address */
+ if (i2c_sr1[port] & (1 << 1)) {
+ /* If it's a receiver slave */
+ if (!(STM32_I2C_SR2(port) & (1 << 2))) {
+ dma_start_rx(dma_rx_option + port, sizeof(host_buffer),
+ host_buffer);
+
+ STM32_I2C_CR2(port) |= (1 << 11);
+ rx_pending = 1;
+ }
+
+ /* cleared by reading SR1 followed by reading SR2 */
+ STM32_I2C_SR1(port);
+ STM32_I2C_SR2(port);
+ } else if (i2c_sr1[port] & (1 << 4)) {
+ /* If it's a receiver slave */
+ if (!(STM32_I2C_SR2(port) & (1 << 2))) {
+ /* Disable, and clear the DMA transfer complete flag */
+ dma_disable(DMAC_SLAVE_RX);
+ dma_clear_isr(DMAC_SLAVE_RX);
+
+ /* Turn off i2c's DMA flag */
+ STM32_I2C_CR2(port) &= ~(1 << 11);
+ }
+ /* clear STOPF bit by reading SR1 and then writing CR1 */
+ STM32_I2C_SR1(port);
+ STM32_I2C_CR1(port) = STM32_I2C_CR1(port);
+ }
+
+ /* TxE event */
+ if (i2c_sr1[port] & (1 << 7)) {
+ if (port == I2C2) { /* AP is waiting for EC response */
+ if (rx_pending) {
+ i2c_process_command();
+ /* reset host buffer after end of transfer */
+ rx_pending = 0;
+ } else {
+ /* spurious read : return dummy value */
+ STM32_I2C_DR(port) = 0xec;
+ }
+ }
+ }
+}
+static void i2c2_event_interrupt(void) { i2c_event_handler(I2C2); }
+DECLARE_IRQ(STM32_IRQ_I2C2_EV, i2c2_event_interrupt, 3);
+
+static void i2c_error_handler(int port)
+{
+ i2c_sr1[port] = STM32_I2C_SR1(port);
+
+ if (i2c_sr1[port] & 1 << 10) {
+ /* ACK failed (NACK); expected when AP reads final byte.
+ * Software must clear AF bit. */
+ } else {
+ CPRINTF("%s: I2C_SR1(%d): 0x%04x\n",
+ __func__, port, i2c_sr1[port]);
+ CPRINTF("%s: I2C_SR2(%d): 0x%04x\n",
+ __func__, port, STM32_I2C_SR2(port));
+ }
+
+ STM32_I2C_SR1(port) &= ~0xdf00;
+}
+static void i2c2_error_interrupt(void) { i2c_error_handler(I2C2); }
+DECLARE_IRQ(STM32_IRQ_I2C2_ER, i2c2_error_interrupt, 2);
+
+/* board-specific setup for post-I2C module init */
+void __board_i2c_post_init(int port)
+{
+}
+
+void board_i2c_post_init(int port)
+ __attribute__((weak, alias("__board_i2c_post_init")));
+
+/*
+ * Unwedge the i2c bus for the given port.
+ *
+ * Some devices on our i2c busses keep power even if we get a reset. That
+ * means that they could be partway through a transaction and could be
+ * driving the bus in a way that makes it hard for us to talk on the bus.
+ * ...or they might listen to the next transaction and interpret it in a
+ * weird way.
+ *
+ * Note that devices could be in one of several states:
+ * - If a device got interrupted in a write transaction it will be watching
+ * for additional data to finish its write. It will probably be looking to
+ * ack the data (drive the data line low) after it gets everything. Ideally
+ * we'd like to abort right away so we don't write bogus data.
+ * - If a device got interrupted while responding to a register read, it will
+ * be watching for clocks and will drive data out when it sees clocks. At
+ * the moment it might be trying to send out a 1 (so both clock and data
+ * may be high) or it might be trying to send out a 0 (so it's driving data
+ * low). Ideally we want to finish reading the current byte and then nak to
+ * abort everything.
+ *
+ * We attempt to unwedge the bus by doing:
+ * - If possible, send a pseudo-"stop" bit. We can only do this if nobody
+ * else is driving the clock or data lines, since that's the only way we
+ * have enough control. The idea here is to abort any writes that might
+ * be in progress. Note that a real "stop" bit would actually be a "low to
+ * high transition of SDA while SCL is high". ...but both must be high for
+ * us to be in control of the bus. Thus we _first_ drive SDA low so we can
+ * transition it high. This first transition looks like a start bit. In any
+ * case, the hope here is that it will look enough like an error condition
+ * that slaves will abort.
+ * - If we failed to send the pseudo-stop bit, try one clock and try again.
+ * I've seen a reset happen while the device was waiting for us to clock out
+ * its ack of the address. That should be the only time that the other side
+ * is driving things in the case of a write, so only 1 clock is enough.
+ * - Try to clock 9 times, if we can. This should finish reading out any data
+ * and then should nak.
+ * - Send one last pseudo-stop bit, just for good measure.
+ *
+ * @param port The i2c port to unwedge.
+ */
+static void unwedge_i2c_bus(int port)
+{
+ enum gpio_signal sda, scl;
+ int i;
+
+ 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;
+ }
+
+ /*
+ * Reconfigure ports as general purpose open-drain outputs, initted
+ * to high.
+ */
+ gpio_set_flags(scl, GPIO_ODR_HIGH);
+ gpio_set_flags(sda, GPIO_ODR_HIGH);
+
+ /* Try to send out pseudo-stop bit. See function description */
+ if (gpio_get_level(scl) && gpio_get_level(sda)) {
+ gpio_set_level(sda, 0);
+ udelay(I2C_BITBANG_DELAY_US);
+ gpio_set_level(sda, 1);
+ udelay(I2C_BITBANG_DELAY_US);
+ } else {
+ /* One more clock in case it was trying to ack its address */
+ gpio_set_level(scl, 0);
+ udelay(I2C_BITBANG_DELAY_US);
+ gpio_set_level(scl, 1);
+ udelay(I2C_BITBANG_DELAY_US);
+
+ if (gpio_get_level(scl) && gpio_get_level(sda)) {
+ gpio_set_level(sda, 0);
+ udelay(I2C_BITBANG_DELAY_US);
+ gpio_set_level(sda, 1);
+ udelay(I2C_BITBANG_DELAY_US);
+ }
+ }
+
+ /*
+ * Now clock 9 to read pending data; one of these will be a NAK.
+ *
+ * Don't bother even checking if scl is high--we can't do anything about
+ * it anyway.
+ */
+ for (i = 0; i < 9; i++) {
+ gpio_set_level(scl, 0);
+ udelay(I2C_BITBANG_DELAY_US);
+ gpio_set_level(scl, 1);
+ udelay(I2C_BITBANG_DELAY_US);
+ }
+
+ /* One last try at a pseudo-stop bit */
+ if (gpio_get_level(scl) && gpio_get_level(sda)) {
+ gpio_set_level(sda, 0);
+ udelay(I2C_BITBANG_DELAY_US);
+ gpio_set_level(sda, 1);
+ udelay(I2C_BITBANG_DELAY_US);
+ }
+
+ /*
+ * Set things back to quiescent.
+ *
+ * We rely on board_i2c_post_init() to actually reconfigure pins to
+ * be special function.
+ */
+ gpio_set_level(scl, 1);
+ gpio_set_level(sda, 1);
+}
+
+static void i2c_init_port(unsigned int port)
+{
+ const int i2c_clock_bit[] = {21, 22};
+
+ ASSERT(port == I2C1 || port == I2C2);
+ ASSERT(port < 2);
+
+ if (!(STM32_RCC_APB1ENR & (1 << i2c_clock_bit[port]))) {
+ /* Only unwedge the bus if the clock is off */
+ if (i2c_claim(port) == EC_SUCCESS) {
+ unwedge_i2c_bus(port);
+ i2c_release(port);
+ }
+
+ /* enable I2C2 clock */
+ STM32_RCC_APB1ENR |= 1 << i2c_clock_bit[port];
+ }
+
+ /* force reset of the i2c peripheral */
+ STM32_I2C_CR1(port) = 0x8000;
+ STM32_I2C_CR1(port) = 0x0000;
+
+ /* set clock configuration : standard mode (100kHz) */
+ STM32_I2C_CCR(port) = I2C_CCR;
+
+ /* set slave address */
+ if (port == I2C2)
+ STM32_I2C_OAR1(port) = I2C_ADDRESS;
+
+ /* configuration : I2C mode / Periphal enabled, ACK enabled */
+ STM32_I2C_CR1(port) = (1 << 10) | (1 << 0);
+ /* error and event interrupts enabled / input clock is 16Mhz */
+ STM32_I2C_CR2(port) = (1 << 9) | (1 << 8) | 0x10;
+
+ /* clear status */
+ STM32_I2C_SR1(port) = 0;
+
+ board_i2c_post_init(port);
+}
+
+static void i2c_init(void)
+{
+ /* TODO: Add #defines to determine which channels to init */
+ i2c_init_port(I2C1);
+ i2c_init_port(I2C2);
+
+ /* Enable event and error interrupts */
+ task_enable_irq(STM32_IRQ_I2C2_EV);
+ task_enable_irq(STM32_IRQ_I2C2_ER);
+}
+DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);
+
+/*****************************************************************************/
+/* STM32 Host I2C */
+
+#define SR1_SB (1 << 0) /* Start bit sent */
+#define SR1_ADDR (1 << 1) /* Address sent */
+#define SR1_BTF (1 << 2) /* Byte transfered */
+#define SR1_ADD10 (1 << 3) /* 10bit address sent */
+#define SR1_STOPF (1 << 4) /* Stop detected */
+#define SR1_RxNE (1 << 6) /* Data reg not empty */
+#define SR1_TxE (1 << 7) /* Data reg empty */
+#define SR1_BERR (1 << 8) /* Buss error */
+#define SR1_ARLO (1 << 9) /* Arbitration lost */
+#define SR1_AF (1 << 10) /* Ack failure */
+#define SR1_OVR (1 << 11) /* Overrun/underrun */
+#define SR1_PECERR (1 << 12) /* PEC err in reception */
+#define SR1_TIMEOUT (1 << 14) /* Timeout : 25ms */
+#define CR2_DMAEN (1 << 11) /* DMA enable */
+#define CR2_LAST (1 << 12) /* Next EOT is last EOT */
+
+static inline void dump_i2c_reg(int port)
+{
+#ifdef CONFIG_DEBUG_I2C
+ CPRINTF("CR1 : %016b\n", STM32_I2C_CR1(port));
+ CPRINTF("CR2 : %016b\n", STM32_I2C_CR2(port));
+ CPRINTF("SR2 : %016b\n", STM32_I2C_SR2(port));
+ CPRINTF("SR1 : %016b\n", STM32_I2C_SR1(port));
+ CPRINTF("OAR1 : %016b\n", STM32_I2C_OAR1(port));
+ CPRINTF("OAR2 : %016b\n", STM32_I2C_OAR2(port));
+ CPRINTF("DR : %016b\n", STM32_I2C_DR(port));
+ CPRINTF("CCR : %016b\n", STM32_I2C_CCR(port));
+ CPRINTF("TRISE: %016b\n", STM32_I2C_TRISE(port));
+#endif /* CONFIG_DEBUG_I2C */
+}
+
+enum wait_t {
+ WAIT_NONE,
+ WAIT_MASTER_START,
+ WAIT_ADDR_READY,
+ WAIT_XMIT_TXE,
+ WAIT_XMIT_FINAL_TXE,
+ WAIT_XMIT_BTF,
+ WAIT_XMIT_STOP,
+ WAIT_RX_NE,
+ WAIT_RX_NE_FINAL,
+ WAIT_RX_NE_STOP,
+ WAIT_RX_NE_STOP_SIZE2,
+};
+
+/**
+ * Wait for a specific i2c event
+ *
+ * This function waits until the bit(s) corresponding to mask in
+ * the specified port's I2C SR1 register is/are set. It may
+ * return a timeout or success.
+ *
+ * @param port Port to wait on
+ * @param mask A mask specifying which bits in SR1 to wait to be set
+ * @param wait A wait code to be returned with the timeout error code if that
+ * occurs, to help with debugging.
+ * @return EC_SUCCESS, or EC_ERROR_TIMEOUT with the wait code OR'd onto the
+ * bits 8-16 to indicate what it timed out waiting for.
+ */
+static int wait_status(int port, uint32_t mask, enum wait_t wait)
+{
+ uint32_t r;
+ timestamp_t t1, t2;
+
+ t1 = t2 = get_time();
+ r = STM32_I2C_SR1(port);
+ while (mask ? ((r & mask) != mask) : r) {
+ t2 = get_time();
+ if (t2.val - t1.val > I2C_TX_TIMEOUT_MASTER) {
+ return EC_ERROR_TIMEOUT | (wait << 8);
+ } else if (t2.val - t1.val > 150) {
+ usleep(100);
+ }
+ r = STM32_I2C_SR1(port);
+ }
+
+ return EC_SUCCESS;
+}
+
+static inline uint32_t read_clear_status(int port)
+{
+ uint32_t sr1, sr2;
+
+ sr1 = STM32_I2C_SR1(port);
+ sr2 = STM32_I2C_SR2(port);
+ return (sr2 << 16) | (sr1 & 0xffff);
+}
+
+static int master_start(int port, int slave_addr)
+{
+ int rv;
+
+ /* Change to master send mode, reset stop bit, send start bit */
+ STM32_I2C_CR1(port) = (STM32_I2C_CR1(port) & ~(1 << 9)) | (1 << 8);
+ /* Wait for start bit sent event */
+ rv = wait_status(port, SR1_SB, WAIT_MASTER_START);
+ if (rv)
+ return rv;
+
+ /* Send address */
+ STM32_I2C_DR(port) = slave_addr;
+ /* Wait for addr ready */
+ rv = wait_status(port, SR1_ADDR, WAIT_ADDR_READY);
+ if (rv)
+ return rv;
+
+ read_clear_status(port);
+
+ return EC_SUCCESS;
+}
+
+static void master_stop(int port)
+{
+ STM32_I2C_CR1(port) |= (1 << 9);
+}
+
+static int wait_until_stop_sent(int port)
+{
+ timestamp_t deadline;
+ timestamp_t slow_cutoff;
+ uint8_t is_slow;
+
+ deadline = slow_cutoff = get_time();
+ deadline.val += TIMEOUT_STOP_SENT_US;
+ slow_cutoff.val += SLOW_STOP_SENT_US;
+
+ while (STM32_I2C_CR1(port) & (1 << 9)) {
+ if (timestamp_expired(deadline, NULL)) {
+ ccprintf("Stop event deadline passed:\ttask=%d"
+ "\tCR1=%016b\n",
+ (int)task_get_current(), STM32_I2C_CR1(port));
+ return EC_ERROR_TIMEOUT;
+ }
+
+ if (is_slow) {
+ /* If we haven't gotten a fast response, sleep */
+ usleep(STOP_SENT_RETRY_US);
+ } else {
+ /* Check to see if this request is taking a while */
+ if (timestamp_expired(slow_cutoff, NULL)) {
+ ccprintf("Stop event taking a while: task=%d",
+ (int)task_get_current());
+ is_slow = 1;
+ }
+ }
+ }
+
+ return EC_SUCCESS;
+}
+
+static void handle_i2c_error(int port, int rv)
+{
+ timestamp_t t1, t2;
+ uint32_t r;
+
+ /* We have not used the bus, just exit */
+ if (rv == EC_ERROR_BUSY)
+ return;
+
+ /* EC_ERROR_TIMEOUT may have a code specifying where the timeout was */
+ if ((rv & 0xff) == EC_ERROR_TIMEOUT) {
+#ifdef CONFIG_DEBUG_I2C
+ CPRINTF("Wait_status() timeout type: %d\n", (rv >> 8));
+#endif
+ rv = EC_ERROR_TIMEOUT;
+ }
+ if (rv)
+ dump_i2c_reg(port);
+
+ /* Clear rc_w0 bits */
+ STM32_I2C_SR1(port) = 0;
+ /* Clear seq read status bits */
+ r = STM32_I2C_SR1(port);
+ r = STM32_I2C_SR2(port);
+ /* Clear busy state */
+ t1 = get_time();
+
+ if (rv == EC_ERROR_TIMEOUT && (STM32_I2C_CR1(port) & (1 << 8))) {
+ /*
+ * If it failed while just trying to send the start bit then
+ * something is wrong with the internal state of the i2c,
+ * (Probably a stray pulse on the line got it out of sync with
+ * the actual bytes) so reset it.
+ */
+ CPRINTF("Unable to send START, resetting i2c.\n");
+ i2c_init_port(port);
+ goto cr_cleanup;
+ } else if (rv == EC_ERROR_TIMEOUT && !(r & 2)) {
+ /*
+ * If the BUSY bit is faulty, send a stop bit just to be sure.
+ * It seems that this can be happen very briefly while sending
+ * a 1. We've not actually seen this, but just to be safe.
+ */
+ CPRINTF("Bad BUSY bit detected.\n");
+ master_stop(port);
+ }
+
+ /* Try to send stop bits until the bus becomes idle */
+ while (r & 2) {
+ t2 = get_time();
+ if (t2.val - t1.val > I2C_TX_TIMEOUT_MASTER) {
+ dump_i2c_reg(port);
+ /* Reset the i2c periph to get it back to slave mode */
+ i2c_init_port(port);
+ goto cr_cleanup;
+ }
+ /* Send stop */
+ master_stop(port);
+ usleep(1000);
+ r = STM32_I2C_SR2(port);
+ }
+
+cr_cleanup:
+ /*
+ * Reset control register to the default state :
+ * I2C mode / Periphal enabled, ACK enabled
+ */
+ STM32_I2C_CR1(port) = (1 << 10) | (1 << 0);
+}
+
+static int i2c_master_transmit(int port, int slave_addr, const uint8_t *data,
+ int size, int stop)
+{
+ int rv, rv_start;
+
+ disable_ack(port);
+
+ /* Configure DMA channel for TX to host */
+ dma_prepare_tx(dma_tx_option + port, size, data);
+ dma_enable_tc_interrupt(DMAC_HOST_TX);
+
+ /* Start the DMA */
+ dma_go(dma_get_channel(DMAC_HOST_TX));
+
+ /* Configuring i2c2 to use DMA */
+ STM32_I2C_CR2(port) |= CR2_DMAEN;
+
+ /* Initialise i2c communication by sending START and ADDR */
+ rv_start = master_start(port, slave_addr);
+
+ /* If it started, wait for the transmission complete Interrupt */
+ if (!rv_start)
+ rv = task_wait_event(DMA_TRANSFER_TIMEOUT_US);
+
+ dma_disable(DMAC_HOST_TX);
+ dma_disable_tc_interrupt(DMAC_HOST_TX);
+ STM32_I2C_CR2(port) &= ~CR2_DMAEN;
+
+ if (rv_start)
+ return rv_start;
+ if (!(rv & TASK_EVENT_WAKE))
+ return EC_ERROR_TIMEOUT;
+
+ rv = wait_status(port, SR1_BTF, WAIT_XMIT_BTF);
+ if (rv)
+ return rv;
+
+ if (stop) {
+ master_stop(port);
+ return wait_status(port, 0, WAIT_XMIT_STOP);
+ }
+
+ return EC_SUCCESS;
+}
+
+static int i2c_master_receive(int port, int slave_addr, uint8_t *data,
+ int size)
+{
+ int rv, rv_start;
+
+ if (data == NULL || size < 1)
+ return EC_ERROR_INVAL;
+
+ /* Master receive only supports DMA for payloads > 1 byte */
+ if (size > 1) {
+ enable_ack(port);
+ dma_start_rx(dma_rx_option + port, size, data);
+
+ dma_enable_tc_interrupt(DMAC_HOST_RX);
+
+ STM32_I2C_CR2(port) |= CR2_DMAEN;
+ STM32_I2C_CR2(port) |= CR2_LAST;
+
+ rv_start = master_start(port, slave_addr | 1);
+ if (!rv_start)
+ rv = task_wait_event(DMA_TRANSFER_TIMEOUT_US);
+
+ dma_disable(DMAC_HOST_RX);
+ dma_disable_tc_interrupt(DMAC_HOST_RX);
+ STM32_I2C_CR2(port) &= ~CR2_DMAEN;
+ disable_ack(port);
+
+ if (rv_start)
+ return rv_start;
+ if (!(rv & TASK_EVENT_WAKE))
+ return EC_ERROR_TIMEOUT;
+
+ master_stop(port);
+ } else {
+ disable_ack(port);
+
+ rv = master_start(port, slave_addr | 1);
+ if (rv)
+ return rv;
+ master_stop(port);
+ rv = wait_status(port, SR1_RxNE, WAIT_RX_NE_STOP_SIZE2);
+ if (rv)
+ return rv;
+ data[0] = STM32_I2C_DR(port);
+ }
+
+ return wait_until_stop_sent(port);
+}
+
+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;
+
+ /* TODO: support start/stop flags */
+
+ ASSERT(out || !out_bytes);
+ ASSERT(in || !in_bytes);
+
+ if (i2c_claim(port))
+ return EC_ERROR_BUSY;
+
+ disable_i2c_interrupt(port);
+
+ rv = i2c_master_transmit(port, slave_addr, out, out_bytes,
+ in_bytes ? 0 : 1);
+ if (!rv && in_bytes)
+ rv = i2c_master_receive(port, slave_addr, in, in_bytes);
+ handle_i2c_error(port, rv);
+
+ enable_i2c_interrupt(port);
+
+ i2c_release(port);
+
+ return rv;
+}
+
+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;
+}
+
+/*****************************************************************************/
+/* Console commands */
+
+#ifdef I2C_PORT_HOST
+
+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(i2c, command_i2c,
+ "r/r16/w/w16 slave_addr offset [value]",
+ "Read write i2c",
+ NULL);
+
+#endif /* I2C_PORT_HOST */