diff --git a/chip/stm32/dma.c b/chip/stm32/dma.c index 14058bfaa8..8281422fa3 100644 --- a/chip/stm32/dma.c +++ b/chip/stm32/dma.c @@ -7,6 +7,7 @@ #include "console.h" #include "dma.h" #include "registers.h" +#include "task.h" #include "timer.h" #include "util.h" @@ -14,6 +15,9 @@ #define CPUTS(outstr) cputs(CC_DMA, outstr) #define CPRINTF(format, args...) cprintf(CC_DMA, format, ## args) +/* Task IDs for the interrupt handlers to wake up */ +static task_id_t id[DMA_NUM_CHANNELS]; + /* * 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! @@ -193,23 +197,37 @@ void dma_init(void) STM32_RCC_AHBENR |= RCC_AHBENR_DMA1EN; } -int dma_wait(int channel) +int dma_get_irq(int channel) { - struct dma_ctlr *dma; - uint32_t mask; - timestamp_t deadline; + ASSERT(channel < DMA_NUM_CHANNELS); + if (channel < DMA1_NUM_CHANNELS) + return STM32_IRQ_DMA_CHANNEL_1 + channel; + else + return STM32_IRQ_DMA2_CHANNEL1 + channel - + DMA1_NUM_CHANNELS; +} - dma = dma_get_ctlr(channel); - mask = DMA_TCIF(channel); +void dma_enable_tc_interrupt(int channel) +{ + struct dma_channel *chan; + chan = dma_get_channel(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; + /* Storing task ID's so the ISRs knows which task to wake */ + id[channel] = task_get_current(); + + REG32(&chan->ccr) |= DMA_TCIE; + task_enable_irq(dma_get_irq(channel)); +} + +void dma_disable_tc_interrupt(int channel) +{ + struct dma_channel *chan; + chan = dma_get_channel(channel); + + id[channel] = TASK_ID_INVALID; + + REG32(&chan->ccr) &= ~DMA_TCIE; + task_disable_irq(dma_get_irq(channel)); } void dma_clear_isr(int channel) @@ -220,8 +238,7 @@ void dma_clear_isr(int 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); + REG32(&dma->ifcr) |= 0x0f << (4 * channel); } struct dma_ctlr *dma_get_ctlr(int channel) @@ -232,3 +249,19 @@ struct dma_ctlr *dma_get_ctlr(int channel) else return (struct dma_ctlr *)STM32_DMA2_BASE; } + +static void dma_event_interrupt_channel_4(void) +{ + dma_clear_isr(DMAC_I2C_TX); + if (id[DMAC_I2C_TX] != TASK_ID_INVALID) + task_wake(id[DMAC_I2C_TX]); +} +DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_4, dma_event_interrupt_channel_4, 3); + +static void dma_event_interrupt_channel_5(void) +{ + dma_clear_isr(DMAC_I2C_RX); + if (id[DMAC_I2C_RX] != TASK_ID_INVALID) + task_wake(id[DMAC_I2C_RX]); +} +DECLARE_IRQ(STM32_IRQ_DMA_CHANNEL_5, dma_event_interrupt_channel_5, 3); diff --git a/chip/stm32/dma.h b/chip/stm32/dma.h index 0ba963c11c..606bcf72cb 100644 --- a/chip/stm32/dma.h +++ b/chip/stm32/dma.h @@ -64,9 +64,12 @@ enum { DMA_PL_VERY_HIGH, }; -#define DMA_MINC_MASK (1 << 7) -#define DMA_DIR_FROM_MEM_MASK (1 << 4) #define DMA_EN (1 << 0) +#define DMA_TCIE (1 << 1) +#define DMA_HTIE (1 << 2) +#define DMA_TEIE (1 << 3) +#define DMA_DIR_FROM_MEM_MASK (1 << 4) +#define DMA_MINC_MASK (1 << 7) #define DMA_TCIF(channel) (1 << (1 + 4 * channel)) #define DMA_POLLING_INTERVAL_US 100 /* us */ @@ -174,14 +177,6 @@ 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 * @@ -189,6 +184,20 @@ int dma_wait(int channel); */ void dma_clear_isr(int channel); +/** + * Enable "Transfer Complete" interrupt for a DMA channel + * + * @param channel Which channel's interrupts to change (DMAC_...) + */ +void dma_enable_tc_interrupt(int channel); + +/** + * Disable "Transfer Complete" interrupt for a DMA channel + * + * @param channel Which channel's interrupts to change (DMAC_...) + */ +void dma_disable_tc_interrupt(int channel); + /** * Get a pointer to the DMA peripheral controller that owns the channel * diff --git a/chip/stm32/i2c.c b/chip/stm32/i2c.c index 8cd684be93..7139695b23 100644 --- a/chip/stm32/i2c.c +++ b/chip/stm32/i2c.c @@ -108,24 +108,25 @@ static int i2c_write_raw_slave(int port, void *buf, int len) /* we don't want to race with TxE interrupt event */ disable_i2c_interrupt(port); - enable_ack(port); - /* Configuring DMA1 channel DMAC_I2X_TX */ + enable_ack(port); chan = dma_get_channel(DMAC_I2C_TX); dma_prepare_tx(chan, len, (void *)&STM32_I2C_DR(port), buf); + + /* set up DMA interrupts to signal when the transfer is over */ + dma_enable_tc_interrupt(DMAC_I2C_TX); + + /* Start the DMA */ dma_go(chan); - /* Configuring i2c2 */ + /* Configuring i2c2 to use DMA*/ STM32_I2C_CR2(port) |= (1 << 11); - /* Wait for the dma to transfer all the data */ - dma_wait(DMAC_I2C_TX); + /* Wait for the transmission complete Interrupt */ + task_wait_event(DMA_TRANSFER_TIMEOUT_US); - /* 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 */ + dma_disable_tc_interrupt(DMAC_I2C_TX); STM32_I2C_CR2(port) &= ~(1 << 11); enable_i2c_interrupt(port); @@ -538,28 +539,28 @@ static int i2c_master_transmit(int port, int slave_addr, uint8_t *data, /* 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_enable_tc_interrupt(DMAC_I2C_TX); + + /* Start the DMA */ dma_go(chan); - /* Configuring i2c2 */ + /* Configuring i2c2 to use DMA */ STM32_I2C_CR2(port) |= CR2_DMAEN; /* Initialise i2c communication by sending START and ADDR */ rv = master_start(port, slave_addr); - if (rv) - return rv; - /* Wait for the dma to transfer all the data */ - rv = dma_wait(DMAC_I2C_TX); - if (rv) - return WAIT_XMIT_TXE; + /* If it started, wait for the transmission complete Interrupt */ + if (!rv) + rv = task_wait_event(DMA_TRANSFER_TIMEOUT_US); - /* 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 */ + dma_disable_tc_interrupt(DMAC_I2C_TX); STM32_I2C_CR2(port) &= ~CR2_DMAEN; + if (rv && !(rv & TASK_EVENT_WAKE)) + return rv; + rv = wait_status(port, SR1_BTF, WAIT_XMIT_BTF); if (rv) return rv; @@ -586,25 +587,23 @@ static int i2c_master_receive(int port, int slave_addr, uint8_t *data, dma_start_rx(DMAC_I2C_RX, size, (void *)&STM32_I2C_DR(port), data); + dma_enable_tc_interrupt(DMAC_I2C_RX); + STM32_I2C_CR2(port) |= CR2_DMAEN; STM32_I2C_CR2(port) |= CR2_LAST; rv = master_start(port, slave_addr | 1); - if (rv) - return rv; + if (!rv) + rv = task_wait_event(DMA_TRANSFER_TIMEOUT_US); - /* 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 */ + dma_disable_tc_interrupt(DMAC_I2C_RX); STM32_I2C_CR2(port) &= ~CR2_DMAEN; disable_ack(port); + + if (rv && !(rv & TASK_EVENT_WAKE)) + return rv; + master_stop(port); } else { disable_ack(port);