Skip to content

Commit

Permalink
stmhal: Turn off DMA clocks when idle for 100 msec
Browse files Browse the repository at this point in the history
Turning on each DMA block increases the current consumption
by about 8 mA. This code adds an idle timer for each DMA
block and turns off the clocks when no streams are in use
for 128 msec. Having a small timeout allows for improved
performance when back-to-back transfers are being performed.

The 128 msec is basically a guess.
  • Loading branch information
dhylands committed Nov 16, 2015
1 parent 1fd6405 commit a1ebf69
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 18 deletions.
132 changes: 114 additions & 18 deletions stmhal/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
#include "py/obj.h"
#include "irq.h"

#define NSTREAM (16)
#define NSTREAMS_PER_CONTROLLER (8)
#define NCONTROLLERS (2)
#define NSTREAM (NCONTROLLERS * NSTREAMS_PER_CONTROLLER)

static const uint8_t dma_irqn[NSTREAM] = {
DMA1_Stream0_IRQn,
Expand Down Expand Up @@ -72,7 +74,16 @@ const DMA_InitTypeDef dma_init_struct_spi_i2c = {
};

static DMA_HandleTypeDef *dma_handle[NSTREAM] = {NULL};
static uint32_t dma_last_channel[NSTREAM];
static uint8_t dma_last_channel[NSTREAM];
static volatile uint32_t dma_enable_mask = 0;

volatile dma_idle_count_t dma_idle;

#define DMA1_ENABLE_MASK 0x00ff // Bits in dma_enable_mask corresponfing to DMA1
#define DMA2_ENABLE_MASK 0xff00 // Bits in dma_enable_mask corresponding to DMA2
#define DMA_INVALID_CHANNEL 0xff // Value stored in dma_last_channel which means invalid

#define DMA_CHANNEL_AS_UINT8(dma_channel) (((dma_channel) & DMA_SxCR_CHSEL) >> 24)

void DMA1_Stream0_IRQHandler(void) { if (dma_handle[0] != NULL) { HAL_DMA_IRQHandler(dma_handle[0]); } }
void DMA1_Stream1_IRQHandler(void) { if (dma_handle[1] != NULL) { HAL_DMA_IRQHandler(dma_handle[1]); } }
Expand All @@ -91,19 +102,76 @@ void DMA2_Stream5_IRQHandler(void) { if (dma_handle[13] != NULL) { HAL_DMA_IRQHa
void DMA2_Stream6_IRQHandler(void) { if (dma_handle[14] != NULL) { HAL_DMA_IRQHandler(dma_handle[14]); } }
void DMA2_Stream7_IRQHandler(void) { if (dma_handle[15] != NULL) { HAL_DMA_IRQHandler(dma_handle[15]); } }

#define DMA1_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA1EN) != 0)
#define DMA2_IS_CLK_ENABLED() ((RCC->AHB1ENR & RCC_AHB1ENR_DMA2EN) != 0)

static int get_dma_id(DMA_Stream_TypeDef *dma_stream) {
if ((uint32_t)dma_stream < DMA2_BASE) {
return ((uint32_t)dma_stream - DMA1_Stream0_BASE) / 0x18;
int dma_id;
if (dma_stream < DMA2_Stream0) {
dma_id = dma_stream - DMA1_Stream0;
} else {
dma_id = NSTREAMS_PER_CONTROLLER + (dma_stream - DMA2_Stream0);
}
return dma_id;
}

// Resets the idle counter for the DMA controller associated with dma_id.
static void dma_tickle(int dma_id) {
if (dma_id < NSTREAMS_PER_CONTROLLER) {
dma_idle.counter[0] = 1;
} else {
dma_idle.counter[1] = 1;
}
}

static void dma_enable_clock(int dma_id) {
// We don't want dma_tick_handler() to turn off the clock right after we
// enable it, so we need to mark the channel in use in an atomic fashion.
mp_uint_t irq_state = MICROPY_BEGIN_ATOMIC_SECTION();
uint32_t old_enable_mask = dma_enable_mask;
dma_enable_mask |= (1 << dma_id);
MICROPY_END_ATOMIC_SECTION(irq_state);

if (dma_id <= 7) {
if (((old_enable_mask & DMA1_ENABLE_MASK) == 0) && !DMA1_IS_CLK_ENABLED()) {
__DMA1_CLK_ENABLE();

// We just turned on the clock. This means that anything stored
// in dma_last_channel (for DMA1) needs to be invalidated.

for (int channel = 0; channel < NSTREAMS_PER_CONTROLLER; channel++) {
dma_last_channel[channel] = DMA_INVALID_CHANNEL;
}
}
} else {
return (NSTREAM / 2) + ((uint32_t)dma_stream - DMA2_Stream0_BASE) / 0x18;
if (((old_enable_mask & DMA2_ENABLE_MASK) == 0) && !DMA2_IS_CLK_ENABLED()) {
__DMA2_CLK_ENABLE();

// We just turned on the clock. This means that anything stored
// in dma_last_channel (for DMA1) needs to be invalidated.

for (int channel = NSTREAMS_PER_CONTROLLER; channel < NSTREAM; channel++) {
dma_last_channel[channel] = DMA_INVALID_CHANNEL;
}
}
}
}

static void dma_disable_clock(int dma_id) {
// We just mark the clock as disabled here, but we don't actually disable it.
// We wait for the timer to expire first, which means that back-to-back
// transfers don't have to initialize as much.
dma_tickle(dma_id);
dma_enable_mask &= ~(1 << dma_id);
}

void dma_init(DMA_HandleTypeDef *dma, DMA_Stream_TypeDef *dma_stream, const DMA_InitTypeDef *dma_init, uint32_t dma_channel, uint32_t direction, void *data) {
int dma_id = get_dma_id(dma_stream);
//printf("dma_init(%p, %p(%d), 0x%x, 0x%x, %p)\n", dma, dma_stream, dma_id, (uint)dma_channel, (uint)direction, data);

// TODO possibly don't need to clear the entire structure
// Some drivers allocate the DMA_HandleTypeDef from the stack
// (i.e. dac, i2c, spi) and for those cases we need to clear the
// structure so we don't get random values from the stack)
memset(dma, 0, sizeof(*dma));

// set global pointer for IRQ handler
Expand All @@ -119,22 +187,20 @@ void dma_init(DMA_HandleTypeDef *dma, DMA_Stream_TypeDef *dma_stream, const DMA_
// caller must implement other half by doing: data->xxx = dma
dma->Parent = data;

dma_enable_clock(dma_id);

// if this stream was previously configured for this channel then we
// can skip most of the initialisation
if (dma_last_channel[dma_id] == dma_channel) {
uint8_t channel_uint8 = DMA_CHANNEL_AS_UINT8(dma_channel);
if (dma_last_channel[dma_id] == channel_uint8) {
goto same_channel;
}
dma_last_channel[dma_id] = dma_channel;

// enable clock for needed DMA peripheral
if (dma_id <= 7) {
__DMA1_CLK_ENABLE();
} else {
__DMA2_CLK_ENABLE();
}
dma_last_channel[dma_id] = channel_uint8;

// reset and configure DMA peripheral
HAL_DMA_DeInit(dma);
if (HAL_DMA_GetState(dma) != HAL_DMA_STATE_RESET) {
HAL_DMA_DeInit(dma);
}
HAL_DMA_Init(dma);
HAL_NVIC_SetPriority(dma_irqn[dma_id], IRQ_PRI_DMA, IRQ_SUBPRI_DMA);

Expand All @@ -146,11 +212,41 @@ void dma_deinit(DMA_HandleTypeDef *dma) {
int dma_id = get_dma_id(dma->Instance);
HAL_NVIC_DisableIRQ(dma_irqn[dma_id]);
dma_handle[dma_id] = NULL;

dma_disable_clock(dma_id);
}

void dma_invalidate_channel(DMA_Stream_TypeDef *dma_stream, uint32_t dma_channel) {
int dma_id = get_dma_id(dma_stream);
if (dma_last_channel[dma_id] == dma_channel) {
dma_last_channel[dma_id] = 0xffffffff;
if (dma_last_channel[dma_id] == DMA_CHANNEL_AS_UINT8(dma_channel)) {
dma_last_channel[dma_id] = DMA_INVALID_CHANNEL;
}
}

// Called from the SysTick handler (once per millisecond)
void dma_idle_handler() {
static const uint32_t controller_mask[] = {
DMA1_ENABLE_MASK, DMA2_ENABLE_MASK
};
for (int controller = 0; controller < NCONTROLLERS; controller++) {
if (dma_idle.counter[controller] == 0) {
continue;
}
if (++dma_idle.counter[controller] > DMA_IDLE_TICK_MAX) {
if ((dma_enable_mask & controller_mask[controller]) == 0) {
// Nothing is active and we've reached our idle timeout,
// Now we'll really disable the clock.
dma_idle.counter[controller] = 0;
if (controller == 0) {
__DMA1_CLK_DISABLE();
} else {
__DMA2_CLK_DISABLE();
}
} else {
// Something is still active, but the counter never got
// reset, so we'll reset the counter here.
dma_idle.counter[controller] = 1;
}
}
}
}
20 changes: 20 additions & 0 deletions stmhal/dma.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,28 @@
* THE SOFTWARE.
*/

//TODO: Put stream/channel defs for i2c/spi/can, etc here
#define DMA_STREAM_SDIO_RX DMA2_Stream3
#define DMA_CHANNEL_SDIO_RX DMA_CHANNEL_4

#define DMA_STREAM_SDIO_TX DMA2_Stream6
#define DMA_CHANNEL_SDIO_TX DMA_CHANNEL_4

typedef union {
uint16_t enabled; // Used to test if both counters are == 0
uint8_t counter[2];
} dma_idle_count_t;
extern volatile dma_idle_count_t dma_idle;
#define DMA_IDLE_ENABLED() (dma_idle.enabled != 0)

#define DMA_SYSTICK_MASK 0x0F
#define DMA_MSECS_PER_SYSTICK (DMA_SYSTICK_MASK + 1)
#define DMA_IDLE_TICK_MAX (8) // 128 msec
#define DMA_IDLE_TICK(tick) (((tick) & DMA_SYSTICK_MASK) == 0)

extern const DMA_InitTypeDef dma_init_struct_spi_i2c;

void dma_init(DMA_HandleTypeDef *dma, DMA_Stream_TypeDef *dma_stream, const DMA_InitTypeDef *dma_init, uint32_t dma_channel, uint32_t direction, void *data);
void dma_deinit(DMA_HandleTypeDef *dma);
void dma_invalidate_channel(DMA_Stream_TypeDef *dma_stream, uint32_t dma_channel);
void dma_idle_handler();
5 changes: 5 additions & 0 deletions stmhal/stm32_it.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
#include "uart.h"
#include "storage.h"
#include "can.h"
#include "dma.h"

extern void __fatal_error(const char*);
extern PCD_HandleTypeDef pcd_handle;
Expand Down Expand Up @@ -267,6 +268,10 @@ void SysTick_Handler(void) {
// the COUNTFLAG bit, which makes the logic in sys_tick_get_microseconds
// work properly.
SysTick->CTRL;

if (DMA_IDLE_ENABLED() && DMA_IDLE_TICK(uwTick)) {
dma_idle_handler();
}
}

/******************************************************************************/
Expand Down

0 comments on commit a1ebf69

Please sign in to comment.