/* USER CODE BEGIN Header */
/* you can remove the copyright */
/*
 * Copyright 2020-2025 Yuntu Microelectronics Co., Ltd.
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * @file main.c
 * @brief LIN slave auto-baud DMA demo.
 *
 * This demo keeps the complete implementation inside the USER CODE sections of
 * main.c so that it can be released and ported as a single file.
 *
 * Auto-baud sequence:
 * 1. UART hardware detects Break.
 * 2. After LIN_RECV_BREAK_FIELD_OK, RX is switched to GPIO.
 * 3. GPIO falling edges generate DMA requests; DMA stores 5 pTMR timestamps:
 *    sync start, bit1, bit3, bit5, bit7 falling.
 * 4. DMA callback validates the four 2-bit intervals and prepares UART baud
 *    registers before the Sync stop rising edge arrives.
 * 5. GPIO is then re-armed for a single rising-edge interrupt. That interrupt
 *    must be the Sync stop rising edge; only there does the code switch the
 *    pin mux back to UART so PID reception stays clean.
 *
 * Porting checklist:
 * - keep UART, GPIO, pTMR, and LPTMR resource mapping consistent with YCT;
 * - keep pTMR and UART clocks derived from the same root clock domain;
 * - verify DMA request routing really comes from the RX GPIO falling edge;
 * - guarantee bounded latency for DMA completion and the final stop rising ISR.
 *
 * Unlike the interrupt-based demo, the five falling-edge timestamps are
 * captured by DMA directly from pTMR->TCV, so the falling-edge measurement path
 * is no longer sensitive to per-edge GPIO ISR entry latency. The remaining
 * real-time pressure is concentrated in two points only:
 * 1. the DMA completion callback must finish baud preparation before the next
 *    Sync stop rising edge arrives;
 * 2. the single rising-edge GPIO ISR must restore the UART receive path before
 *    PID start-bit sampling begins.
 */
/* USER CODE END Header */
#include "sdk_project_config.h"
/* Includes ------------------------------------------------------------------*/

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "uart_hw_access.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum
{
    LIN_SYNC_CAPTURE_SYNC_OK = 0U,
    LIN_SYNC_CAPTURE_SYNC_ERROR = 1U,
    LIN_SYNC_CAPTURE_ABORT = 2U
} lin_sync_capture_status_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/*
 * Primary porting configuration area.
 * In most cases only the resource mapping, nominal baud rate, tolerance, and
 * demo PIDs need to be updated; the algorithm itself can remain unchanged.
 */
/* UART/LIN peripheral resources. */
#define LIN_SYNC_UART_INSTANCE                      (0U)
#define LIN_SYNC_UART_BASE                          (UART0)
#define LIN_SYNC_UART_CLOCK_NAME                    (UART0_CLK)
#define LIN_SYNC_UART_IRQn                          (UART0_IRQn)

/* LIN RX GPIO used only during the auto-baud capture window. */
#define LIN_SYNC_RX_GPIO                            (GPIOA)
#define LIN_SYNC_RX_PCTRL                           (PCTRLA)
#define LIN_SYNC_RX_PIN                             (2U)
#define LIN_SYNC_RX_MUX                             (PCTRL_MUX_ALT6)
#define LIN_SYNC_GPIO_IRQn                          (GPIOA_IRQn)

/* DMA channel used to store 5 pTMR timestamps on RX falling-edge requests. */
#define LIN_SYNC_DMA_CHANNEL                        (0U)
#define LIN_SYNC_DMA_IRQn                           (DMA0_IRQn)

/*
 * LPTMR provides the timeout and wakeup reference for the LIN stack.
 * If the LPTMR configuration is changed, update the period-related macros.
 */
#define LIN_SYNC_LPTMR_INSTANCE                     (0U)
#define LIN_SYNC_LPTMR_IRQn                         (lpTMR0_IRQn)
#define LIN_SYNC_LPTMR_PERIOD_US                    (520U)
#define LIN_SYNC_LPTMR_TICKS_PER_US                 (48U)

/*
 * pTMR is the free-running timestamp source for Sync sampling. It should use a
 * clock derived from the same root domain as the UART functional clock.
 * If the pTMR configuration is changed, this macro must be updated accordingly
 * (user modification is not recommended).
 */
#define LIN_SYNC_PTMR_INSTANCE                      (0U)
#define LIN_SYNC_PTMR_CHANNEL                       (0U)
#define LIN_SYNC_PTMR_CLOCK_NAME                    (pTMR0_CLK)
#define LIN_SYNC_PTMR_CLOCK_FALLBACK_HZ             (12000000UL)

/* Nominal baud rate and demo PIDs; adjust to match the target LIN network. */
#define LIN_SYNC_NOMINAL_BAUD                       (19200UL)
#define LIN_SYNC_RX_PID                             (0x3CU)
#define LIN_SYNC_TX_PID                             (0x3DU)

/*
 * Auto-baud policy:
 * - EVERY_HEADER_RECAL: run auto-baud on every LIN header.
 * - STARTUP_RETRY_UNTIL_SUCCESS: stop re-calibrating after the first success.
 */
#define LIN_SYNC_MODE_STARTUP_RETRY_UNTIL_SUCCESS   (0U)
#define LIN_SYNC_MODE_EVERY_HEADER_RECAL            (1U)
#define LIN_SYNC_AUTOBAUD_MODE                      (LIN_SYNC_MODE_EVERY_HEADER_RECAL)

/*
 * Sync = 0x55 contributes five falling edges:
 * sync start, bit1, bit3, bit5, and bit7.
 * The four deltas between them each represent 2 bit times.
 */
#define LIN_SYNC_FALLING_EDGE_COUNT                 (5U)
#define LIN_SYNC_TWOBIT_SAMPLE_COUNT                (LIN_SYNC_FALLING_EDGE_COUNT - 1U)
/* Allowed deviation from the measured baud rate: +/-10.5% relative to nominal baud. */
#define LIN_SYNC_BAUD_TOLERANCE_PERMILLE            (105U)

/* Timeout thresholds for header and response phases, in LPTMR ticks. */
#define LIN_SYNC_TIMEOUT_RESPONSE_TICKS             (15U)
#define LIN_SYNC_TIMEOUT_HEADER_TICKS               (5U)

/* Empty by default; connect to scope/logic analyzer when needed. */
#define LIN_SYNC_TESTPOINT_ENABLE                   (1U)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define LIN_SYNC_LPTMR_TOTAL_TICKS \
    ((uint32_t)(LIN_SYNC_LPTMR_PERIOD_US * LIN_SYNC_LPTMR_TICKS_PER_US))
#define LIN_SYNC_ARRAY_SIZE(array_) \
    ((uint32_t)(sizeof(array_) / sizeof((array_)[0])))

#if ((LIN_SYNC_AUTOBAUD_MODE != LIN_SYNC_MODE_EVERY_HEADER_RECAL) && \
     (LIN_SYNC_AUTOBAUD_MODE != LIN_SYNC_MODE_STARTUP_RETRY_UNTIL_SUCCESS))
#error "LIN_SYNC_AUTOBAUD_MODE has an invalid value."
#endif

#if (LIN_SYNC_FALLING_EDGE_COUNT != 5U)
#error "LIN_SYNC_FALLING_EDGE_COUNT must remain 5 for Sync 0x55."
#endif

#if (LIN_SYNC_TWOBIT_SAMPLE_COUNT != (LIN_SYNC_FALLING_EDGE_COUNT - 1U))
#error "LIN_SYNC_TWOBIT_SAMPLE_COUNT must match falling capture count minus one."
#endif

/* Internal consistency window for adjacent two-bit DMA samples. */
#define LIN_SYNC_TWO_BIT_TOLERANCE_PERCENT          (14U)

#if LIN_SYNC_TESTPOINT_ENABLE
#define LIN_SYNC_TESTPOINT_BREAK_EXIT()       do { PINS_DRV_TogglePins(GPIOC, 1U << 2U); } while (0)
#define LIN_SYNC_TESTPOINT_GPIO_ENTRY()       do { PINS_DRV_ClearPins(GPIOC, 1U << 3U); } while (0)
#define LIN_SYNC_TESTPOINT_GPIO_EXIT()        do { PINS_DRV_SetPins(GPIOC, 1U << 3U); } while (0)
#else
#define LIN_SYNC_TESTPOINT_BREAK_EXIT()       do { } while (0)
#define LIN_SYNC_TESTPOINT_GPIO_ENTRY()       do { } while (0)
#define LIN_SYNC_TESTPOINT_GPIO_EXIT()        do { } while (0)
#endif
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
static uint8_t s_linTxBuff[8] = {0x55U, 0xAAU, 0x69U, 0x96U, 0x96U, 0x69U, 0xAAU, 0x55U};
static uint8_t s_linRxBuff[8];

static volatile bool s_isAutobaudActive = false;
static volatile bool s_dmaCaptureDone = false;
static volatile bool s_dmaCaptureError = false;
static volatile bool s_baudPrepared = false;
static volatile bool s_isStopIrqArmed = false;
static volatile uint32_t s_timerOverflowInterruptCount = 0U;
static volatile uint32_t s_dmaTimerValue[LIN_SYNC_FALLING_EDGE_COUNT];

static bool s_autobaudLocked = false;
static uint32_t s_ptmrClockHz = LIN_SYNC_PTMR_CLOCK_FALLBACK_HZ;
static uint32_t s_uartSourceClockHz = 0U;
static uint32_t s_currentBaudRate = LIN_SYNC_NOMINAL_BAUD;

static dma_loop_transfer_config_t s_dmaLoopTransferConfig = {
    .triggerLoopIterationCount = LIN_SYNC_FALLING_EDGE_COUNT,
    .srcOffsetEnable = false,
    .dstOffsetEnable = false,
    .triggerLoopOffset = 0,
    .transferLoopChnLinkEnable = false,
    .transferLoopChnLinkNumber = 0U,
    .triggerLoopChnLinkEnable = false,
    .triggerLoopChnLinkNumber = 0U,
};

static dma_transfer_config_t s_dmaTransferConfig = {
    .srcAddr = 0U,
    .destAddr = 0U,
    .srcTransferSize = DMA_TRANSFER_SIZE_4B,
    .destTransferSize = DMA_TRANSFER_SIZE_4B,
    .srcOffset = 0,
    .destOffset = (int16_t)sizeof(uint32_t),
    .srcLastAddrAdjust = 0,
    .destLastAddrAdjust = 0,
    .srcModulo = DMA_MODULO_OFF,
    .destModulo = DMA_MODULO_OFF,
    .transferLoopByteCount = sizeof(uint32_t),
    .ramReloadEnable = false,
    .ramReloadNextDescAddr = 0U,
    .interruptEnable = true,
    .loopTransferConfig = &s_dmaLoopTransferConfig,
};
/* USER CODE END PV */

/* Private function declare --------------------------------------------------*/
/* USER CODE BEGIN PFDC */
static uint32_t LinSync_LptmrGetTimeIntervalCallback(uint32_t *ns);

static void LinSync_InitRuntime(void);
static bool LinSync_NeedAutobaud(void);
static void LinSync_MarkAutobaudLocked(void);
static void LinSync_ArmDmaCaptureIdle(void);
static void LinSync_EnableFallingEdgeDmaCapture(void);
static void LinSync_DisableGpioEdgeCapture(void);
static void LinSync_RearmForNextHeader(bool gotoIdleState);
static void LinSync_LptmrIsrHandler(void);
static void LinSync_RxPinStopIrqHandler(void);
static void LinSync_DmaCallback(void *parameter, dma_chn_status_t status);
static void LinSync_LinCallbackHandler(uint32_t instance, void *linState);
static lin_sync_capture_status_t LinSync_ProcessFallingEdgeSamples(void);
static status_t LinSync_SetBaudRate(uint32_t desiredBaudRate);
static uint32_t LinSync_Divide(uint32_t dividend, uint32_t divisor);
static uint32_t LinSync_DivideRoundClosest(uint32_t dividend, uint32_t divisor);
/* USER CODE END PFDC */
static void Board_Init(void);

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/*!
 * @brief LIN stack event callback.
 *
 * This callback bridges low-level LIN driver events to the local auto-baud
 * state machine. Break detection enters the GPIO/DMA Sync capture flow, PID OK
 * starts the response phase, and timeout/frame errors re-arm the next header.
 *
 * @param instance LIN instance number.
 * @param linState LIN driver state object passed by the low-level callback.
 */
static void LinSync_LinCallbackHandler(uint32_t instance, void *linState)
{
    lin_state_t *currentState = (lin_state_t *)linState;


    if (currentState->timeoutCounterFlag)
    {
        currentState->timeoutCounterFlag = false;
        LinSync_RearmForNextHeader(false);
        return;
    }

    switch (currentState->currentEventId)
    {
        case LIN_PID_OK:
            LIN_DRV_SetTimeoutCounter(instance, LIN_SYNC_TIMEOUT_RESPONSE_TICKS);
            if (currentState->currentId == LIN_SYNC_RX_PID)
            {
                (void)LIN_DRV_ReceiveFrameData(instance, s_linRxBuff, (uint8_t)sizeof(s_linRxBuff));
            }
            else if (currentState->currentId == LIN_SYNC_TX_PID)
            {
                (void)LIN_DRV_SendFrameData(instance, s_linTxBuff, (uint8_t)sizeof(s_linTxBuff));
            }
            else
            {
                LinSync_RearmForNextHeader(true);
            }
            break;

        case LIN_RECV_BREAK_FIELD_OK:
            LIN_SYNC_TESTPOINT_BREAK_EXIT();
            if (LinSync_NeedAutobaud())
            {
                /* Break is already confirmed; the first DMA sample must now be Sync start falling. */
                LinSync_EnableFallingEdgeDmaCapture();
            }
            LIN_DRV_SetTimeoutCounter(instance, LIN_SYNC_TIMEOUT_HEADER_TICKS);
            break;

        case LIN_TX_COMPLETED:
            LinSync_MarkAutobaudLocked();
            LinSync_RearmForNextHeader(false);
            break;

        case LIN_RX_COMPLETED:
            LinSync_MarkAutobaudLocked();
            LinSync_RearmForNextHeader(false);
            break;

        case LIN_SYNC_OK:
            break;

        case LIN_SYNC_ERROR:
        case LIN_PID_ERROR:
        case LIN_FRAME_ERROR:
        case LIN_READBACK_ERROR:
        case LIN_CHECKSUM_ERROR:
        case LIN_RX_OVERRUN:
            LinSync_RearmForNextHeader(false);
            break;

        case LIN_BAUDRATE_ADJUSTED:
        case LIN_WAKEUP_SIGNAL:
        case LIN_NO_EVENT:
        default:
            break;
    }
}

/*!
 * @brief Provide elapsed time between consecutive LIN callback invocations.
 *
 * The low-level LIN driver uses this callback for wakeup and timeout
 * supervision. The implementation combines the current LPTMR count with the
 * overflow counter accumulated by the LPTMR ISR.
 *
 * @param ns Output parameter returning the elapsed time since the previous call,
 *           in ns.
 *
 * @return Always returns 0 to match the existing LIN driver callback contract.
 */
static uint32_t LinSync_LptmrGetTimeIntervalCallback(uint32_t *ns)
{
    static uint32_t previousCountValue = 0UL;
    uint32_t counterValue = lpTMR_DRV_GetCounterValueByCount(LIN_SYNC_LPTMR_INSTANCE);
    uint32_t elapsedTicks = (s_timerOverflowInterruptCount * LIN_SYNC_LPTMR_TOTAL_TICKS) +
                            counterValue - previousCountValue;

    *ns = (elapsedTicks * 1000U) / LIN_SYNC_LPTMR_TICKS_PER_US;
    previousCountValue = counterValue;
    s_timerOverflowInterruptCount = 0U;
    return 0UL;
}
/* USER CODE END 0 */


/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void)
{
    /* USER CODE BEGIN 1 */
    /* USER CODE END 1 */ 
    Board_Init();
    /* USER CODE BEGIN 2 */
    /*
     * Auto-baud sequence:
     * 1. UART hardware detects Break and enters RECV_SYNC.
     * 2. If current policy still needs calibration, RX is switched to GPIO and
     *    only falling edges generate DMA requests.
     * 3. DMA captures 5 falling-edge timestamps, then the callback computes the
     *    baud rate from 4 consecutive 2-bit intervals and prepares UART BAUD.
     * 4. GPIO is immediately re-armed for a single rising-edge interrupt.
     * 5. That rising edge must be Sync stop; only there does the code remux RX
     *    back to UART so PID reception stays clean.
     */
    lin_config0.timerGetTimeIntervalCallback = LinSync_LptmrGetTimeIntervalCallback;

    /*
     * DMA completion and the final stop-rising GPIO ISR are the two critical
     * real-time steps in the DMA-based flow, so both run at the highest local
     * priority. LPTMR and UART can remain below them.
     */
    INT_SYS_SetPriority(LIN_SYNC_DMA_IRQn, 0U);
    INT_SYS_SetPriority(LIN_SYNC_GPIO_IRQn, 0U);
    INT_SYS_SetPriority(LIN_SYNC_LPTMR_IRQn, 1U);
    INT_SYS_SetPriority(LIN_SYNC_UART_IRQn, 1U);

    INT_SYS_InstallHandler(LIN_SYNC_LPTMR_IRQn, LinSync_LptmrIsrHandler, NULL);
    INT_SYS_InstallHandler(LIN_SYNC_GPIO_IRQn, LinSync_RxPinStopIrqHandler, NULL);
    INT_SYS_EnableIRQ(LIN_SYNC_DMA_IRQn);
    INT_SYS_EnableIRQ(LIN_SYNC_GPIO_IRQn);
    INT_SYS_EnableIRQ(LIN_SYNC_LPTMR_IRQn);

    lpTMR_DRV_Init(LIN_SYNC_LPTMR_INSTANCE, &LPTMR_Config, false);
    lpTMR_DRV_StartCounter(LIN_SYNC_LPTMR_INSTANCE);

    pTMR_DRV_Init(LIN_SYNC_PTMR_INSTANCE, &PTMR_Config);
    (void)pTMR_DRV_InitChannel(LIN_SYNC_PTMR_INSTANCE, LIN_SYNC_PTMR_CHANNEL, &ptmr_channel_0);
    pTMR_DRV_StartTimerChannels(LIN_SYNC_PTMR_INSTANCE, LIN_SYNC_PTMR_CHANNEL);

    (void)LIN_DRV_Init(LIN_SYNC_UART_INSTANCE, &lin_config0, &lin_config0_State);
    (void)LIN_DRV_InstallCallback(LIN_SYNC_UART_INSTANCE, LinSync_LinCallbackHandler);
    (void)DMA_DRV_InstallCallback(LIN_SYNC_DMA_CHANNEL, LinSync_DmaCallback, NULL);

    LinSync_InitRuntime();
    /* USER CODE END 2 */

    /* Infinite loop */
    /* USER CODE BEGIN WHILE */
    while (1)
    {
        /* USER CODE END WHILE */
        /* USER CODE BEGIN 3 */
    }
    /* USER CODE END 3 */
}

static void Board_Init(void)
{
    CLOCK_SYS_Init(g_clockManConfigsArr,CLOCK_MANAGER_CONFIG_CNT,g_clockManCallbacksArr,CLOCK_MANAGER_CALLBACK_CNT);
    if(STATUS_SUCCESS != CLOCK_SYS_UpdateConfiguration(CLOCK_MANAGER_ACTIVE_INDEX,CLOCK_MANAGER_POLICY_AGREEMENT))
    {
        /* USER CODE BEGIN ERROR_HANDLER 1 */
        /* USER CODE END ERROR_HANDLER 1 */
    }
    PINS_DRV_Init(NUM_OF_CONFIGURED_PINS0,g_pin_mux_InitConfigArr0);
    UTILITY_PRINT_Init();
    FLASH_ECC_Fault_Init(&flash_ecc_fault_config0,&flash_ecc_fault_config0_State);
    DMA_DRV_Init(&dmaState,&dmaController_InitConfig,dmaChnState,dmaChnConfigArray,NUM_OF_CONFIGURED_DMA_CHANNEL);
}

/* USER CODE BEGIN 4 */

/*!
 * @brief Return whether the next header still needs auto-baud measurement.
 */
static bool LinSync_NeedAutobaud(void)
{
#if (LIN_SYNC_AUTOBAUD_MODE == LIN_SYNC_MODE_EVERY_HEADER_RECAL)
    (void)s_autobaudLocked;
    return true;
#else
    return (!s_autobaudLocked);
#endif
}

/*!
 * @brief Lock auto-baud after the first successful Sync capture when configured.
 */
static void LinSync_MarkAutobaudLocked(void)
{
#if (LIN_SYNC_AUTOBAUD_MODE == LIN_SYNC_MODE_STARTUP_RETRY_UNTIL_SUCCESS)
    s_autobaudLocked = true;
#endif
}

/*!
 * @brief Initialize the runtime context used by the auto-baud algorithm.
 */
static void LinSync_InitRuntime(void)
{
    uint32_t index;

    (void)CLOCK_SYS_GetFreq(LIN_SYNC_PTMR_CLOCK_NAME, &s_ptmrClockHz);
    (void)CLOCK_SYS_GetFreq(LIN_SYNC_UART_CLOCK_NAME, &s_uartSourceClockHz);
    if (s_ptmrClockHz == 0U)
    {
        s_ptmrClockHz = LIN_SYNC_PTMR_CLOCK_FALLBACK_HZ;
    }

    s_isAutobaudActive = false;
    s_dmaCaptureDone = false;
    s_dmaCaptureError = false;
    s_baudPrepared = false;
    s_isStopIrqArmed = false;
    s_autobaudLocked = false;
    s_currentBaudRate = LIN_SYNC_NOMINAL_BAUD;

    for (index = 0U; index < LIN_SYNC_FALLING_EDGE_COUNT; index++)
    {
        s_dmaTimerValue[index] = 0U;
    }

    LinSync_DisableGpioEdgeCapture();
    PINS_DRV_SetMuxModeSel(LIN_SYNC_RX_PCTRL, LIN_SYNC_RX_PIN, LIN_SYNC_RX_MUX);
    LinSync_ArmDmaCaptureIdle();
}

/*!
 * @brief Prepare DMA TCD in idle state.
 *
 * DMA must be armed before Break arrives. Break OK callback only switches the
 * RX pin into GPIO falling-edge request mode; it does not reconfigure TCD.
 */
static void LinSync_ArmDmaCaptureIdle(void)
{
    uint32_t index;

    s_dmaCaptureDone = false;
    s_dmaCaptureError = false;
    s_baudPrepared = false;
    s_isStopIrqArmed = false;

    for (index = 0U; index < LIN_SYNC_FALLING_EDGE_COUNT; index++)
    {
        s_dmaTimerValue[index] = 0U;
    }

    LinSync_DisableGpioEdgeCapture();
    PINS_DRV_SetMuxModeSel(LIN_SYNC_RX_PCTRL, LIN_SYNC_RX_PIN, LIN_SYNC_RX_MUX);

    (void)DMA_DRV_StopChannel(LIN_SYNC_DMA_CHANNEL);
    s_dmaTransferConfig.srcAddr = (uint32_t)&pTMR0->CH[LIN_SYNC_PTMR_CHANNEL].TCV;
    s_dmaTransferConfig.destAddr = (uint32_t)&s_dmaTimerValue[0];
    s_dmaLoopTransferConfig.triggerLoopIterationCount = LIN_SYNC_FALLING_EDGE_COUNT;
    (void)DMA_DRV_ConfigLoopTransfer(LIN_SYNC_DMA_CHANNEL, &s_dmaTransferConfig);
    DMA_DRV_DisableRequestsOnTransferComplete(LIN_SYNC_DMA_CHANNEL, true);
    (void)DMA_DRV_StartChannel(LIN_SYNC_DMA_CHANNEL);
}

/*!
 * @brief Enter GPIO falling-edge DMA capture mode.
 *
 * Break detection has already been completed by UART hardware. From this point
 * on, RX is temporarily detached from UART and each falling edge of Sync
 * generates one DMA request that copies pTMR->TCV into s_dmaTimerValue[].
 */
static void LinSync_EnableFallingEdgeDmaCapture(void)
{
    uint32_t index;

    s_isAutobaudActive = true;
    s_dmaCaptureDone = false;
    s_dmaCaptureError = false;
    s_baudPrepared = false;
    s_isStopIrqArmed = false;

    for (index = 0U; index < LIN_SYNC_FALLING_EDGE_COUNT; index++)
    {
        s_dmaTimerValue[index] = 0U;
    }

    PINS_DRV_SetMuxModeSel(LIN_SYNC_RX_PCTRL, LIN_SYNC_RX_PIN, PCTRL_MUX_AS_GPIO);
    PINS_DRV_ClearPinIntFlagCmd(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN);
    PINS_DRV_SetPinIntSel(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN, PCTRL_DMA_FALLING_EDGE);
    UART_SetIntMode(LIN_SYNC_UART_BASE, UART_INT_LIN_BREAK_DETECT, false);
}

/*!
 * @brief Disable GPIO DMA/interrupt edge selection.
 *
 * This helper only stops edge capture and clears the GPIO interrupt flag. RX
 * pin mux restoration is handled separately so the code can defer the handoff
 * back to UART until the Sync stop rising edge is confirmed.
 */
static void LinSync_DisableGpioEdgeCapture(void)
{
    PINS_DRV_SetPinIntSel(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN, PCTRL_DMA_INT_DISABLED);
    PINS_DRV_ClearPinIntFlagCmd(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN);
}

/*!
 * @brief Safe division helper.
 */
static uint32_t LinSync_Divide(uint32_t dividend, uint32_t divisor)
{
    if (divisor == 0U)
    {
        return 0U;
    }

#if defined(DIVSQRT_INSTANCE_COUNT) && (DIVSQRT_INSTANCE_COUNT > 0)
    DIVSQRT->DEND = dividend;
    DIVSQRT->DSOR = divisor;
    DIVSQRT->CSR |= DIVSQRT_CSR_SRT_MASK;
    return DIVSQRT->RES;
#else
    return (dividend / divisor);
#endif
}

/*!
 * @brief Integer division with rounding to nearest.
 */
static uint32_t LinSync_DivideRoundClosest(uint32_t dividend, uint32_t divisor)
{
    return LinSync_Divide((dividend + (divisor / 2U)), divisor);
}

/*!
 * @brief Re-arm local state for the next header.
 *
 * @param gotoIdleState `true` explicitly requests the LIN driver to return to
 *                      IDLE; `false` resets only the local context.
 */
static void LinSync_RearmForNextHeader(bool gotoIdleState)
{
    if (gotoIdleState)
    {
        (void)LIN_DRV_GotoIdleState(LIN_SYNC_UART_INSTANCE);
    }

    s_isAutobaudActive = false;
    s_dmaCaptureDone = false;
    s_dmaCaptureError = false;
    s_baudPrepared = false;
    s_isStopIrqArmed = false;

    LinSync_DisableGpioEdgeCapture();
    PINS_DRV_SetMuxModeSel(LIN_SYNC_RX_PCTRL, LIN_SYNC_RX_PIN, LIN_SYNC_RX_MUX);

    if (LinSync_NeedAutobaud())
    {
        LinSync_ArmDmaCaptureIdle();
    }
    else
    {
        (void)DMA_DRV_StopChannel(LIN_SYNC_DMA_CHANNEL);
    }
}

/*!
 * @brief Reconfigure UART OSR/SBR for the requested baud rate.
 *
 * This function runs after Sync sampling has completed and must finish before
 * the PID byte arrives. For that reason, it scans only a small, prioritized
 * set of OSR candidates and exits early once the baud error is sufficiently low.
 *
 * @param desiredBaudRate Requested baud rate.
 *
 * @return `STATUS_SUCCESS` if a valid UART divider configuration has been applied.
 */
static status_t LinSync_SetBaudRate(uint32_t desiredBaudRate)
{
    // static const uint8_t s_osrCandidates[7] = {16U, 8U, 32U, 12U, 24U, 20U, 4U};
    static const uint8_t s_osrCandidates[1] = {32U};
    uint32_t bestOsr = 0U;
    uint16_t bestSbr = 1U;
    uint32_t bestDiff = UINT32_MAX;
    uint32_t maxOsr;
    uint32_t acceptableError;

    if ((desiredBaudRate == 0U) || (s_uartSourceClockHz < (desiredBaudRate * 4U)))
    {
        return STATUS_ERROR;
    }

    acceptableError = desiredBaudRate / 200U;
    maxOsr = LinSync_Divide(s_uartSourceClockHz, desiredBaudRate);
    if (maxOsr > 32U)
    {
        maxOsr = 32U;
    }

    for (uint32_t index = 0U; index < LIN_SYNC_ARRAY_SIZE(s_osrCandidates); index++)
    {
        uint32_t candidateOsr = (uint32_t)s_osrCandidates[index];
        uint32_t calculatedBaud;
        uint32_t tempDiff;
        uint16_t sbr;

        if ((candidateOsr < 4U) || (candidateOsr > maxOsr))
        {
            continue;
        }

        sbr = (uint16_t)LinSync_DivideRoundClosest(s_uartSourceClockHz, desiredBaudRate * candidateOsr);
        if (sbr == 0U)
        {
            sbr = 1U;
        }

        calculatedBaud = LinSync_DivideRoundClosest(s_uartSourceClockHz, candidateOsr * (uint32_t)sbr);
        tempDiff = (calculatedBaud > desiredBaudRate) ? (calculatedBaud - desiredBaudRate) :
                                                        (desiredBaudRate - calculatedBaud);

        if (tempDiff < bestDiff)
        {
            bestDiff = tempDiff;
            bestOsr = candidateOsr;
            bestSbr = sbr;

            if (bestDiff <= acceptableError)
            {
                break;
            }
        }
    }

    if (bestOsr == 0U)
    {
        return STATUS_ERROR;
    }

    INT_SYS_DisableIRQGlobal();
    if (bestOsr < 8U)
    {
        UART_EnableBothEdgeSamplingCmd(LIN_SYNC_UART_BASE);
    }
    else
    {
        LIN_SYNC_UART_BASE->CTRL1 &= ~(uint32_t)UART_CTRL1_BOTHEDGE_MASK;
    }
    UART_SetOversamplingRatio(LIN_SYNC_UART_BASE, bestOsr - 1U);
    UART_SetBaudRateDivisor(LIN_SYNC_UART_BASE, bestSbr);
    INT_SYS_EnableIRQGlobal();

    return STATUS_SUCCESS;
}

/*!
 * @brief LPTMR ISR.
 */
static void LinSync_LptmrIsrHandler(void)
{
    lpTMR_DRV_ClearCompareFlag(LIN_SYNC_LPTMR_INSTANCE);
    LIN_DRV_TimeoutService(LIN_SYNC_UART_INSTANCE);
    s_timerOverflowInterruptCount++;
}

/*!
 * @brief GPIO ISR used only for the Sync stop rising edge handoff.
 *
 * Once DMA has captured the five falling edges and baud preparation is ready,
 * GPIO detection is switched to a single rising-edge interrupt. That rising
 * edge must be Sync stop; only here is the RX pin mux restored to UART mode.
 */
static void LinSync_RxPinStopIrqHandler(void)
{
    if ((PINS_DRV_GetPortIntFlag(LIN_SYNC_RX_GPIO) & (1UL << LIN_SYNC_RX_PIN)) == 0U)
    {
        return;
    }

    PINS_DRV_ClearPinIntFlagCmd(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN);
    if ((!s_isStopIrqArmed) || (!s_baudPrepared))
    {
        return;
    }

    LIN_SYNC_TESTPOINT_GPIO_ENTRY();

    LinSync_DisableGpioEdgeCapture();

    PINS_DRV_SetMuxModeSel(LIN_SYNC_RX_PCTRL, LIN_SYNC_RX_PIN, LIN_SYNC_RX_MUX);

    lin_config0_State.currentNodeState = LIN_NODE_STATE_RECV_PID;
    lin_config0_State.currentEventId = LIN_SYNC_OK;

    s_isAutobaudActive = false;
    s_baudPrepared = false;
    s_isStopIrqArmed = false;
    s_dmaCaptureDone = false;

    if (lin_config0_State.Callback != NULL)
    {
        lin_config0_State.Callback(LIN_SYNC_UART_INSTANCE, &lin_config0_State);
    }

    LIN_SYNC_TESTPOINT_GPIO_EXIT();
}

/*!
 * @brief DMA completion callback for the 5 falling-edge timestamps.
 *
 * DMA completion means the Sync start/bit1/bit3/bit5/bit7 falling edges are
 * already timestamped in memory. The callback immediately validates the timing,
 * prepares UART baud registers, and then re-arms GPIO for the final stop
 * rising-edge interrupt.
 */
static void LinSync_DmaCallback(void *parameter, dma_chn_status_t status)
{
    lin_sync_capture_status_t captureStatus;

    (void)parameter;
    LIN_SYNC_TESTPOINT_GPIO_ENTRY();

    if (!s_isAutobaudActive)
    {
        LIN_SYNC_TESTPOINT_GPIO_EXIT();
        return;
    }

    LinSync_DisableGpioEdgeCapture();

    if (status != DMA_CHN_NORMAL)
    {
        s_dmaCaptureError = true;
        LinSync_RearmForNextHeader(true);
        LIN_SYNC_TESTPOINT_GPIO_EXIT();
        return;
    }

    s_dmaCaptureDone = true;
    captureStatus = LinSync_ProcessFallingEdgeSamples();
    if (captureStatus == LIN_SYNC_CAPTURE_SYNC_ERROR)
    {
        s_dmaCaptureError = true;
        LinSync_RearmForNextHeader(true);
    }

    LIN_SYNC_TESTPOINT_GPIO_EXIT();
}

/*!
 * @brief Process 5 falling-edge DMA samples.
 *
 * Timestamps correspond to:
 * sync start falling, bit1 falling, bit3 falling, bit5 falling, bit7 falling.
 * Four deltas therefore each represent a 2-bit interval.
 *
 * @return Capture status:
 *         - `LIN_SYNC_CAPTURE_SYNC_OK`: Sync timings are valid and the stop
 *           rising-edge interrupt has been armed;
 *         - `LIN_SYNC_CAPTURE_SYNC_ERROR`: Sync evaluation failed and the frame
 *           must be abandoned;
 *         - `LIN_SYNC_CAPTURE_ABORT`: capture processing is not valid in the
 *           current local/LIN state.
 */
static lin_sync_capture_status_t LinSync_ProcessFallingEdgeSamples(void)
{
    lin_state_t *linState = &lin_config0_State;
    uint32_t deltaSum = 0U;
    uint32_t previousDelta = 0U;
    uint32_t averageTwoBitDelta;
    uint32_t measuredBaud;
    uint32_t minBaud = (LIN_SYNC_NOMINAL_BAUD * (1000U - LIN_SYNC_BAUD_TOLERANCE_PERMILLE)) / 1000U;
    uint32_t maxBaud = (LIN_SYNC_NOMINAL_BAUD * (1000U + LIN_SYNC_BAUD_TOLERANCE_PERMILLE)) / 1000U;
    uint32_t sampleIndex;

    if ((!s_isAutobaudActive) || (linState->currentNodeState != LIN_NODE_STATE_RECV_SYNC))
    {
        return LIN_SYNC_CAPTURE_ABORT;
    }

    PINS_DRV_ClearPinIntFlagCmd(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN);
    PINS_DRV_SetPinIntSel(LIN_SYNC_RX_GPIO, LIN_SYNC_RX_PIN, PCTRL_INT_RISING_EDGE);

    for (sampleIndex = 0U; sampleIndex < LIN_SYNC_TWOBIT_SAMPLE_COUNT; sampleIndex++)
    {
        /*
         * pTMR runs as a free-running down-counter in this project, so unsigned
         * subtraction naturally yields the elapsed ticks between two captures.
         */
         uint32_t delta = ((const uint32_t *)s_dmaTimerValue)[sampleIndex] - 
                            ((const uint32_t *)s_dmaTimerValue)[sampleIndex + 1U];

        if (delta == 0U)
        {
            linState->currentEventId = LIN_SYNC_ERROR;
            return LIN_SYNC_CAPTURE_SYNC_ERROR;
        }

        if (sampleIndex > 0U)
        {
            uint32_t lowerBound = ((100U - LIN_SYNC_TWO_BIT_TOLERANCE_PERCENT) * previousDelta) / 100U;
            uint32_t upperBound = ((100U + LIN_SYNC_TWO_BIT_TOLERANCE_PERCENT) * previousDelta) / 100U;

            if ((delta < lowerBound) || (delta > upperBound))
            {
                linState->currentEventId = LIN_SYNC_ERROR;
                return LIN_SYNC_CAPTURE_SYNC_ERROR;
            }
        }

        deltaSum += delta;
        previousDelta = delta;
    }

    averageTwoBitDelta = LinSync_DivideRoundClosest(deltaSum, LIN_SYNC_TWOBIT_SAMPLE_COUNT);
    if (averageTwoBitDelta == 0U)
    {
        linState->currentEventId = LIN_SYNC_ERROR;
        return LIN_SYNC_CAPTURE_SYNC_ERROR;
    }

    measuredBaud = LinSync_DivideRoundClosest(2U * s_ptmrClockHz, averageTwoBitDelta);

    s_currentBaudRate = measuredBaud;
    if ((s_currentBaudRate < minBaud) || (s_currentBaudRate > maxBaud) ||
        (LinSync_SetBaudRate(s_currentBaudRate) != STATUS_SUCCESS))
    {
        linState->currentEventId = LIN_SYNC_ERROR;
        return LIN_SYNC_CAPTURE_SYNC_ERROR;
    }

    s_baudPrepared = true;
    s_isStopIrqArmed = true;

    return LIN_SYNC_CAPTURE_SYNC_OK;
}

/* USER CODE END 4 */
