| /* 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) || defined(CHIP_FAMILY_STM32G4) |
| 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) || defined(CHIP_FAMILY_STM32G4) |
| /* 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 |