397 lines
9.9 KiB
C
397 lines
9.9 KiB
C
/* Copyright 2012 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.
|
|
*/
|
|
|
|
/* USART driver for Chrome EC */
|
|
|
|
#include "common.h"
|
|
#include "clock.h"
|
|
#include "dma.h"
|
|
#include "gpio.h"
|
|
#include "hooks.h"
|
|
#include "registers.h"
|
|
#include "system.h"
|
|
#include "task.h"
|
|
#include "uart.h"
|
|
#include "util.h"
|
|
#include "stm32-dma.h"
|
|
|
|
/* Console USART index */
|
|
#define UARTN CONFIG_UART_CONSOLE
|
|
#define UARTN_BASE STM32_USART_BASE(CONFIG_UART_CONSOLE)
|
|
|
|
#ifdef CONFIG_UART_TX_DMA
|
|
#define UART_TX_INT_ENABLE STM32_USART_CR1_TCIE
|
|
|
|
#ifndef CONFIG_UART_TX_DMA_CH
|
|
#define CONFIG_UART_TX_DMA_CH STM32_DMAC_USART1_TX
|
|
#endif
|
|
|
|
/* DMA channel options; assumes UART1 */
|
|
static const struct dma_option dma_tx_option = {
|
|
CONFIG_UART_TX_DMA_CH, (void *)&STM32_USART_TDR(UARTN_BASE),
|
|
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT
|
|
#ifdef CHIP_FAMILY_STM32F4
|
|
| STM32_DMA_CCR_CHANNEL(CONFIG_UART_TX_REQ_CH)
|
|
#endif
|
|
};
|
|
|
|
#else
|
|
#define UART_TX_INT_ENABLE STM32_USART_CR1_TXEIE
|
|
#endif
|
|
|
|
#ifdef CONFIG_UART_RX_DMA
|
|
|
|
#ifndef CONFIG_UART_RX_DMA_CH
|
|
#define CONFIG_UART_RX_DMA_CH STM32_DMAC_USART1_RX
|
|
#endif
|
|
/* DMA channel options; assumes UART1 */
|
|
static const struct dma_option dma_rx_option = {
|
|
CONFIG_UART_RX_DMA_CH, (void *)&STM32_USART_RDR(UARTN_BASE),
|
|
STM32_DMA_CCR_MSIZE_8_BIT | STM32_DMA_CCR_PSIZE_8_BIT |
|
|
#ifdef CHIP_FAMILY_STM32F4
|
|
STM32_DMA_CCR_CHANNEL(CONFIG_UART_RX_REQ_CH) |
|
|
#endif
|
|
STM32_DMA_CCR_CIRC
|
|
};
|
|
|
|
static int dma_rx_len; /* Size of receive DMA circular buffer */
|
|
#endif
|
|
|
|
static int init_done; /* Initialization done? */
|
|
static int should_stop; /* Last TX control action */
|
|
|
|
int uart_init_done(void)
|
|
{
|
|
return init_done;
|
|
}
|
|
|
|
void uart_tx_start(void)
|
|
{
|
|
/* If interrupt is already enabled, nothing to do */
|
|
if (STM32_USART_CR1(UARTN_BASE) & UART_TX_INT_ENABLE)
|
|
return;
|
|
|
|
disable_sleep(SLEEP_MASK_UART);
|
|
should_stop = 0;
|
|
STM32_USART_CR1(UARTN_BASE) |= UART_TX_INT_ENABLE |
|
|
STM32_USART_CR1_TCIE;
|
|
task_trigger_irq(STM32_IRQ_USART(UARTN));
|
|
}
|
|
|
|
void uart_tx_stop(void)
|
|
{
|
|
STM32_USART_CR1(UARTN_BASE) &= ~UART_TX_INT_ENABLE;
|
|
should_stop = 1;
|
|
#ifdef CONFIG_UART_TX_DMA
|
|
enable_sleep(SLEEP_MASK_UART);
|
|
#endif
|
|
}
|
|
|
|
void uart_tx_flush(void)
|
|
{
|
|
while (!(STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_TXE))
|
|
;
|
|
}
|
|
|
|
int uart_tx_ready(void)
|
|
{
|
|
return STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_TXE;
|
|
}
|
|
|
|
#ifdef CONFIG_UART_TX_DMA
|
|
|
|
int uart_tx_dma_ready(void)
|
|
{
|
|
return STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_TC;
|
|
}
|
|
|
|
void uart_tx_dma_start(const char *src, int len)
|
|
{
|
|
/* Prepare DMA */
|
|
dma_prepare_tx(&dma_tx_option, len, src);
|
|
|
|
/* Force clear TC so we don't re-interrupt */
|
|
STM32_USART_SR(UARTN_BASE) &= ~STM32_USART_SR_TC;
|
|
|
|
/* Enable TCIE (chrome-os-partner:28837) */
|
|
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_TCIE;
|
|
|
|
/* Start DMA */
|
|
dma_go(dma_get_channel(dma_tx_option.channel));
|
|
}
|
|
|
|
#endif /* CONFIG_UART_TX_DMA */
|
|
|
|
int uart_rx_available(void)
|
|
{
|
|
return STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_RXNE;
|
|
}
|
|
|
|
#ifdef CONFIG_UART_RX_DMA
|
|
|
|
void uart_rx_dma_start(char *dest, int len)
|
|
{
|
|
/* Start receiving */
|
|
dma_rx_len = len;
|
|
dma_start_rx(&dma_rx_option, len, dest);
|
|
}
|
|
|
|
int uart_rx_dma_head(void)
|
|
{
|
|
return dma_bytes_done(dma_get_channel(CONFIG_UART_RX_DMA_CH),
|
|
dma_rx_len);
|
|
}
|
|
|
|
#endif
|
|
|
|
void uart_write_char(char c)
|
|
{
|
|
/* Wait for space */
|
|
while (!uart_tx_ready())
|
|
;
|
|
|
|
STM32_USART_TDR(UARTN_BASE) = c;
|
|
}
|
|
|
|
int uart_read_char(void)
|
|
{
|
|
return STM32_USART_RDR(UARTN_BASE);
|
|
}
|
|
|
|
/* Interrupt handler for console USART */
|
|
void uart_interrupt(void)
|
|
{
|
|
#ifndef CONFIG_UART_TX_DMA
|
|
/*
|
|
* When transmission completes, enable sleep if we are done with Tx.
|
|
* After that, proceed if there is other interrupt to handle.
|
|
*/
|
|
if (STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_TC) {
|
|
if (should_stop) {
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_TCIE;
|
|
enable_sleep(SLEEP_MASK_UART);
|
|
}
|
|
#if defined(CHIP_FAMILY_STM32F4)
|
|
STM32_USART_SR(UARTN_BASE) &= ~STM32_USART_SR_TC;
|
|
#else
|
|
STM32_USART_ICR(UARTN_BASE) |= STM32_USART_SR_TC;
|
|
#endif
|
|
if (!(STM32_USART_SR(UARTN_BASE) & ~STM32_USART_SR_TC))
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_UART_TX_DMA
|
|
/* Disable transmission complete interrupt if DMA done */
|
|
if (STM32_USART_SR(UARTN_BASE) & STM32_USART_SR_TC)
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_TCIE;
|
|
#else
|
|
/*
|
|
* Disable the TX empty interrupt before filling the TX buffer since it
|
|
* needs an actual write to DR to be cleared.
|
|
*/
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_TXEIE;
|
|
#endif
|
|
|
|
#ifndef CONFIG_UART_RX_DMA
|
|
/*
|
|
* Read input FIFO until empty. DMA-based receive does this from a
|
|
* hook in the UART buffering module.
|
|
*/
|
|
uart_process_input();
|
|
#endif
|
|
|
|
/* Fill output FIFO */
|
|
uart_process_output();
|
|
|
|
#ifndef CONFIG_UART_TX_DMA
|
|
/*
|
|
* Re-enable TX empty interrupt only if it was not disabled by
|
|
* uart_process_output().
|
|
*/
|
|
if (!should_stop)
|
|
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_TXEIE;
|
|
#endif
|
|
}
|
|
DECLARE_IRQ(STM32_IRQ_USART(UARTN), uart_interrupt, 2);
|
|
|
|
/**
|
|
* Handle clock frequency changes
|
|
*/
|
|
static void uart_freq_change(void)
|
|
{
|
|
int freq;
|
|
int div;
|
|
|
|
#if (defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3)) && \
|
|
(UARTN <= 2)
|
|
/*
|
|
* UART is clocked from HSI (8MHz) to allow it to work when waking
|
|
* up from sleep
|
|
*/
|
|
freq = 8000000;
|
|
#elif defined(CHIP_FAMILY_STM32H7)
|
|
freq = 64000000; /* from 64 Mhz HSI */
|
|
#else
|
|
/* UART clocked from the main clock */
|
|
freq = clock_get_freq();
|
|
#endif
|
|
|
|
#if (UARTN == 9) /* LPUART */
|
|
div = DIV_ROUND_NEAREST(freq, CONFIG_UART_BAUD_RATE) * 256;
|
|
#else
|
|
div = DIV_ROUND_NEAREST(freq, CONFIG_UART_BAUD_RATE);
|
|
#endif
|
|
|
|
#if defined(CHIP_FAMILY_STM32L) || defined(CHIP_FAMILY_STM32F0) || \
|
|
defined(CHIP_FAMILY_STM32F3) || defined(CHIP_FAMILY_STM32L4) || \
|
|
defined(CHIP_FAMILY_STM32F4)
|
|
if (div / 16 > 0) {
|
|
/*
|
|
* CPU clock is high enough to support x16 oversampling.
|
|
* BRR = (div mantissa)<<4 | (4-bit div fraction)
|
|
*/
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_OVER8;
|
|
STM32_USART_BRR(UARTN_BASE) = div;
|
|
} else {
|
|
/*
|
|
* CPU clock is low; use x8 oversampling.
|
|
* BRR = (div mantissa)<<4 | (3-bit div fraction)
|
|
*/
|
|
STM32_USART_BRR(UARTN_BASE) = ((div / 8) << 4) | (div & 7);
|
|
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_OVER8;
|
|
}
|
|
#else
|
|
/* STM32F only supports x16 oversampling */
|
|
STM32_USART_BRR(UARTN_BASE) = div;
|
|
#endif
|
|
|
|
}
|
|
DECLARE_HOOK(HOOK_FREQ_CHANGE, uart_freq_change, HOOK_PRIO_DEFAULT);
|
|
|
|
void uart_init(void)
|
|
{
|
|
/* Select clock source */
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3)
|
|
#if (UARTN == 1)
|
|
STM32_RCC_CFGR3 |= 0x0003; /* USART1 clock source from HSI(8MHz) */
|
|
#elif (UARTN == 2)
|
|
STM32_RCC_CFGR3 |= 0x030000; /* USART2 clock source from HSI(8MHz) */
|
|
#endif /* UARTN */
|
|
#elif defined(CHIP_FAMILY_STM32H7) /* Clocked from 64 Mhz HSI */
|
|
#if ((UARTN == 1) || (UARTN == 6))
|
|
STM32_RCC_D2CCIP2R |= STM32_RCC_D2CCIP2_USART16SEL_HSI;
|
|
#else
|
|
STM32_RCC_D2CCIP2R |= STM32_RCC_D2CCIP2_USART234578SEL_HSI;
|
|
#endif /* UARTN */
|
|
#elif defined(CHIP_FAMILY_STM32L4)
|
|
/* USART1 clock source from SYSCLK */
|
|
STM32_RCC_CCIPR &= ~STM32_RCC_CCIPR_USART1SEL_MASK;
|
|
STM32_RCC_CCIPR |=
|
|
(STM32_RCC_CCIPR_UART_SYSCLK << STM32_RCC_CCIPR_USART1SEL_SHIFT);
|
|
/* LPUART1 clock source from SYSCLK */
|
|
STM32_RCC_CCIPR &= ~STM32_RCC_CCIPR_LPUART1SEL_MASK;
|
|
STM32_RCC_CCIPR |=
|
|
(STM32_RCC_CCIPR_UART_SYSCLK << STM32_RCC_CCIPR_LPUART1SEL_SHIFT);
|
|
#endif /* CHIP_FAMILY_STM32F0 || CHIP_FAMILY_STM32F3 */
|
|
|
|
/* Enable USART clock */
|
|
#if (UARTN == 1)
|
|
STM32_RCC_APB2ENR |= STM32_RCC_PB2_USART1;
|
|
#elif (UARTN == 6)
|
|
STM32_RCC_APB2ENR |= STM32_RCC_PB2_USART6;
|
|
#elif (UARTN == 9)
|
|
STM32_RCC_APB1ENR2 |= STM32_RCC_APB1ENR2_LPUART1EN;
|
|
#else
|
|
STM32_RCC_APB1ENR |= CONCAT2(STM32_RCC_PB1_USART, UARTN);
|
|
#endif
|
|
|
|
/*
|
|
* For STM32F3, A delay of 1 APB clock cycles is needed before we
|
|
* can access any USART register. Fortunately, we have
|
|
* gpio_config_module() below and thus don't need to add the delay.
|
|
*/
|
|
|
|
/* Configure GPIOs */
|
|
gpio_config_module(MODULE_UART, 1);
|
|
|
|
#if defined(CHIP_FAMILY_STM32F0) || defined(CHIP_FAMILY_STM32F3) \
|
|
|| defined(CHIP_FAMILY_STM32H7)
|
|
/*
|
|
* Wake up on start bit detection. WUS can only be written when UE=0,
|
|
* so clear UE first.
|
|
*/
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_UE;
|
|
|
|
/*
|
|
* Also disable the RX overrun interrupt, since we don't care about it
|
|
* and we don't want to clear an extra flag in the interrupt
|
|
*/
|
|
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_WUS_START_BIT |
|
|
STM32_USART_CR3_OVRDIS;
|
|
#endif
|
|
|
|
/*
|
|
* UART enabled, 8 Data bits, oversampling x16, no parity,
|
|
* TX and RX enabled.
|
|
*/
|
|
STM32_USART_CR1(UARTN_BASE) =
|
|
STM32_USART_CR1_UE | STM32_USART_CR1_TE | STM32_USART_CR1_RE;
|
|
|
|
/* 1 stop bit, no fancy stuff */
|
|
STM32_USART_CR2(UARTN_BASE) = 0x0000;
|
|
|
|
#ifdef CONFIG_UART_TX_DMA
|
|
/* Enable DMA transmitter */
|
|
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_DMAT;
|
|
#ifdef CONFIG_UART_TX_DMA_PH
|
|
dma_select_channel(CONFIG_UART_TX_DMA_CH, CONFIG_UART_TX_DMA_PH);
|
|
#endif
|
|
#else
|
|
/* DMA disabled, special modes disabled, error interrupt disabled */
|
|
STM32_USART_CR3(UARTN_BASE) &= ~STM32_USART_CR3_DMAR &
|
|
~STM32_USART_CR3_DMAT &
|
|
~STM32_USART_CR3_EIE;
|
|
#endif
|
|
|
|
#ifdef CONFIG_UART_RX_DMA
|
|
/* Enable DMA receiver */
|
|
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_DMAR;
|
|
#else
|
|
/* Enable receive-not-empty interrupt */
|
|
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_RXNEIE;
|
|
#endif
|
|
|
|
#if defined(CHIP_FAMILY_STM32L) || defined(CHIP_FAMILY_STM32F4)
|
|
/* Use single-bit sampling */
|
|
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_ONEBIT;
|
|
#endif
|
|
|
|
/* Set initial baud rate */
|
|
uart_freq_change();
|
|
|
|
/* Enable interrupts */
|
|
task_enable_irq(STM32_IRQ_USART(UARTN));
|
|
|
|
init_done = 1;
|
|
}
|
|
|
|
#ifdef CONFIG_FORCE_CONSOLE_RESUME
|
|
void uart_enable_wakeup(int enable)
|
|
{
|
|
if (enable) {
|
|
/*
|
|
* Allow UART wake up from STOP mode. Note, UART clock must
|
|
* be HSI(8MHz) for wakeup to work.
|
|
*/
|
|
STM32_USART_CR1(UARTN_BASE) |= STM32_USART_CR1_UESM;
|
|
STM32_USART_CR3(UARTN_BASE) |= STM32_USART_CR3_WUFIE;
|
|
} else {
|
|
/* Disable wake up from STOP mode. */
|
|
STM32_USART_CR1(UARTN_BASE) &= ~STM32_USART_CR1_UESM;
|
|
}
|
|
}
|
|
#endif
|