qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

serial_vendor.c (15960B)


      1 // Copyright 2022 Stefan Kerkmann
      2 // SPDX-License-Identifier: GPL-2.0-or-later
      3 
      4 #include "serial_usart.h"
      5 #include "serial_protocol.h"
      6 #include "hardware/pio.h"
      7 #include "hardware/clocks.h"
      8 #include "wait.h"
      9 #include "debug.h"
     10 
     11 #if !defined(MCU_RP)
     12 #    error PIO Driver is only available for Raspberry Pi 2040 MCUs!
     13 #endif
     14 
     15 static inline bool receive_impl(uint8_t* destination, const size_t size, sysinterval_t timeout);
     16 static inline bool send_impl(const uint8_t* source, const size_t size);
     17 static inline void pio_serve_interrupt(void);
     18 
     19 #define MSG_PIO_ERROR ((msg_t)(-3))
     20 
     21 #if defined(SERIAL_PIO_USE_PIO1)
     22 static const PIO pio = pio1;
     23 
     24 OSAL_IRQ_HANDLER(RP_PIO1_IRQ_0_HANDLER) {
     25     OSAL_IRQ_PROLOGUE();
     26     pio_serve_interrupt();
     27     OSAL_IRQ_EPILOGUE();
     28 }
     29 #else
     30 static const PIO pio = pio0;
     31 
     32 OSAL_IRQ_HANDLER(RP_PIO0_IRQ_0_HANDLER) {
     33     OSAL_IRQ_PROLOGUE();
     34     pio_serve_interrupt();
     35     OSAL_IRQ_EPILOGUE();
     36 }
     37 #endif
     38 
     39 #define UART_TX_WRAP_TARGET 0
     40 #define UART_TX_WRAP 3
     41 
     42 // clang-format off
     43 #if defined(SERIAL_USART_FULL_DUPLEX)
     44 static const uint16_t uart_tx_program_instructions[] = {
     45             //     .wrap_target
     46     0x9fa0, //  0: pull   block           side 1 [7]
     47     0xf727, //  1: set    x, 7            side 0 [7]
     48     0x6001, //  2: out    pins, 1
     49     0x0642, //  3: jmp    x--, 2                 [6]
     50             //     .wrap
     51 };
     52 #else
     53 static const uint16_t uart_tx_program_instructions[] = {
     54             //     .wrap_target
     55     0x9fa0, //  0: pull   block           side 1 [7]
     56     0xf727, //  1: set    x, 7            side 0 [7]
     57     0x6081, //  2: out    pindirs, 1
     58     0x0642, //  3: jmp    x--, 2                 [6]
     59             //     .wrap
     60 };
     61 #endif
     62 // clang-format on
     63 
     64 static const pio_program_t uart_tx_program = {
     65     .instructions = uart_tx_program_instructions,
     66     .length       = 4,
     67     .origin       = -1,
     68 };
     69 
     70 #define UART_RX_WRAP_TARGET 0
     71 #define UART_RX_WRAP 8
     72 
     73 // clang-format off
     74 static const uint16_t uart_rx_program_instructions[] = {
     75             //     .wrap_target
     76     0x2020, //  0: wait   0 pin, 0
     77     0xea27, //  1: set    x, 7                   [10]
     78     0x4001, //  2: in     pins, 1
     79     0x0642, //  3: jmp    x--, 2                 [6]
     80     0x00c8, //  4: jmp    pin, 8
     81     0xc020, //  5: irq    wait 0
     82     0x20a0, //  6: wait   1 pin, 0
     83     0x0000, //  7: jmp    0
     84     0x8020, //  8: push   block
     85             //     .wrap
     86 };
     87 // clang-format on
     88 
     89 static const pio_program_t uart_rx_program = {
     90     .instructions = uart_rx_program_instructions,
     91     .length       = 9,
     92     .origin       = -1,
     93 };
     94 
     95 thread_reference_t rx_thread        = NULL;
     96 static int         rx_state_machine = -1;
     97 
     98 thread_reference_t tx_thread        = NULL;
     99 static int         tx_state_machine = -1;
    100 
    101 void pio_serve_interrupt(void) {
    102     uint32_t irqs = pio->ints0;
    103 
    104     // The RX FIFO is not empty any more, therefore wake any sleeping rx thread
    105     if (irqs & (PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS << rx_state_machine)) {
    106         // Disable rx not empty interrupt
    107         pio_set_irq0_source_enabled(pio, pis_sm0_rx_fifo_not_empty + rx_state_machine, false);
    108 
    109         osalSysLockFromISR();
    110         osalThreadResumeI(&rx_thread, MSG_OK);
    111         osalSysUnlockFromISR();
    112     }
    113 
    114     // The TX FIFO is not full any more, therefore wake any sleeping tx thread
    115     if (irqs & (PIO_IRQ0_INTF_SM0_TXNFULL_BITS << tx_state_machine)) {
    116         // Disable tx not full interrupt
    117         pio_set_irq0_source_enabled(pio, pis_sm0_tx_fifo_not_full + tx_state_machine, false);
    118         osalSysLockFromISR();
    119         osalThreadResumeI(&tx_thread, MSG_OK);
    120         osalSysUnlockFromISR();
    121     }
    122 
    123     // IRQ 0 is set on framing or break errors by the rx state machine
    124     if (pio_interrupt_get(pio, 0UL)) {
    125         pio_interrupt_clear(pio, 0UL);
    126 
    127         osalSysLockFromISR();
    128         osalThreadResumeI(&rx_thread, MSG_PIO_ERROR);
    129         osalSysUnlockFromISR();
    130     }
    131 }
    132 
    133 #if !defined(SERIAL_USART_FULL_DUPLEX)
    134 // The internal pull-ups of the RP2040 are rather weakish with a range of 50k to
    135 // 80k, which in turn do not provide enough current to guarantee fast signal rise
    136 // times with a parasitic capacitance of greater than 100pf. In real world
    137 // applications, like split keyboards which might have vias in the signal path
    138 // or long PCB traces, this prevents a successful communication. The solution
    139 // is to temporarily augment the weak pull ups from the receiving side by
    140 // driving the tx pin high. On the receiving side the lowest possible drive
    141 // strength is chosen because the transmitting side must still be able to drive
    142 // the signal low. With this configuration the rise times are fast enough and
    143 // the generated low level with 360mV will generate a logical zero.
    144 static void __no_inline_not_in_flash_func(enter_rx_state)(void) {
    145     osalSysLock();
    146     // Wait for the transmitting state machines FIFO to run empty. At this point
    147     // the last byte has been pulled from the transmitting state machines FIFO
    148     // into the output shift register. We have to wait a tiny bit more until
    149     // this byte is transmitted, before we can turn on the receiving state
    150     // machine again.
    151     while (!pio_sm_is_tx_fifo_empty(pio, tx_state_machine)) {
    152     }
    153     // Wait for ~11 bits, 1 start bit + 8 data bits + 1 stop bit + 1 bit
    154     // headroom.
    155     wait_us(1000000U * 11U / SERIAL_USART_SPEED);
    156     // Disable tx state machine to not interfere with our tx pin manipulation
    157     pio_sm_set_enabled(pio, tx_state_machine, false);
    158     gpio_set_drive_strength(SERIAL_USART_TX_PIN, GPIO_DRIVE_STRENGTH_2MA);
    159     pio_sm_set_pins_with_mask(pio, tx_state_machine, 1U << SERIAL_USART_TX_PIN, 1U << SERIAL_USART_TX_PIN);
    160     pio_sm_set_consecutive_pindirs(pio, tx_state_machine, SERIAL_USART_TX_PIN, 1U, false);
    161     pio_sm_set_enabled(pio, rx_state_machine, true);
    162     osalSysUnlock();
    163 }
    164 
    165 static void __no_inline_not_in_flash_func(leave_rx_state)(void) {
    166     osalSysLock();
    167     // In Half-duplex operation the tx pin dual-functions as sender and
    168     // receiver. To not receive the data we will send, we disable the receiving
    169     // state machine.
    170     pio_sm_set_enabled(pio, rx_state_machine, false);
    171     pio_sm_set_consecutive_pindirs(pio, tx_state_machine, SERIAL_USART_TX_PIN, 1U, true);
    172     pio_sm_set_pins_with_mask(pio, tx_state_machine, 0U, 1U << SERIAL_USART_TX_PIN);
    173     gpio_set_drive_strength(SERIAL_USART_TX_PIN, GPIO_DRIVE_STRENGTH_12MA);
    174     pio_sm_restart(pio, tx_state_machine);
    175     pio_sm_set_enabled(pio, tx_state_machine, true);
    176     osalSysUnlock();
    177 }
    178 #else
    179 // All this trickery is gladly not necessary for full-duplex.
    180 static inline void enter_rx_state(void) {}
    181 static inline void leave_rx_state(void) {}
    182 #endif
    183 
    184 /**
    185  * @brief Clear the FIFO of the RX state machine.
    186  */
    187 inline void serial_transport_driver_clear(void) {
    188     osalSysLock();
    189     while (!pio_sm_is_rx_fifo_empty(pio, rx_state_machine)) {
    190         pio_sm_clear_fifos(pio, rx_state_machine);
    191     }
    192     osalSysUnlock();
    193 }
    194 
    195 static inline msg_t sync_tx(sysinterval_t timeout) {
    196     msg_t msg = MSG_OK;
    197     osalSysLock();
    198     while (pio_sm_is_tx_fifo_full(pio, tx_state_machine)) {
    199         pio_set_irq0_source_enabled(pio, pis_sm0_tx_fifo_not_full + tx_state_machine, true);
    200         msg = osalThreadSuspendTimeoutS(&tx_thread, timeout);
    201         if (msg < MSG_OK) {
    202             pio_set_irq0_source_enabled(pio, pis_sm0_tx_fifo_not_full + tx_state_machine, false);
    203             break;
    204         }
    205     }
    206     osalSysUnlock();
    207     return msg;
    208 }
    209 
    210 static inline bool send_impl(const uint8_t* source, const size_t size) {
    211     size_t send = 0;
    212     msg_t  msg;
    213     while (send < size) {
    214         msg = sync_tx(TIME_MS2I(SERIAL_USART_TIMEOUT));
    215         if (msg < MSG_OK) {
    216             return false;
    217         }
    218 
    219         osalSysLock();
    220         while (send < size) {
    221             if (pio_sm_is_tx_fifo_full(pio, tx_state_machine)) {
    222                 break;
    223             }
    224             if (send >= size) {
    225                 break;
    226             }
    227             pio_sm_put(pio, tx_state_machine, (uint32_t)(*source));
    228             source++;
    229             send++;
    230         }
    231         osalSysUnlock();
    232     }
    233 
    234     return send == size;
    235 }
    236 
    237 /**
    238  * @brief Blocking send of buffer with timeout.
    239  *
    240  * @return true Send success.
    241  * @return false Send failed.
    242  */
    243 inline bool serial_transport_send(const uint8_t* source, const size_t size) {
    244     leave_rx_state();
    245     bool result = send_impl(source, size);
    246     enter_rx_state();
    247 
    248     return result;
    249 }
    250 
    251 static inline msg_t sync_rx(sysinterval_t timeout) {
    252     msg_t msg = MSG_OK;
    253     osalSysLock();
    254     while (pio_sm_is_rx_fifo_empty(pio, rx_state_machine)) {
    255         pio_set_irq0_source_enabled(pio, pis_sm0_rx_fifo_not_empty + rx_state_machine, true);
    256         msg = osalThreadSuspendTimeoutS(&rx_thread, timeout);
    257         if (msg < MSG_OK) {
    258             pio_set_irq0_source_enabled(pio, pis_sm0_rx_fifo_not_empty + rx_state_machine, false);
    259             break;
    260         }
    261     }
    262     osalSysUnlock();
    263     return msg;
    264 }
    265 
    266 static inline bool receive_impl(uint8_t* destination, const size_t size, sysinterval_t timeout) {
    267     size_t read = 0U;
    268 
    269     while (read < size) {
    270         msg_t msg = sync_rx(timeout);
    271         if (msg < MSG_OK) {
    272             return false;
    273         }
    274         osalSysLock();
    275         while (true) {
    276             if (pio_sm_is_rx_fifo_empty(pio, rx_state_machine)) {
    277                 break;
    278             }
    279             if (read >= size) {
    280                 break;
    281             }
    282             *destination++ = *((uint8_t*)&pio->rxf[rx_state_machine] + 3U);
    283             read++;
    284         }
    285         osalSysUnlock();
    286     }
    287 
    288     return read == size;
    289 }
    290 
    291 /**
    292  * @brief  Blocking receive of size * bytes with timeout.
    293  *
    294  * @return true Receive success.
    295  * @return false Receive failed, e.g. by timeout.
    296  */
    297 inline bool serial_transport_receive(uint8_t* destination, const size_t size) {
    298     return receive_impl(destination, size, TIME_MS2I(SERIAL_USART_TIMEOUT));
    299 }
    300 
    301 /**
    302  * @brief  Blocking receive of size * bytes.
    303  *
    304  * @return true Receive success.
    305  * @return false Receive failed.
    306  */
    307 inline bool serial_transport_receive_blocking(uint8_t* destination, const size_t size) {
    308     return receive_impl(destination, size, TIME_INFINITE);
    309 }
    310 
    311 static inline void pio_tx_init(pin_t tx_pin) {
    312     uint pio_idx = pio_get_index(pio);
    313     uint offset  = pio_add_program(pio, &uart_tx_program);
    314 
    315 #if defined(SERIAL_USART_FULL_DUPLEX)
    316     // clang-format off
    317     iomode_t tx_pin_mode = PAL_RP_GPIO_OE |
    318                            PAL_RP_PAD_SLEWFAST |
    319                            PAL_RP_PAD_DRIVE4 |
    320                            (pio_idx == 0 ? PAL_MODE_ALTERNATE_PIO0 : PAL_MODE_ALTERNATE_PIO1);
    321     // clang-format on
    322     pio_sm_set_pins_with_mask(pio, tx_state_machine, 1U << tx_pin, 1U << tx_pin);
    323     pio_sm_set_consecutive_pindirs(pio, tx_state_machine, tx_pin, 1U, true);
    324 #else
    325     // clang-format off
    326     iomode_t tx_pin_mode = PAL_RP_PAD_IE |
    327                            PAL_RP_GPIO_OE |
    328                            PAL_RP_PAD_SCHMITT |
    329                            PAL_RP_PAD_PUE |
    330                            PAL_RP_PAD_SLEWFAST |
    331                            PAL_RP_PAD_DRIVE12 |
    332                            PAL_RP_IOCTRL_OEOVER_DRVINVPERI |
    333                            (pio_idx == 0 ? PAL_MODE_ALTERNATE_PIO0 : PAL_MODE_ALTERNATE_PIO1);
    334     // clang-format on
    335     pio_sm_set_pins_with_mask(pio, tx_state_machine, 0U << tx_pin, 1U << tx_pin);
    336     pio_sm_set_consecutive_pindirs(pio, tx_state_machine, tx_pin, 1U, true);
    337 #endif
    338 
    339     palSetLineMode(tx_pin, tx_pin_mode);
    340 
    341     pio_sm_config config = pio_get_default_sm_config();
    342     sm_config_set_wrap(&config, offset + UART_TX_WRAP_TARGET, offset + UART_TX_WRAP);
    343 #if defined(SERIAL_USART_FULL_DUPLEX)
    344     sm_config_set_sideset(&config, 2, true, false);
    345 #else
    346     sm_config_set_sideset(&config, 2, true, true);
    347 #endif
    348     // OUT shifts to right, no autopull
    349     sm_config_set_out_shift(&config, true, false, 32);
    350     // We are mapping both OUT and side-set to the same pin, because sometimes
    351     // we need to assert user data onto the pin (with OUT) and sometimes
    352     // assert constant values (start/stop bit)
    353     sm_config_set_out_pins(&config, tx_pin, 1);
    354     sm_config_set_sideset_pins(&config, tx_pin);
    355     // We only need TX, so get an 8-deep FIFO!
    356     sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_TX);
    357     // SM transmits 1 bit per 8 execution cycles.
    358     float div = (float)clock_get_hz(clk_sys) / (8 * SERIAL_USART_SPEED);
    359     sm_config_set_clkdiv(&config, div);
    360     pio_sm_init(pio, tx_state_machine, offset, &config);
    361     pio_sm_set_enabled(pio, tx_state_machine, true);
    362 }
    363 
    364 static inline void pio_rx_init(pin_t rx_pin) {
    365     uint offset = pio_add_program(pio, &uart_rx_program);
    366 
    367 #if defined(SERIAL_USART_FULL_DUPLEX)
    368     uint pio_idx = pio_get_index(pio);
    369     pio_sm_set_consecutive_pindirs(pio, rx_state_machine, rx_pin, 1, false);
    370     // clang-format off
    371     iomode_t rx_pin_mode = PAL_RP_PAD_IE |
    372                            PAL_RP_PAD_SCHMITT |
    373                            PAL_RP_PAD_PUE |
    374                            (pio_idx == 0 ? PAL_MODE_ALTERNATE_PIO0 : PAL_MODE_ALTERNATE_PIO1);
    375     // clang-format on
    376     palSetLineMode(rx_pin, rx_pin_mode);
    377 #endif
    378 
    379     pio_sm_config config = pio_get_default_sm_config();
    380     sm_config_set_wrap(&config, offset + UART_RX_WRAP_TARGET, offset + UART_RX_WRAP);
    381     sm_config_set_in_pins(&config, rx_pin); // for WAIT, IN
    382     sm_config_set_jmp_pin(&config, rx_pin); // for JMP
    383     // Shift to right, autopush disabled
    384     sm_config_set_in_shift(&config, true, false, 32);
    385     // Deeper FIFO as we're not doing any TX
    386     sm_config_set_fifo_join(&config, PIO_FIFO_JOIN_RX);
    387     // SM transmits 1 bit per 8 execution cycles.
    388     float div = (float)clock_get_hz(clk_sys) / (8 * SERIAL_USART_SPEED);
    389     sm_config_set_clkdiv(&config, div);
    390     pio_sm_init(pio, rx_state_machine, offset, &config);
    391     pio_sm_set_enabled(pio, rx_state_machine, true);
    392 }
    393 
    394 static inline void pio_init(pin_t tx_pin, pin_t rx_pin) {
    395     uint pio_idx = pio_get_index(pio);
    396 
    397     /* Get PIOx peripheral out of reset state. */
    398     hal_lld_peripheral_unreset(pio_idx == 0 ? RESETS_ALLREG_PIO0 : RESETS_ALLREG_PIO1);
    399 
    400     tx_state_machine = pio_claim_unused_sm(pio, true);
    401     if (tx_state_machine < 0) {
    402         dprintln("ERROR: Failed to acquire state machine for serial transmission!");
    403         return;
    404     }
    405     pio_tx_init(tx_pin);
    406 
    407     rx_state_machine = pio_claim_unused_sm(pio, true);
    408     if (rx_state_machine < 0) {
    409         dprintln("ERROR: Failed to acquire state machine for serial reception!");
    410         return;
    411     }
    412     pio_rx_init(rx_pin);
    413 
    414     // Enable error flag IRQ source for rx state machine
    415     pio_set_irq0_source_enabled(pio, pis_sm0_rx_fifo_not_empty + rx_state_machine, true);
    416     pio_set_irq0_source_enabled(pio, pis_sm0_tx_fifo_not_full + tx_state_machine, true);
    417     pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    418 
    419     // Enable PIO specific interrupt vector, as the pio implementation is timing
    420     // critical we use the highest possible priority.
    421 #if defined(SERIAL_PIO_USE_PIO1)
    422     nvicEnableVector(RP_PIO1_IRQ_0_NUMBER, CORTEX_MAX_KERNEL_PRIORITY);
    423 #else
    424     nvicEnableVector(RP_PIO0_IRQ_0_NUMBER, CORTEX_MAX_KERNEL_PRIORITY);
    425 #endif
    426 
    427     enter_rx_state();
    428 }
    429 
    430 /**
    431  * @brief PIO driver specific initialization function for the master side.
    432  */
    433 void serial_transport_driver_master_init(void) {
    434 #if defined(SERIAL_USART_FULL_DUPLEX)
    435     pin_t tx_pin = SERIAL_USART_TX_PIN;
    436     pin_t rx_pin = SERIAL_USART_RX_PIN;
    437 #else
    438     pin_t tx_pin = SERIAL_USART_TX_PIN;
    439     pin_t rx_pin = SERIAL_USART_TX_PIN;
    440 #endif
    441 
    442 #if defined(SERIAL_USART_PIN_SWAP)
    443     pio_init(rx_pin, tx_pin);
    444 #else
    445     pio_init(tx_pin, rx_pin);
    446 #endif
    447 }
    448 
    449 /**
    450  * @brief PIO driver specific initialization function for the slave side.
    451  */
    452 void serial_transport_driver_slave_init(void) {
    453 #if defined(SERIAL_USART_FULL_DUPLEX)
    454     pin_t tx_pin = SERIAL_USART_TX_PIN;
    455     pin_t rx_pin = SERIAL_USART_RX_PIN;
    456 #else
    457     pin_t tx_pin = SERIAL_USART_TX_PIN;
    458     pin_t rx_pin = SERIAL_USART_TX_PIN;
    459 #endif
    460 
    461     pio_init(tx_pin, rx_pin);
    462 }