summaryrefslogtreecommitdiff
path: root/chip/stm32
diff options
context:
space:
mode:
authorAnton Staaf <robotboy@chromium.org>2015-07-15 15:03:40 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-07-21 18:30:40 +0000
commit1e3d00ff7ae32d58ac9c27ef09b22df58e48b1c4 (patch)
tree190ad8a9e3b463fb7acc6068b1cf73f0286baadd /chip/stm32
parentff9934264163dd0d7e795f32144c6c1082fdf040 (diff)
downloadchrome-ec-1e3d00ff7ae32d58ac9c27ef09b22df58e48b1c4.tar.gz
USART: Add DMA based transmitter
This adds a new transmission implementation for the multi UART driver. It is a DMA based transmitter that can directly read from the TX queue with zero copy overhead. The DMA channel used as well as the maximum DMA transmission size are configurable per UART at the board level. This also updates the Ryu AP UART to use DMA transmission. Signed-off-by: Anton Staaf <robotboy@chromium.org> BRANCH=None BUG=None TEST=make buildall -j Manually verify that the AP UART forwarding works Change-Id: I3cb27d0f9015043d75a38c12919388afe90dc4af Reviewed-on: https://chromium-review.googlesource.com/286274 Trybot-Ready: Anton Staaf <robotboy@chromium.org> Tested-by: Anton Staaf <robotboy@chromium.org> Reviewed-by: Randall Spangler <rspangler@chromium.org> Commit-Queue: Anton Staaf <robotboy@chromium.org>
Diffstat (limited to 'chip/stm32')
-rw-r--r--chip/stm32/build.mk1
-rw-r--r--chip/stm32/registers.h1
-rw-r--r--chip/stm32/usart-stm32f0.c5
-rw-r--r--chip/stm32/usart-stm32f3.c5
-rw-r--r--chip/stm32/usart-stm32l.c5
-rw-r--r--chip/stm32/usart.h6
-rw-r--r--chip/stm32/usart_tx_dma.c117
-rw-r--r--chip/stm32/usart_tx_dma.h90
8 files changed, 230 insertions, 0 deletions
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 */