/**
  **********************************************************************************************************************
  * @file    stm32_hal_timebase_tim.c
  * @brief   This pattern provides read-only components that are not configurable and require no customization.
  **********************************************************************************************************************
  * @attention
  *
  * Copyright (c) 2026 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  **********************************************************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "stm32_hal.h"
#include "stm32_hal_timebase.h"

/** @defgroup TimeBase_TIM_Introduction TimeBase TIM Introduction
  * @{

  - The TIM time base pattern provides an alternative to the default Cortex System Timer (SysTick)
    for the HAL time base in STM32 microcontrollers.

  - This is useful in applications where SysTick is reserved for an RTOS, and a hardware timer (TIM)
    can generate periodic interrupts for the HAL tick.

  - The TIM timebase implementation is ready-to-use, requires no customization, and integrates
    seamlessly with the STM32 HAL.
  */
/**
  * @}
  */

/** @defgroup TimeBase_TIM_How_To_Use TimeBase TIM How To Use
  * @{

  # How to use this pattern

  - **Add the required files to the project:**
    - `hal_timebase_tim.c`
    - The corresponding TIM HAL driver (`stm32c5xx_hal_tim.c`).

  - **Enable the TIM HAL module:**
    - In `stm32c5xx_hal_conf.h`, uncomment or add:
      #define USE_HAL_TIM_MODULE 1U

  - **Initialize the TIM peripheral instance used as the time base:**
    - Use the initialization functions, either generated by STM32CubeMX2 or the TIM template (`mx_timx.c/.h`).

  - **Ensure the link between files:**
    - The connection between `stm32c5xx_hal_timebase_tim.c` and `mx_timx.h` is managed
      through aliases in `stm32c5xx_hal_timebase.h`.

  - **Example of time base initialization:**
    - HAL_InitTick(HAL_TICK_FREQ_1KHZ, 0); // 1 ms tick, priority 0

  - **Suspend/resume the tick if needed:**
    - HAL_SuspendTick();
    - // ... critical section ...
    - HAL_ResumeTick();

  - **Note:**
    - This component is ready-to-use and read-only. No customization is required.
  */
/**
  * @}
  */

/** @defgroup TimeBase_TIM_Configuration_Table TimeBase TIM Configuration Table
  * @{
  # Configuration inside the TIM time base

  Config defines                 | Where            | Default value     | Note
  ------------------------------ | ---------------- | ----------------- | -------------------------------------------
  USE_HAL_TIM_MODULE             | hal_conf.h       | 1                 | HAL TIM module is enabled
  USE_HAL_TIM_REGISTER_CALLBACKS | hal_conf.h       | 0                 | Enable register callback feature
  USE_HAL_TIM_CLK_ENABLE_MODEL   | hal_conf.h       | HAL_CLK_ENABLE_NO | Enable the gating of the peripheral clock
  USE_HAL_CHECK_PARAM            | hal_conf.h       | 0                 | Enable run-time parameter check
  USE_HAL_CHECK_PROCESS_STATE    | hal_conf.h       | 0                 | Enable atomic access to process state check
  USE_ASSERT_DBG_PARAM           | PreProcessor env | NA                | Enable the parameter assert
  USE_ASSERT_DBG_STATE           | PreProcessor env | NA                | Enable the state assert
  */
/**
  * @}
  */

/* Private types -------------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macros ------------------------------------------------------------*/
/*! Check HAL tick frequency value */
#define IS_TICK_FREQ(freq) (((freq) == HAL_TICK_FREQ_10HZ)     \
                            || ((freq) == HAL_TICK_FREQ_100HZ) \
                            || ((freq) == HAL_TICK_FREQ_1KHZ))

/*! Check HAL tick priority value */
#define IS_TICK_PRIO(prio) ((prio) <= ((1UL << __NVIC_PRIO_BITS) - 1UL))

static hal_status_t find_psc_arr(uint32_t tim_freq, uint32_t Update_event_freq, uint32_t *psc, uint32_t *arr);
static hal_status_t tim_timebase_config_tick(hal_tick_freq_t tick_freq);

/* Private constants ---------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
extern hal_tick_freq_t uwTickFreq;

/* Private function prototypes -----------------------------------------------*/
#if defined(USE_HAL_TIM_REGISTER_CALLBACKS) && (USE_HAL_TIM_REGISTER_CALLBACKS == 1U)
void TimeBase_TIM_UpdateCallback(hal_tim_handle_t *htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */

/* Private functions ---------------------------------------------------------*/
static hal_status_t tim_timebase_config_tick(hal_tick_freq_t tick_freq)
{
  hal_status_t status;

  hal_tim_config_t  config;

  uint32_t tim_freq = HAL_TIM_GetClockFreq(timebase_gethandle());

  config.counter_mode           = HAL_TIM_COUNTER_UP;
  config.repetition_counter     = 0;
  config.clock_sel.clock_source = HAL_TIM_CLK_INTERNAL;

  status = find_psc_arr(tim_freq, (1000UL / (uint32_t)tick_freq), &config.prescaler, &config.period);
  if (status == HAL_OK)
  {
    status = HAL_TIM_SetConfig(timebase_gethandle(), &config);
  }

  return status;
}

hal_status_t find_psc_arr(uint32_t tim_freq, uint32_t Update_event_freq, uint32_t *psc, uint32_t *arr)
{
  /* The update event period is calculated as follows:
   * Update_event = TIM_CLK/((PSC + 1)*(ARR + 1)*(RCR + 1))
   *
   * Where: TIM_CLK = timer clock input
   * PSC = 16-bit prescaler register (0..65535)
   * ARR = 16/32-bit Autoreload register (1..65535)
   * RCR = 16-bit repetition counter
   */
  uint32_t target = 0;
  uint32_t ppsc = 0;
  uint32_t parr = 0;

  if (Update_event_freq == 0 || tim_freq % Update_event_freq != 0)
  {
    return HAL_ERROR; /* Invalid parameters */
  }

  target = tim_freq / Update_event_freq;

  for (ppsc = 0; ppsc <= UINT16_MAX; ppsc++)
  {
    if (target % (ppsc + 1) == 0)
    {
      parr = (target / (ppsc + 1)) - 1;
      if (parr >= 1 && parr <= UINT16_MAX) /* ARR min = 1, max = UINT16_MAX (65535) */
      {
        *psc = (uint16_t)ppsc;
        *arr = (uint16_t)parr;
        return HAL_OK; /* Found a valid pair */
      }
    }
  }

  return HAL_ERROR;
}

/* Exported functions --------------------------------------------------------*/
/** @defgroup TimeBase_TIM_Exported_Functions HAL TimeBase TIM Functions
  * @{
  This group gathers functions for the HAL timebase based on a timer instance.
  */
/**
  * @brief  This function configures the TIMx as a time base source.
  *         The time source is configured to have a 1 ms time base with a dedicated
  *         tick interrupt priority.
  * @param  tick_priority  Tick interrupt priority.
  * @note   This function is called automatically at the beginning of the program after
  *         reset by HAL_Init() or when the clock is configured by HAL_RCC_ClockConfig().
  * @retval HAL_OK     HAL TIM time base correctly configured.
  * @retval HAL_ERROR  Error occurred during HAL TIM time base configuration.
  */
hal_status_t HAL_InitTick(hal_tick_freq_t tick_freq, uint32_t tick_priority)
{
  hal_status_t status = HAL_ERROR;
  ASSERT_DBG_PARAM(IS_TICK_FREQ(tick_freq));
  ASSERT_DBG_PARAM(IS_TICK_PRIO(tick_priority));

  if (HAL_TIM_GetState(timebase_gethandle()) == HAL_TIM_STATE_RESET)
  {
    if (timebase_init() == NULL)
    {
      return HAL_ERROR;
    }
  }
  else
  {
    HAL_TIM_Stop_IT(timebase_gethandle());
  }

  status = tim_timebase_config_tick(tick_freq);

  if (status == HAL_OK)
  {
#if defined(USE_HAL_TIM_REGISTER_CALLBACKS) && (USE_HAL_TIM_REGISTER_CALLBACKS == 1U)
    HAL_TIM_RegisterUpdateCallback(timebase_gethandle(), TimeBase_TIM_UpdateCallback);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */

    uwTickFreq = tick_freq;
    uwTickPrio = tick_priority;

    HAL_CORTEX_NVIC_SetPriority(TIMEBASE_IRQ, (hal_cortex_nvic_preemp_priority_t)tick_priority,
                                HAL_CORTEX_NVIC_SUB_PRIORITY_0);

    status = HAL_TIM_Start_IT(timebase_gethandle());
  }

  return status;
}

/**
  * @brief  Suspend tick increment.
  * @note   Disable the tick increment by disabling the TIMx update interrupt.
  */
void HAL_SuspendTick(void)
{
  HAL_TIM_Stop_IT(timebase_gethandle());
}

/**
  * @brief  Resume tick increment.
  * @note   Enable the tick increment by enabling the TIMx update interrupt.
  */
void HAL_ResumeTick(void)
{
  HAL_TIM_Start_IT(timebase_gethandle());
}

/**
  * @brief Timer update interrupt callback.
  * @note  Depending on the configuration, it can be registered as a callback or used directly.
  */
#if defined(USE_HAL_TIM_REGISTER_CALLBACKS) && (USE_HAL_TIM_REGISTER_CALLBACKS == 1U)
void TimeBase_TIM_UpdateCallback(hal_tim_handle_t *htim)
#else
void HAL_TIM_UpdateCallback(hal_tim_handle_t *htim)
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
{
  STM32_UNUSED(htim);
  HAL_IncTick();
}

/**
  * @}
  */
