serial_usart.c (8280B)
1 // Copyright 2021 QMK 2 // Copyright 2022 Stefan Kerkmann 3 // SPDX-License-Identifier: GPL-2.0-or-later 4 5 #include "serial_usart.h" 6 #include "serial_protocol.h" 7 #include "synchronization_util.h" 8 #include "chibios_config.h" 9 10 #if defined(SERIAL_USART_CONFIG) 11 static QMKSerialConfig serial_config = SERIAL_USART_CONFIG; 12 #elif defined(MCU_AT32) /* AT32 MCUs */ 13 static QMKSerialConfig serial_config = { 14 .speed = (SERIAL_USART_SPEED), 15 .ctrl1 = (SERIAL_USART_CTRL1), 16 .ctrl2 = (SERIAL_USART_CTRL2), 17 # if !defined(SERIAL_USART_FULL_DUPLEX) 18 .ctrl3 = ((SERIAL_USART_CTRL3) | USART_CTRL3_SLBEN) /* activate half-duplex mode */ 19 # else 20 .ctrl3 = (SERIAL_USART_CTRL3) 21 # endif 22 }; 23 #elif defined(MCU_STM32) /* STM32 MCUs */ 24 static QMKSerialConfig serial_config = { 25 # if HAL_USE_SERIAL 26 .speed = (SERIAL_USART_SPEED), 27 # else 28 .baud = (SERIAL_USART_SPEED), 29 # endif 30 .cr1 = (SERIAL_USART_CR1), 31 .cr2 = (SERIAL_USART_CR2), 32 # if !defined(SERIAL_USART_FULL_DUPLEX) 33 .cr3 = ((SERIAL_USART_CR3) | USART_CR3_HDSEL) /* activate half-duplex mode */ 34 # else 35 .cr3 = (SERIAL_USART_CR3) 36 # endif 37 }; 38 #elif defined(MCU_RP) /* Raspberry Pi MCUs */ 39 /* USART in 8E2 config with RX and TX FIFOs enabled. */ 40 // clang-format off 41 static QMKSerialConfig serial_config = { 42 .baud = (SERIAL_USART_SPEED), 43 .UARTLCR_H = UART_UARTLCR_H_WLEN_8BITS | UART_UARTLCR_H_PEN | UART_UARTLCR_H_STP2 | UART_UARTLCR_H_FEN, 44 .UARTCR = 0U, 45 .UARTIFLS = UART_UARTIFLS_RXIFLSEL_1_8F | UART_UARTIFLS_TXIFLSEL_1_8E, 46 .UARTDMACR = 0U 47 }; 48 // clang-format on 49 #else 50 # error MCU Familiy not supported by default, supply your own serial_config by defining SERIAL_USART_CONFIG in your keyboard files. 51 #endif 52 53 static QMKSerialDriver* serial_driver = (QMKSerialDriver*)&SERIAL_USART_DRIVER; 54 55 #if HAL_USE_SERIAL 56 57 /** 58 * @brief SERIAL Driver startup routine. 59 */ 60 static inline void usart_driver_start(void) { 61 sdStart(serial_driver, &serial_config); 62 } 63 64 inline void serial_transport_driver_clear(void) { 65 osalSysLock(); 66 bool volatile queue_not_empty = !iqIsEmptyI(&serial_driver->iqueue); 67 osalSysUnlock(); 68 69 while (queue_not_empty) { 70 osalSysLock(); 71 /* Hard reset the input queue. */ 72 iqResetI(&serial_driver->iqueue); 73 osalSysUnlock(); 74 /* Allow pending interrupts to preempt. 75 * Do not merge the lock/unlock blocks into one 76 * or the code will not work properly. 77 * The empty read adds a tiny amount of delay. */ 78 (void)queue_not_empty; 79 osalSysLock(); 80 queue_not_empty = !iqIsEmptyI(&serial_driver->iqueue); 81 osalSysUnlock(); 82 } 83 } 84 85 #elif HAL_USE_SIO 86 87 /** 88 * @brief SIO Driver startup routine. 89 */ 90 static inline void usart_driver_start(void) { 91 sioStart(serial_driver, &serial_config); 92 } 93 94 inline void serial_transport_driver_clear(void) { 95 if (sioHasRXErrorsX(serial_driver)) { 96 sioGetAndClearErrors(serial_driver); 97 } 98 osalSysLock(); 99 while (!sioIsRXEmptyX(serial_driver)) { 100 (void)sioGetX(serial_driver); 101 } 102 osalSysUnlock(); 103 } 104 105 #else 106 107 # error Either the SERIAL or SIO driver has to be activated to use the usart driver for split keyboards. 108 109 #endif 110 111 inline bool serial_transport_send(const uint8_t* source, const size_t size) { 112 bool success = (size_t)chnWriteTimeout(serial_driver, source, size, TIME_MS2I(SERIAL_USART_TIMEOUT)) == size; 113 114 #if !defined(SERIAL_USART_FULL_DUPLEX) 115 /* Half duplex fills the input queue with the data we wrote - just throw it away. */ 116 if (likely(success)) { 117 size_t bytes_left = size; 118 # if HAL_USE_SERIAL 119 /* The SERIAL driver uses large soft FIFOs that are filled from an IRQ 120 * context, so there is a delay between receiving the data and it 121 * becoming actually available, therefore we have to apply a timeout 122 * mechanism. Under the right circumstances (e.g. bad cables paired with 123 * high baud rates) less bytes can be present in the input queue as 124 * well. */ 125 uint8_t dump[64]; 126 127 while (unlikely(bytes_left >= 64)) { 128 if (unlikely(!serial_transport_receive(dump, 64))) { 129 return false; 130 } 131 bytes_left -= 64; 132 } 133 134 return serial_transport_receive(dump, bytes_left); 135 # else 136 /* The SIO driver directly accesses the hardware FIFOs of the USART 137 * peripheral. As these are limited in depth, the RX FIFO might have 138 * been overflowed by a large transaction that we just send. Therefore 139 * we attempt to read back all the data we send or until the FIFO runs 140 * empty in case it overflowed and data was truncated. */ 141 if (unlikely(sioSynchronizeTXEnd(serial_driver, TIME_MS2I(SERIAL_USART_TIMEOUT)) < MSG_OK)) { 142 return false; 143 } 144 145 osalSysLock(); 146 while (bytes_left > 0 && !sioIsRXEmptyX(serial_driver)) { 147 (void)sioGetX(serial_driver); 148 bytes_left--; 149 } 150 osalSysUnlock(); 151 # endif 152 } 153 #endif 154 155 return success; 156 } 157 158 inline bool serial_transport_receive(uint8_t* destination, const size_t size) { 159 bool success = (size_t)chnReadTimeout(serial_driver, destination, size, TIME_MS2I(SERIAL_USART_TIMEOUT)) == size; 160 return success; 161 } 162 163 inline bool serial_transport_receive_blocking(uint8_t* destination, const size_t size) { 164 bool success = (size_t)chnRead(serial_driver, destination, size) == size; 165 return success; 166 } 167 168 #if !defined(SERIAL_USART_FULL_DUPLEX) 169 170 /** 171 * @brief Initiate pins for USART peripheral. Half-duplex configuration. 172 */ 173 __attribute__((weak)) void usart_init(void) { 174 # if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */ 175 # if defined(USE_GPIOV1) 176 palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_OPENDRAIN); 177 # else 178 palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_OUTPUT_TYPE_OPENDRAIN); 179 # endif 180 181 # if defined(USART_REMAP) 182 USART_REMAP; 183 # endif 184 # elif defined(MCU_RP) /* Raspberry Pi MCUs */ 185 # error Half-duplex with the SIO driver is not supported due to hardware limitations on the RP2040, switch to the PIO driver which has half-duplex support. 186 # else 187 # pragma message "usart_init: MCU Familiy not supported by default, please supply your own init code by implementing usart_init() in your keyboard files." 188 # endif 189 } 190 191 #else 192 193 /** 194 * @brief Initiate pins for USART peripheral. Full-duplex configuration. 195 */ 196 __attribute__((weak)) void usart_init(void) { 197 # if defined(MCU_STM32) || defined(MCU_AT32) /* STM32 and AT32 MCUs */ 198 # if defined(USE_GPIOV1) 199 palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_PUSHPULL); 200 palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_INPUT); 201 # else 202 palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_TX_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST); 203 palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_ALTERNATE(SERIAL_USART_RX_PAL_MODE) | PAL_OUTPUT_TYPE_PUSHPULL | PAL_OUTPUT_SPEED_HIGHEST); 204 # endif 205 206 # if defined(USART_REMAP) 207 USART_REMAP; 208 # endif 209 # elif defined(MCU_RP) /* Raspberry Pi MCUs */ 210 palSetLineMode(SERIAL_USART_TX_PIN, PAL_MODE_ALTERNATE_UART); 211 palSetLineMode(SERIAL_USART_RX_PIN, PAL_MODE_ALTERNATE_UART); 212 # else 213 # pragma message "usart_init: MCU Familiy not supported by default, please supply your own init code by implementing usart_init() in your keyboard files." 214 # endif 215 } 216 217 #endif 218 219 /** 220 * @brief Overridable master specific initializations. 221 */ 222 __attribute__((weak, nonnull)) void usart_master_init(QMKSerialDriver** driver) { 223 (void)driver; 224 usart_init(); 225 } 226 227 /** 228 * @brief Overridable slave specific initializations. 229 */ 230 __attribute__((weak, nonnull)) void usart_slave_init(QMKSerialDriver** driver) { 231 (void)driver; 232 usart_init(); 233 } 234 235 void serial_transport_driver_slave_init(void) { 236 usart_slave_init(&serial_driver); 237 usart_driver_start(); 238 } 239 240 void serial_transport_driver_master_init(void) { 241 usart_master_init(&serial_driver); 242 243 #if defined(MCU_STM32) && defined(SERIAL_USART_PIN_SWAP) 244 serial_config.cr2 |= USART_CR2_SWAP; // master has swapped TX/RX pins 245 #endif 246 247 usart_driver_start(); 248 }