/**
 * @file CBSP_AsyncComm.c
 * @author owalter
 * @brief Continuus receive UART-driver based on circular DMA
 * @version 0.1
 * @date 28.11.2022
 *
 * driver based on example imlementation of https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx
 *
 * @copyright Copyright © 2022 habemus! electronic + transfer GmbH.  All rights reserved.
 */

#include "AsyncComm.h"

#include "stm32h7xx_ll_dma.h"
#include "stm32h7xx_ll_usart.h"

#ifndef USE_HAL_DRIVER
#include "stm32l4xx_ll_gpio.h"
#endif

void usart_rx_check(const uart_desc_t* uart);
void usart_process_data(const uart_desc_t* uart, const void* data, size_t len);
void usart_send_string(const uart_desc_t* uart, const char* str);

/**
 * \brief           USART DMA check thread
 * \param[in]       arg: Thread argument
 */
_Noreturn void usart_rx_dma_thread(void* arg) {
    uart_desc_t* uart = arg;
    void*        d;

    /* Notify user to start sending data */
    // usart_send_string(uart, "USART DMA example: DMA HT & TC + USART IDLE LINE IRQ + RTOS processing\r\n");
    // usart_send_string(uart, "Start sending data to STM32\r\n");

    while (1) {
        /* Block thread and wait for event to process USART data */
        osMessageQueueGet(uart->data->queue, &d, NULL, osWaitForever);

        /* Simply call processing function */
        usart_rx_check(uart);

        (void)d;
    }
}

/**
 * \brief           Check for new data received with DMA
 *
 * User must select context to call this function from:
 * - Only interrupts (DMA HT, DMA TC, UART IDLE) with same preemption priority level
 * - Only thread context (outside interrupts)
 *
 * If called from both context-es, exclusive access protection must be implemented
 * This mode is not advised as it usually means architecture design problems
 *
 * When IDLE interrupt is not present, application must rely only on thread context,
 * by manually calling function as quickly as possible, to make sure
 * data are read from raw buffer and processed.
 *
 * Not doing reads fast enough may cause DMA to overflow unread received bytes,
 * hence application will lost useful data.
 *
 * Solutions to this are:
 * - Improve architecture design to achieve faster reads
 * - Increase raw buffer size and allow DMA to write more data before this function is called
 */
void usart_rx_check(const uart_desc_t* uart) {
    size_t pos;

    /* Calculate current position in buffer and check for new data available */
    pos = ARRAY_LEN(uart->data->dma_rx_buffer) - LL_DMA_GetDataLength(uart->dma_rx, uart->dma_rx_ch);
    if (pos != uart->data->old_pos) {    /* Check change in received data */
        if (pos > uart->data->old_pos) { /* Current position is over previous one */
            /*
             * Processing is done in "linear" mode.
             *
             * Application processing is fast with single data block,
             * length is simply calculated by subtracting pointers
             *
             * [   0   ]
             * [   1   ] <- old_pos |------------------------------------|
             * [   2   ]            |                                    |
             * [   3   ]            | Single block (len = pos - old_pos) |
             * [   4   ]            |                                    |
             * [   5   ]            |------------------------------------|
             * [   6   ] <- pos
             * [   7   ]
             * [ N - 1 ]
             */
            usart_process_data(uart, &uart->data->dma_rx_buffer[uart->data->old_pos], pos - uart->data->old_pos);
        } else {
            /*
             * Processing is done in "overflow" mode..
             *
             * Application must process data twice,
             * since there are 2 linear memory blocks to handle
             *
             * [   0   ]            |---------------------------------|
             * [   1   ]            | Second block (len = pos)        |
             * [   2   ]            |---------------------------------|
             * [   3   ] <- pos
             * [   4   ] <- old_pos |---------------------------------|
             * [   5   ]            |                                 |
             * [   6   ]            | First block (len = N - old_pos) |
             * [   7   ]            |                                 |
             * [ N - 1 ]            |---------------------------------|
             */
            usart_process_data(uart, &uart->data->dma_rx_buffer[uart->data->old_pos],
                               ARRAY_LEN(uart->data->dma_rx_buffer) - uart->data->old_pos);
            if (pos > 0) {
                usart_process_data(uart, &uart->data->dma_rx_buffer[0], pos);
            }
        }
        uart->data->old_pos = pos; /* Save current position as old for next transfers */
    }
}

/**
 * \brief           Check if DMA is active and if not try to send data
 * \return          `1` if transfer just started, `0` if on-going or no data to transmit
 */
uint8_t usart_start_tx_dma_transfer(const uart_desc_t* uart) {
    uint32_t primask;
    uint8_t  started = 0;

    /*
     * First check if transfer is currently in-active,
     * by examining the value of usart_tx_dma_current_len variable.
     *
     * This variable is set before DMA transfer is started and cleared in DMA TX complete interrupt.
     *
     * It is not necessary to disable the interrupts before checking the variable:
     *
     * When usart_tx_dma_current_len == 0
     *    - This function is called by either application or TX DMA interrupt
     *    - When called from interrupt, it was just reset before the call,
     *         indicating transfer just completed and ready for more
     *    - When called from an application, transfer was previously already in-active
     *         and immediate call from interrupt cannot happen at this moment
     *
     * When usart_tx_dma_current_len != 0
     *    - This function is called only by an application.
     *    - It will never be called from interrupt with usart_tx_dma_current_len != 0 condition
     *
     * Disabling interrupts before checking for next transfer is advised
     * only if multiple operating system threads can access to this function w/o
     * exclusive access protection (mutex) configured,
     * or if application calls this function from multiple interrupts.
     *
     * This example assumes worst use case scenario,
     * hence interrupts are disabled prior every check
     */
    primask = __get_PRIMASK();
    __disable_irq();
    if (uart->data->tx_dma_current_len == 0 &&
        (uart->data->tx_dma_current_len = lwrb_get_linear_block_read_length(&uart->data->tx_rb)) > 0) {
        /* Disable channel if enabled */
        LL_DMA_DisableStream(uart->dma_tx, uart->dma_tx_ch);

        /* Clear all flags */
        uart->dma_tx_clear_tc_fn(uart->dma_tx);
        uart->dma_tx_clear_ht_fn(uart->dma_tx);
        uart->dma_tx_clear_gi_fn(uart->dma_tx);
        uart->dma_tx_clear_te_fn(uart->dma_tx);
        uart->dma_tx_clear_fe_fn(uart->dma_tx);

        /* Prepare DMA data and length */
        LL_DMA_SetDataLength(uart->dma_tx, uart->dma_tx_ch, uart->data->tx_dma_current_len);
        LL_DMA_SetMemoryAddress(uart->dma_tx, uart->dma_tx_ch,
                                (uint32_t)lwrb_get_linear_block_read_address(&uart->data->tx_rb));

        /* Start transfer */
        LL_DMA_EnableStream(uart->dma_tx, uart->dma_tx_ch);
        uart->data->tx_running = 1;
        started = 1;
    }
    __set_PRIMASK(primask);
    return started;
}

/**
 * \brief           Process received data over UART
 * \note            Either process them directly or copy to other bigger buffer
 * \param[in]       data: Data to process
 * \param[in]       len: Length in units of bytes
 */
void usart_process_data(const uart_desc_t* uart, const void* data, size_t len) {
    /*
     * This function is called on DMA TC or HT events, and on UART IDLE (if enabled) event.
     */
    uart->uart_external_receive_callback(data, len);
}

/**
 * \brief           Send data over UART
 * \note            copy data into ringbuffer and trigger UART transmission
 * \param[in]       data: Data to process
 * \param[in]       len: Length in units of bytes
 */
void usart_send_data(const uart_desc_t* uart, const void* data, size_t len) {
    lwrb_write(&uart->data->tx_rb, data, len); /* Write data to TX buffer  */
    usart_start_tx_dma_transfer(uart);         /* Then try to start transfer */
}

/**
 * \brief           Send string to USART
 * \param[in]       str: String to send
 */
void usart_send_string(const uart_desc_t* uart, const char* str) {
    lwrb_write(&uart->data->tx_rb, str, strnlen(str, 255)); /* Write data to TX buffer for loopback */
    usart_start_tx_dma_transfer(uart);                /* Then try to start transfer */
}

/**
 * \brief           USART DMA Initialization Function
 *              other init is handled by MX_HAL
 */
osThreadId_t usart_init(const uart_desc_t* uart, const osThreadAttr_t* attr) {

#ifndef USE_HAL_DRIVER
    LL_USART_InitTypeDef USART_InitStruct = { 0 };
    LL_GPIO_InitTypeDef  GPIO_InitStruct  = { 0 };

    /* USART GPIO Configuration */
    /* RX pin */
    GPIO_InitStruct.Pin        = uart->uart_rx_pin;
    GPIO_InitStruct.Mode       = LL_GPIO_MODE_ALTERNATE;
    GPIO_InitStruct.Speed      = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
    GPIO_InitStruct.Pull       = LL_GPIO_PULL_NO;
    GPIO_InitStruct.Alternate  = uart->uart_rx_pin_af;
    LL_GPIO_Init(uart->uart_rx_port, &GPIO_InitStruct);

    /* Optional TX pin */
    if (uart->uart_tx_port != NULL) {
        GPIO_InitStruct.Pin        = uart->uart_tx_pin;
        GPIO_InitStruct.Mode       = LL_GPIO_MODE_ALTERNATE;
        GPIO_InitStruct.Speed      = LL_GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
        GPIO_InitStruct.Pull       = LL_GPIO_PULL_NO;
        GPIO_InitStruct.Alternate  = uart->uart_tx_pin_af;
        LL_GPIO_Init(uart->uart_tx_port, &GPIO_InitStruct);
    }
#endif

    /* USART RX DMA init */
#ifndef USE_HAL_DRIVER
    LL_DMA_SetPeriphRequest(uart->dma_rx, uart->dma_rx_ch, uart->dma_rx, uart->dma_rx_req);
    LL_DMA_SetDataTransferDirection(uart->dma_rx, uart->dma_rx_ch, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
    LL_DMA_SetChannelPriorityLevel(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PRIORITY_LOW);
    LL_DMA_SetMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MODE_NORMAL);
    LL_DMA_SetPeriphIncMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PERIPH_NOINCREMENT);
    LL_DMA_SetMemoryIncMode(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MEMORY_INCREMENT);
    LL_DMA_SetPeriphSize(uart->dma_rx, uart->dma_rx_ch, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_SetMemorySize(uart->dma_rx, uart->dma_rx_ch, LL_DMA_MDATAALIGN_BYTE);
#endif
    LL_DMA_SetPeriphAddress(uart->dma_rx, uart->dma_rx_ch,
                            LL_USART_DMA_GetRegAddr(uart->uart, LL_USART_DMA_REG_DATA_RECEIVE));
    LL_DMA_SetMemoryAddress(uart->dma_rx, uart->dma_rx_ch, (uint32_t)uart->data->dma_rx_buffer);
    LL_DMA_SetDataLength(uart->dma_rx, uart->dma_rx_ch, ARRAY_LEN(uart->data->dma_rx_buffer));

    /* USART TX DMA init */
#ifndef USE_HAL_DRIVER
    LL_DMA_SetPeriphRequest(uart->dma_tx, uart->dma_tx_ch, uart->dma_tx, uart->dma_tx_req);
    LL_DMA_SetDataTransferDirection(uart->dma_tx, uart->dma_tx_ch, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
    LL_DMA_SetChannelPriorityLevel(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PRIORITY_LOW);
    LL_DMA_SetMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MODE_NORMAL);
    LL_DMA_SetPeriphIncMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PERIPH_NOINCREMENT);
    LL_DMA_SetMemoryIncMode(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MEMORY_INCREMENT);
    LL_DMA_SetPeriphSize(uart->dma_tx, uart->dma_tx_ch, LL_DMA_PDATAALIGN_BYTE);
    LL_DMA_SetMemorySize(uart->dma_tx, uart->dma_tx_ch, LL_DMA_MDATAALIGN_BYTE);
#endif
    LL_DMA_SetPeriphAddress(uart->dma_tx, uart->dma_tx_ch,
                            LL_USART_DMA_GetRegAddr(uart->uart, LL_USART_DMA_REG_DATA_TRANSMIT));

    /* Enable HT & TC interrupts */
    LL_DMA_EnableIT_HT(uart->dma_rx, uart->dma_rx_ch);
    LL_DMA_EnableIT_TC(uart->dma_rx, uart->dma_rx_ch);

    /* Enable TC interrupts for TX */
    LL_DMA_EnableIT_TC(uart->dma_tx, uart->dma_tx_ch);


    /* DMA interrupt init */
#ifndef USE_HAL_DRIVER
    NVIC_SetPriority(uart->dma_irq_rx, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 0));
    NVIC_EnableIRQ(uart->dma_irq_rx);
    NVIC_SetPriority(uart->dma_irq_tx, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 0));
    NVIC_EnableIRQ(uart->dma_irq_tx);
#endif

    /* USART configuration */
#ifndef USE_HAL_DRIVER
    USART_InitStruct.BaudRate            = uart->uart_baudrate;
    USART_InitStruct.DataWidth           = LL_USART_DATAWIDTH_8B;
    USART_InitStruct.StopBits            = LL_USART_STOPBITS_1;
    USART_InitStruct.Parity              = LL_USART_PARITY_NONE;
    USART_InitStruct.TransferDirection   = LL_USART_DIRECTION_TX_RX;
    USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;
    USART_InitStruct.OverSampling        = LL_USART_OVERSAMPLING_16;
    LL_USART_Init(uart->uart, &USART_InitStruct);
    LL_USART_ConfigAsyncMode(uart->uart);
#endif
    LL_USART_Disable(uart->uart);
    LL_USART_EnableDMAReq_RX(uart->uart);
    LL_USART_EnableDMAReq_TX(uart->uart);
    LL_USART_EnableIT_IDLE(uart->uart);

    /* USART interrupt */
#ifndef USE_HAL_DRIVER
    NVIC_SetPriority(uart->uart_irq, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), uart->prio, 1));
    NVIC_EnableIRQ(uart->uart_irq);
#endif

    /* Create message queue before enabling UART or DMA */
    /* This is to make sure message queue is ready before UART/DMA interrupts are enabled */
    uart->data->queue = osMessageQueueNew(10, sizeof(void*), NULL);

    /*init tx ringbuffer*/
    lwrb_init(&uart->data->tx_rb, uart->data->tx_rb_data, sizeof(uart->data->tx_rb_data));

    /* Enable USART and DMA */
    LL_DMA_EnableStream(uart->dma_rx, uart->dma_rx_ch);
    LL_USART_Enable(uart->uart);

    /* Create new thread for USART RX DMA processing */
    return osThreadNew(usart_rx_dma_thread, (void*)uart, attr);
}

/**
 * \brief           General purpose DMA interrupt handler
 * \note            Function must be called from DMA interrupt
 *
 * It handles half-transfer and transfer-cmakecomplete interrupts and does the job accordingly
 *
 *  must be called from the IT function with the correct context pointer
 *
* \param[in]       uart: Uart description to handle
 */
void usart_dma_irq_handler(const uart_desc_t* uart) {
    void* d = (void*)1;

    /* Check half-transfer complete interrupt */
    if (LL_DMA_IsEnabledIT_HT(uart->dma_rx, uart->dma_rx_ch) && uart->dma_rx_is_ht_fn(uart->dma_rx)) {
        uart->dma_rx_clear_ht_fn(uart->dma_rx);         /* Clear half-transfer complete flag */
        osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */
    }

    /* Check transfer-complete interrupt */
    if (LL_DMA_IsEnabledIT_TC(uart->dma_rx, uart->dma_rx_ch) && uart->dma_rx_is_tc_fn(uart->dma_rx)) {
        uart->dma_rx_clear_tc_fn(uart->dma_rx);         /* Clear transfer complete flag */
        osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */
    }
}

/// must be called from the IT function with the correct context pointer
void usart_dma_irq_handler_tx(const uart_desc_t* uart) {
    /* Check transfer-complete interrupt */
    if (LL_DMA_IsEnabledIT_TC(uart->dma_tx, uart->dma_tx_ch) && uart->dma_tx_is_tc_fn(uart->dma_tx)) {
        uart->dma_tx_clear_tc_fn(uart->dma_tx);
        /* Clear transfer complete flag */
        lwrb_skip(&uart->data->tx_rb,
                  uart->data->tx_dma_current_len); /* Skip buffer, it has been successfully sent out */
        uart->data->tx_dma_current_len = 0;        /* Reset data length */
        uart->data->tx_running = 0;
        usart_start_tx_dma_transfer(uart);         /* Start new transfer */
    }
}

/**
 * \brief           General purpose UART interrupt handler
 * \note            Function must be called from UART interrupt
 *
 * It handles IDLE line detection interrupt and does the job accordingly
 *
 * must be called from the IT function with the correct context pointer
 *
 * \param[in]       uart: Uart description to handle
 */
void usart_irq_handler(const uart_desc_t* uart) {
    void* d = (void*)1;

    /* Check for IDLE line interrupt */
    if (LL_USART_IsEnabledIT_IDLE(uart->uart) && LL_USART_IsActiveFlag_IDLE(uart->uart)) {
        LL_USART_ClearFlag_IDLE(uart->uart);            /* Clear IDLE line flag */
        osMessageQueuePut(uart->data->queue, &d, 0, 0); /* Write data to queue. Do not use wait function! */
    }
}
