mirror of
https://github.com/Telecominfraproject/OpenCellular.git
synced 2026-01-08 16:41:55 +00:00
Snow: Switching i2c from polling to Interrupts
To reduce the amount of time spent polling to see if the i2c bus has completed its transfer, I'm converting it over to interrupts. Before starting a dma transfer, the i2c code now enables dma interrupts with an ISR that will just wake up the i2c task when the transfer is complete. This leaves the cpu free while the dma is handling all the i2c work. The slave-receiver didn't require any updates as it is already interrupt driven, via the i2c events. The other three cases: master-receiver, master-transmitter, and slave-transmitter, have all been converted over to use the dma interrupts. With these changes, the cpu should spend very little time waiting for i2c transfers to complete. BUG=chrome-os-partner:12405 TEST=To test the master modes, from the EC console run "battery" and "pmu." If those work, then master mode is functioning. For slave modes, power on the machine and monitor the cpu console for errors. When it's on, try typing and confirm there are no errors there either. Change-Id: I1ca020911b7be6762389ca2b858b2b973f8754bc Signed-off-by: Charlie Mooney <charliemooney@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/30229 Reviewed-by: David Hendricks <dhendrix@chromium.org>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user