/* * Copyright (C) 2020 Koen Zandberg * * This file is subject to the terms and conditions of the GNU Lesser General * Public License v2.1. See the file LICENSE in the top level directory for more * details. */ /** * @ingroup cpu_gd32v * @{ * * @file * @brief GD32V UART peripheral implementation * * @author Koen Zandberg */ #include #include #include "periph/uart.h" #include "periph/gpio.h" #include "periph_cpu.h" #include "cpu.h" #include "clic.h" #define RXENABLE (USART_CTL0_REN_Msk | USART_CTL0_RBNEIE_Msk) /** * @brief Allocate memory to store the callback functions * * Extend standard uart_isr_ctx_t with data_mask field. This is needed * in order to mask parity bit. */ static struct { uart_rx_cb_t rx_cb; /**< data received interrupt callback */ void *arg; /**< argument to both callback routines */ uint8_t data_mask; /**< mask applied to the data register */ } isr_ctx[UART_NUMOF]; static inline void _uart_isr(unsigned uart); static inline USART_Type *dev(uart_t uart) { return uart_config[uart].dev; } static inline void uart_init_pins(uart_t uart, uart_rx_cb_t rx_cb) { /* configure TX pin */ gpio_init_af(uart_config[uart].tx_pin, GPIO_AF_OUT_PP); /* configure RX pin */ if (rx_cb) { gpio_init(uart_config[uart].rx_pin, GPIO_IN_PU); } } static inline void uart_enable_clock(uart_t uart) { /* TODO: add pm blocker */ periph_clk_en(uart_config[uart].bus, uart_config[uart].rcu_mask); } static inline void uart_disable_clock(uart_t uart) { periph_clk_dis(uart_config[uart].bus, uart_config[uart].rcu_mask); /* TODO remove pm blocker */ } static inline void uart_init_usart(uart_t uart, uint32_t baudrate) { uint16_t mantissa; uint8_t fraction; uint32_t clk; /* calculate and apply baudrate */ clk = periph_apb_clk(uart_config[uart].bus) / baudrate; mantissa = (uint16_t)(clk / 16); fraction = (uint8_t)(clk - (mantissa * 16)); dev(uart)->BAUD = ((mantissa & 0x0fff) << 4) | (fraction & 0x0f); } int uart_init(uart_t uart, uint32_t baudrate, uart_rx_cb_t rx_cb, void *arg) { assert(uart < UART_NUMOF); /* save ISR context */ isr_ctx[uart].rx_cb = rx_cb; isr_ctx[uart].arg = arg; isr_ctx[uart].data_mask = 0xFF; uart_enable_clock(uart); /* reset UART configuration -> defaults to 8N1 mode */ dev(uart)->CTL0 = 0; dev(uart)->CTL1 = 0; dev(uart)->CTL2 = 0; uart_init_usart(uart, baudrate); /* Attach pins to enabled UART periph. Note: It is important that the UART * interface is configured prior to attaching the pins, as otherwise the * signal level flickers during initialization resulting in garbage being * sent. */ uart_init_pins(uart, rx_cb); /* enable RX interrupt if applicable */ if (rx_cb) { clic_set_handler(uart_config[uart].irqn, _uart_isr); clic_enable_interrupt(uart_config[uart].irqn, CPU_DEFAULT_IRQ_PRIO); dev(uart)->CTL0 = (USART_CTL0_UEN_Msk | USART_CTL0_TEN_Msk | RXENABLE); } else { dev(uart)->CTL0 = (USART_CTL0_UEN_Msk | USART_CTL0_TEN_Msk); } return UART_OK; } #ifdef MODULE_PERIPH_UART_MODECFG int uart_mode(uart_t uart, uart_data_bits_t data_bits, uart_parity_t parity, uart_stop_bits_t stop_bits) { assert(uart < UART_NUMOF); isr_ctx[uart].data_mask = 0xFF; if (parity) { switch (data_bits) { case UART_DATA_BITS_6: data_bits = UART_DATA_BITS_7; isr_ctx[uart].data_mask = 0x3F; break; case UART_DATA_BITS_7: data_bits = UART_DATA_BITS_8; isr_ctx[uart].data_mask = 0x7F; break; case UART_DATA_BITS_8: #ifdef USART_CTL0_M0 data_bits = USART_CTL0_M0; #else data_bits = USART_CTL0_M; #endif break; default: return UART_NOMODE; } } if ((data_bits & UART_INVALID_MODE) || (parity & UART_INVALID_MODE)) { return UART_NOMODE; } #ifdef USART_CTL0_M1 if (!(dev(uart)->ISR & USART_ISR_TC)) { return UART_INTERR; } dev(uart)->CTL0 &= ~(USART_CTL0_UEN_Msk | USART_CTL0_TEN_Msk); #endif dev(uart)->CTL1 &= ~USART_CTL1_STOP; dev(uart)->CTL0 &= ~(USART_CTL0_PS | USART_CTL0_PCE | USART_CTL0_M); dev(uart)->CTL1 |= stop_bits; dev(uart)->CTL0 |= (USART_CTL0_UE | USART_CTL0_TE | data_bits | parity); return UART_OK; } #endif /* MODULE_PERIPH_UART_MODECFG */ static inline void send_byte(uart_t uart, uint8_t byte) { while (!(dev(uart)->STAT & USART_STAT_TBE_Msk)) {} dev(uart)->DATA = byte; } static inline void wait_for_tx_complete(uart_t uart) { while (!(dev(uart)->STAT & USART_STAT_TC_Msk)) {} } void uart_write(uart_t uart, const uint8_t *data, size_t len) { assert(uart < UART_NUMOF); #if DEVELHELP /* If tx is not enabled don't try to send */ if (!(dev(uart)->CTL0 & USART_CTL0_TEN_Msk)) { return; } #endif for (size_t i = 0; i < len; i++) { send_byte(uart, data[i]); } /* make sure the function is synchronous by waiting for the transfer to * finish */ wait_for_tx_complete(uart); } void uart_poweron(uart_t uart) { assert(uart < UART_NUMOF); uart_enable_clock(uart); dev(uart)->CTL0 |= (USART_CTL0_UEN_Msk); } void uart_poweroff(uart_t uart) { assert(uart < UART_NUMOF); dev(uart)->CTL0 &= ~(USART_CTL0_UEN_Msk); uart_disable_clock(uart); } static inline void _irq_handler(uart_t uart) { uint32_t status = dev(uart)->STAT; if (status & USART_STAT_RBNE_Msk) { isr_ctx[uart].rx_cb(isr_ctx[uart].arg, (uint8_t)dev(uart)->DATA & isr_ctx[uart].data_mask); } if (status & USART_STAT_ORERR_Msk) { dev(uart)->DATA; } } static void _uart_isr(unsigned irq) { switch (irq) { #ifdef UART_0_IRQN case UART_0_IRQN: _irq_handler(UART_DEV(0)); break; #endif #ifdef UART_1_IRQN case UART_1_IRQN: _irq_handler(UART_DEV(1)); break; #endif #ifdef UART_2_IRQN case UART_2_IRQN: _irq_handler(UART_DEV(2)); break; #endif #ifdef UART_3_IRQN case UART_3_IRQN: _irq_handler(UART_DEV(3)); break; #endif #ifdef UART_4_IRQN case UART_4_IRQN: _irq_handler(UART_DEV(4)); break; #endif #ifdef UART_5_IRQN case UART_5_IRQN: _irq_handler(UART_DEV(5)); break; #endif default: assert(false); } }