diff --git a/board/ryu/board.c b/board/ryu/board.c index f8d8615f47..297ba2512e 100644 --- a/board/ryu/board.c +++ b/board/ryu/board.c @@ -37,6 +37,7 @@ #include "usb-stm32f3.h" #include "usb-stream.h" #include "usart-stm32f3.h" +#include "usart_tx_dma.h" #include "util.h" #include "pi3usb9281.h" @@ -131,12 +132,16 @@ static struct queue const sh_usb_to_usart = QUEUE_DIRECT(64, uint8_t, sh_usb.producer, sh_usart.consumer); -static struct usart_config const ap_usart = USART_CONFIG(usart1_hw, - usart_rx_interrupt, - usart_tx_interrupt, - 115200, - ap_usart_to_usb, - ap_usb_to_usart); +static struct usart_tx_dma const ap_usart_tx_dma = + USART_TX_DMA(STM32_DMAC_USART1_TX, 16); + +static struct usart_config const ap_usart = + USART_CONFIG(usart1_hw, + usart_rx_interrupt, + ap_usart_tx_dma.usart_tx, + 115200, + ap_usart_to_usb, + ap_usb_to_usart); static struct usart_config const sh_usart = USART_CONFIG(usart3_hw, usart_rx_interrupt, diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index d4a8152897..8ec6191397 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -36,6 +36,7 @@ chip-$(CONFIG_COMMON_TIMER)+=hwtimer$(TIMER_TYPE).o chip-$(CONFIG_I2C)+=i2c-$(CHIP_FAMILY).o chip-$(CONFIG_STREAM_USART)+=usart.o usart-$(CHIP_FAMILY).o chip-$(CONFIG_STREAM_USART)+=usart_rx_interrupt.o usart_tx_interrupt.o +chip-$(CONFIG_STREAM_USART)+=usart_tx_dma.o chip-$(CONFIG_STREAM_USB)+=usb-stream.o chip-$(CONFIG_WATCHDOG)+=watchdog.o chip-$(HAS_TASK_CONSOLE)+=uart.o diff --git a/chip/stm32/registers.h b/chip/stm32/registers.h index d008ee0167..f4416d9fc6 100644 --- a/chip/stm32/registers.h +++ b/chip/stm32/registers.h @@ -185,6 +185,7 @@ #define STM32_USART_RQR(base) STM32_USART_REG(base, 0x18) #define STM32_USART_ISR(base) STM32_USART_REG(base, 0x1C) #define STM32_USART_ICR(base) STM32_USART_REG(base, 0x20) +#define STM32_USART_ICR_TCCF (1 << 6) #define STM32_USART_RDR(base) STM32_USART_REG(base, 0x24) #define STM32_USART_TDR(base) STM32_USART_REG(base, 0x28) /* register alias */ diff --git a/chip/stm32/usart-stm32f0.c b/chip/stm32/usart-stm32f0.c index 4351966da6..f5f6c910e6 100644 --- a/chip/stm32/usart-stm32f0.c +++ b/chip/stm32/usart-stm32f0.c @@ -66,6 +66,11 @@ static void freq_change(void) DECLARE_HOOK(HOOK_FREQ_CHANGE, freq_change, HOOK_PRIO_DEFAULT); +void usart_clear_tc(struct usart_config const *config) +{ + STM32_USART_ICR(config->hw->base) |= STM32_USART_ICR_TCCF; +} + /* * USART interrupt bindings. These functions can not be defined as static or * they will be removed by the linker because of the way that DECLARE_IRQ works. diff --git a/chip/stm32/usart-stm32f3.c b/chip/stm32/usart-stm32f3.c index 58eb500b1d..9484fcdbd7 100644 --- a/chip/stm32/usart-stm32f3.c +++ b/chip/stm32/usart-stm32f3.c @@ -46,6 +46,11 @@ static struct usart_hw_ops const usart_variant_hw_ops = { .disable = usart_variant_disable, }; +void usart_clear_tc(struct usart_config const *config) +{ + STM32_USART_ICR(config->hw->base) |= STM32_USART_ICR_TCCF; +} + /* * USART interrupt bindings. These functions can not be defined as static or * they will be removed by the linker because of the way that DECLARE_IRQ works. diff --git a/chip/stm32/usart-stm32l.c b/chip/stm32/usart-stm32l.c index 0fd98df7c0..0ac9d092b2 100644 --- a/chip/stm32/usart-stm32l.c +++ b/chip/stm32/usart-stm32l.c @@ -59,6 +59,11 @@ static void freq_change(void) DECLARE_HOOK(HOOK_FREQ_CHANGE, freq_change, HOOK_PRIO_DEFAULT); +void usart_clear_tc(struct usart_config const *config) +{ + STM32_USART_SR(config->hw->base) &= ~STM32_USART_SR_TC; +} + /* * USART interrupt bindings. These functions can not be defined as static or * they will be removed by the linker because of the way that DECLARE_IRQ works. diff --git a/chip/stm32/usart.h b/chip/stm32/usart.h index 589af6c26b..c0003c6cb9 100644 --- a/chip/stm32/usart.h +++ b/chip/stm32/usart.h @@ -176,4 +176,10 @@ void usart_interrupt(struct usart_config const *config); void usart_set_baud_f0_l(struct usart_config const *config, int frequency_hz); void usart_set_baud_f(struct usart_config const *config, int frequency_hz); +/* + * Different families provide different ways of clearing the transmit complete + * flag. This function will be provided by the family specific implementation. + */ +void usart_clear_tc(struct usart_config const *config); + #endif /* __CROS_EC_USART_H */ diff --git a/chip/stm32/usart_tx_dma.c b/chip/stm32/usart_tx_dma.c new file mode 100644 index 0000000000..c17f2ae1bb --- /dev/null +++ b/chip/stm32/usart_tx_dma.c @@ -0,0 +1,117 @@ +/* Copyright (c) 2015 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 "usart_tx_dma.h" + +#include "usart.h" +#include "common.h" +#include "registers.h" +#include "system.h" +#include "task.h" +#include "util.h" + +void usart_tx_dma_written(struct consumer const *consumer, size_t count) +{ + struct usart_config const *config = + DOWNCAST(consumer, struct usart_config, consumer); + + task_trigger_irq(config->hw->irq); +} + +void usart_tx_dma_flush(struct consumer const *consumer) +{ + struct usart_config const *config = + DOWNCAST(consumer, struct usart_config, consumer); + + /* + * Enable the USART interrupt. This causes the USART interrupt handler + * to start fetching from the TX queue if it wasn't already. + */ + task_trigger_irq(config->hw->irq); + + while (queue_count(consumer->queue)) + ; +} + +void usart_tx_dma_init(struct usart_config const *config) +{ + struct usart_tx_dma const *dma_config = + DOWNCAST(config->tx, struct usart_tx_dma const, usart_tx); + + intptr_t base = config->hw->base; + + STM32_USART_CR1(base) |= STM32_USART_CR1_TE; + STM32_USART_CR3(base) |= STM32_USART_CR3_DMAT; + + dma_config->state->dma_active = 0; +} + +static void usart_tx_dma_start(struct usart_config const *config, + struct usart_tx_dma const *dma_config) +{ + struct usart_tx_dma_state volatile *state = dma_config->state; + intptr_t base = config->hw->base; + + struct dma_option options = { + .channel = dma_config->channel, + .periph = (void *)&STM32_USART_TDR(base), + .flags = (STM32_DMA_CCR_MSIZE_8_BIT | + STM32_DMA_CCR_PSIZE_8_BIT), + }; + + /* + * Limit our DMA transfer. If we didn't do this then it would be + * possible to start a large DMA transfer of an entirely full buffer + * that would hold up any additional writes to the TX queue + * unnecessarily. + */ + state->chunk.length = MIN(state->chunk.length, dma_config->max_bytes); + + dma_prepare_tx(&options, state->chunk.length, state->chunk.buffer); + + state->dma_active = 1; + + usart_clear_tc(config); + STM32_USART_CR1(base) |= STM32_USART_CR1_TCIE; + + dma_go(dma_get_channel(options.channel)); +} + +static void usart_tx_dma_stop(struct usart_config const *config, + struct usart_tx_dma const *dma_config) +{ + dma_config->state->dma_active = 0; + + STM32_USART_CR1(config->hw->base) &= ~STM32_USART_CR1_TCIE; +} + +void usart_tx_dma_interrupt(struct usart_config const *config) +{ + struct usart_tx_dma const *dma_config = + DOWNCAST(config->tx, struct usart_tx_dma const, usart_tx); + struct usart_tx_dma_state volatile *state = dma_config->state; + + /* + * If we have completed a DMA transaction, or if we haven't yet started + * one then we clean up and start one now. + */ + if ((STM32_USART_SR(config->hw->base) & STM32_USART_SR_TC) || + !state->dma_active) { + struct queue const *queue = config->consumer.queue; + + /* + * Only advance the queue head (indicating that we have read + * units from the queue if we had an active DMA transfer. + */ + if (state->dma_active) + queue_advance_head(queue, state->chunk.length); + + state->chunk = queue_get_read_chunk(queue); + + if (state->chunk.length) + usart_tx_dma_start(config, dma_config); + else + usart_tx_dma_stop(config, dma_config); + } +} diff --git a/chip/stm32/usart_tx_dma.h b/chip/stm32/usart_tx_dma.h new file mode 100644 index 0000000000..a1e4e0831d --- /dev/null +++ b/chip/stm32/usart_tx_dma.h @@ -0,0 +1,90 @@ +/* Copyright (c) 2015 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. + * + * DMA based USART TX driver for STM32 + */ +#ifndef __CROS_EC_USART_TX_DMA_H +#define __CROS_EC_USART_TX_DMA_H + +#include "consumer.h" +#include "dma.h" +#include "queue.h" +#include "usart.h" + +/* + * Construct a USART TX instance for DMA using the given DMA channel. + * + * This macro creates a new usart_tx_dma struct, complete with in RAM state, + * the contained usart_tx struct can be used in initializing a usart_config + * struct. + * + * CHANNEL is the DMA channel to be used for transmission. This must be a + * valid DMA channel for the USART peripheral and any alternate channel + * mappings must be handled by the board specific code. + * + * MAX_BYTES is the maximum size in bytes of a single DMA transfer. This + * allows the board to tune how often the TX engine updates the queue state. + * A larger number here could cause the queue to appear full for longer than + * required because the queue isn't notified that it has been read from until + * after the DMA transfer completes. + */ +#define USART_TX_DMA(CHANNEL, MAX_BYTES) \ + ((struct usart_tx_dma const) { \ + .usart_tx = { \ + .consumer_ops = { \ + .written = usart_tx_dma_written,\ + .flush = usart_tx_dma_flush, \ + }, \ + \ + .init = usart_tx_dma_init, \ + .interrupt = usart_tx_dma_interrupt, \ + }, \ + \ + .state = &((struct usart_tx_dma_state){}), \ + .channel = CHANNEL, \ + .max_bytes = MAX_BYTES, \ + }) + +/* + * In RAM state required to manage DMA based transmission. + */ +struct usart_tx_dma_state { + /* + * The current chunk of queue buffer being used for transmission. Once + * the transfer is complete, this is used to update the TX queue head + * pointer as well. + */ + struct queue_chunk chunk; + + /* + * Flag indicating whether a DMA transfer is currently active. + */ + int dma_active; +}; + +/* + * Extension of the usart_tx struct to include required configuration for + * DMA based transmission. + */ +struct usart_tx_dma { + struct usart_tx usart_tx; + + struct usart_tx_dma_state volatile *state; + + enum dma_channel channel; + + size_t max_bytes; +}; + +/* + * Function pointers needed to intialize a usart_tx struct. These shouldn't + * be called in any other context as they assume that the consumer or config + * that they are passed was initialized with a complete usart_tx_dma struct. + */ +void usart_tx_dma_written(struct consumer const *consumer, size_t count); +void usart_tx_dma_flush(struct consumer const *consumer); +void usart_tx_dma_init(struct usart_config const *config); +void usart_tx_dma_interrupt(struct usart_config const *config); + +#endif /* __CROS_EC_USART_TX_DMA_H */