summaryrefslogtreecommitdiff
path: root/chip
diff options
context:
space:
mode:
authorCharlie Mooney <charliemooney@chromium.org>2012-08-07 09:43:44 -0700
committerGerrit <chrome-bot@google.com>2012-08-13 12:33:32 -0700
commit397a7aa33633b912cc08eabe125b88a6648efb10 (patch)
treeb76b64eaa8f7b4c9191dfb5f1f000a98c731e3a3 /chip
parent03d4ed278de2a27a9c2c580ad4d68e30dbcc7630 (diff)
downloadchrome-ec-397a7aa33633b912cc08eabe125b88a6648efb10.tar.gz
Lucas: Switching the i2c transmit code over to dma
There was an errata issues for the i2c on STMF100xx. It specified that not all guarantees apply to i2c on these chips if you are not using DMA to load the data. To prevent problems, I am converting the i2c code on the EC for Lucas over to DMA. Here the i2c's master functionality is retrofitted to use DMA instead of polling to fill the i2c buffer. The slave functionality is still left in the old style for the time being, but will also be converted soon. BUG=chrome-os-partner:10901 TEST=From EC console, make sure that "battery" and "pmu" commands work. They both use i2c, so if i2c had been broken they would fail. Change-Id: I2fb80dcb68632938df1c9165ebd5a67cb5194451 Signed-off-by: Charlie Mooney <charliemooney@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/29811 Reviewed-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'chip')
-rw-r--r--chip/stm32/dma.c45
-rw-r--r--chip/stm32/dma.h37
-rw-r--r--chip/stm32/i2c.c120
3 files changed, 143 insertions, 59 deletions
diff --git a/chip/stm32/dma.c b/chip/stm32/dma.c
index 2f0f868e47..14058bfaa8 100644
--- a/chip/stm32/dma.c
+++ b/chip/stm32/dma.c
@@ -14,7 +14,10 @@
#define CPUTS(outstr) cputs(CC_DMA, outstr)
#define CPRINTF(format, args...) cprintf(CC_DMA, format, ## args)
-
+/*
+ * Note, you must decrement the channel value by 1 from what is specified
+ * in the datasheets, as they index from 1 and this indexes from 0!
+ */
struct dma_channel *dma_get_channel(int channel)
{
struct dma_channel *chan;
@@ -189,3 +192,43 @@ void dma_init(void)
/* Enable DMA1, we don't support DMA2 yet */
STM32_RCC_AHBENR |= RCC_AHBENR_DMA1EN;
}
+
+int dma_wait(int channel)
+{
+ struct dma_ctlr *dma;
+ uint32_t mask;
+ timestamp_t deadline;
+
+ dma = dma_get_ctlr(channel);
+ mask = DMA_TCIF(channel);
+
+ deadline.val = get_time().val + DMA_TRANSFER_TIMEOUT_US;
+ while ((REG32(&dma->isr) & mask) != mask) {
+ if (deadline.val <= get_time().val)
+ return -1;
+ else
+ usleep(DMA_POLLING_INTERVAL_US);
+ }
+ return 0;
+}
+
+void dma_clear_isr(int channel)
+{
+ struct dma_ctlr *dma;
+
+ dma = dma_get_ctlr(channel);
+ /* Adjusting the channel number if it's from the second DMA */
+ if (channel > DMA1_NUM_CHANNELS)
+ channel -= DMA1_NUM_CHANNELS;
+
+ REG32(&dma->ifcr) |= 0xff << (4 * channel);
+}
+
+struct dma_ctlr *dma_get_ctlr(int channel)
+{
+ ASSERT(channel < DMA_NUM_CHANNELS);
+ if (channel < DMA1_NUM_CHANNELS)
+ return (struct dma_ctlr *)STM32_DMA1_BASE;
+ else
+ return (struct dma_ctlr *)STM32_DMA2_BASE;
+}
diff --git a/chip/stm32/dma.h b/chip/stm32/dma.h
index 106d16d76b..0ba963c11c 100644
--- a/chip/stm32/dma.h
+++ b/chip/stm32/dma.h
@@ -15,7 +15,8 @@
*
* Note: The STM datasheet tends to number things from 1. We should ask
* the European elevator engineers to talk to MCU engineer counterparts
- * about this.
+ * about this. This means that if the datasheet refers to channel n,
+ * you need to use n-1 in the code.
*/
enum {
DMAC_ADC,
@@ -24,6 +25,13 @@ enum {
DMAC_SPI2_RX,
DMAC_SPI2_TX,
+ /*
+ * The same channels are used for i2c and spi, you can't use them at
+ * the same time or it will cause dma to not work
+ */
+ DMAC_I2C_RX = 4,
+ DMAC_I2C_TX = 3,
+
/* DMA1 has 7 channels, DMA2 has 5 */
DMA1_NUM_CHANNELS = 7,
DMA2_NUM_CHANNELS = 5,
@@ -59,6 +67,10 @@ enum {
#define DMA_MINC_MASK (1 << 7)
#define DMA_DIR_FROM_MEM_MASK (1 << 4)
#define DMA_EN (1 << 0)
+#define DMA_TCIF(channel) (1 << (1 + 4 * channel))
+
+#define DMA_POLLING_INTERVAL_US 100 /* us */
+#define DMA_TRANSFER_TIMEOUT_US 100000 /* us */
/*
* Certain DMA channels must be used for certain peripherals and transfer
@@ -162,4 +174,27 @@ void dma_test(void);
*/
void dma_init(void);
+/**
+ * Wait for the DMA transfer to complete
+ *
+ * @param channel Channel number to wait on (DMAC_...)
+ * @return -1 for timeout, 0 for sucess
+ */
+int dma_wait(int channel);
+
+/**
+ * Clear the DMA interrupt/event flags for a given channel
+ *
+ * @param channel Which channel's isr to clear (DMAC_...)
+ */
+void dma_clear_isr(int channel);
+
+/**
+ * Get a pointer to the DMA peripheral controller that owns the channel
+ *
+ * @param channel Channel number to get the controller for (DMAC_...)
+ * @return pointer to DMA channel registers
+ */
+struct dma_ctlr *dma_get_ctlr(int channel);
+
#endif
diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c
index e0955c3f24..fe3f03db5f 100644
--- a/chip/stm32/i2c.c
+++ b/chip/stm32/i2c.c
@@ -8,6 +8,7 @@
#include "clock.h"
#include "common.h"
#include "console.h"
+#include "dma.h"
#include "ec_commands.h"
#include "gpio.h"
#include "hooks.h"
@@ -361,6 +362,9 @@ DECLARE_HOOK(HOOK_INIT, i2c_init, HOOK_PRIO_DEFAULT);
#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)
{
@@ -391,6 +395,20 @@ enum wait_t {
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;
@@ -512,23 +530,36 @@ cr_cleanup:
static int i2c_master_transmit(int port, int slave_addr, uint8_t *data,
int size, int stop)
{
- int rv, i;
+ int rv;
+ struct dma_channel *chan;
disable_ack(port);
+
+ /* Configuring DMA1 channel DMAC_I2X_TX */
+ chan = dma_get_channel(DMAC_I2C_TX);
+ dma_prepare_tx(chan, size, (void *)&STM32_I2C_DR(port), data);
+ dma_go(chan);
+
+ /* Configuring i2c2 */
+ STM32_I2C_CR2(port) |= CR2_DMAEN;
+
+ /* Initialise i2c communication by sending START and ADDR */
rv = master_start(port, slave_addr);
if (rv)
return rv;
- /* TODO: use common i2c_write_raw instead */
- for (i = 0; i < size; i++) {
- rv = wait_status(port, SR1_TxE, WAIT_XMIT_TXE);
- if (rv)
- return rv;
- STM32_I2C_DR(port) = data[i];
- }
- rv = wait_status(port, SR1_TxE, WAIT_XMIT_FINAL_TXE);
+ /* Wait for the dma to transfer all the data */
+ rv = dma_wait(DMAC_I2C_TX);
if (rv)
- return rv;
+ return WAIT_XMIT_TXE;
+
+ /* Disable, and clear the DMA transfer complete flag */
+ dma_disable(DMAC_I2C_TX);
+ dma_clear_isr(DMAC_I2C_TX);
+
+ /* Turn off i2c's DMA flag */
+ STM32_I2C_CR2(port) &= ~CR2_DMAEN;
+
rv = wait_status(port, SR1_BTF, WAIT_XMIT_BTF);
if (rv)
return rv;
@@ -544,68 +575,43 @@ static int i2c_master_transmit(int port, int slave_addr, uint8_t *data,
static int i2c_master_receive(int port, int slave_addr, uint8_t *data,
int size)
{
- /* Master receiver sequence
- *
- * 1 byte
- * S ADDR ACK D0 NACK P
- * -o- -oooo- -iii- -ii- -oooo- -o-
- *
- * multi bytes
- * S ADDR ACK D0 ACK Dn-2 ACK Dn-1 NACK P
- * -o- -oooo- -iii- -ii- -ooo- -iiii- -ooo- -iiii- -oooo- -o-
- *
- */
- int rv, i;
+ int rv;
if (data == NULL || size < 1)
return EC_ERROR_INVAL;
- /* Set ACK to high only on receiving more than 1 byte */
- if (size > 1)
+ /* Master receive only supports DMA for payloads > 1 byte */
+ if (size > 1) {
enable_ack(port);
- else
- disable_ack(port);
-
- /* Send START pulse, slave address, receive mode */
- rv = master_start(port, slave_addr | 1);
- if (rv)
- return rv;
+ dma_start_rx(DMAC_I2C_RX, size, (void *)&STM32_I2C_DR(port),
+ data);
- if (size >= 2) {
- for (i = 0; i < (size - 2); i++) {
- rv = wait_status(port, SR1_RxNE, WAIT_RX_NE);
- if (rv)
- return rv;
-
- data[i] = STM32_I2C_DR(port);
- }
+ STM32_I2C_CR2(port) |= CR2_DMAEN;
+ STM32_I2C_CR2(port) |= CR2_LAST;
- /* Process last two bytes: data[n-2], data[n-1]
- * => wait rx ready
- * => [n-2] in data-reg, [n-1] in shift-reg
- * => [n-2] ACK done
- * => disable ACK to let [n-1] byte send NACK
- * => set STOP high
- * => read [n-2]
- * => wait rx ready
- * => read [n-1]
- */
- rv = wait_status(port, SR1_RxNE, WAIT_RX_NE_FINAL);
+ rv = master_start(port, slave_addr | 1);
if (rv)
return rv;
+ /* Wait for the dma to transfer all the data */
+ rv = dma_wait(DMAC_I2C_RX);
+ if (rv)
+ return WAIT_RX_NE;
+
+ /* Disable, and clear the DMA transfer complete flag */
+ dma_disable(DMAC_I2C_RX);
+ dma_clear_isr(DMAC_I2C_RX);
+
+ /* Turn off i2c's DMA flag */
+ STM32_I2C_CR2(port) &= ~CR2_DMAEN;
disable_ack(port);
master_stop(port);
+ } else {
+ disable_ack(port);
- data[i] = STM32_I2C_DR(port);
-
- rv = wait_status(port, SR1_RxNE, WAIT_RX_NE_STOP);
+ rv = master_start(port, slave_addr | 1);
if (rv)
return rv;
-
- i++;
- data[i] = STM32_I2C_DR(port);
- } else {
master_stop(port);
rv = wait_status(port, SR1_RxNE, WAIT_RX_NE_STOP_SIZE2);
if (rv)