/* =============================================================================
 * bsp.c - 보드 지원 패키지 (클럭/HAL/공통 주변장치 초기화)  [STM32F407VGT6]
 *
 * 책임:
 *   - HAL_Init() 및 SystemClock_Config(): HSE 8MHz -> PLL -> SYSCLK 168MHz,
 *     AHB 168MHz, APB1 42MHz, APB2 84MHz, Flash 5WS, PWR/VOS Scale1.
 *   - 공통 GPIO RCC 클럭 게이팅 + 상태 LED.
 *   - bsp_led_set/toggle, bsp_fatal(IRQ 차단 + 고속 점멸 + 워치독 미갱신 → 리셋).
 *   - HAL 타임베이스를 SysTick 이 아닌 TIM6 으로 둬서 FreeRTOS 가 SysTick 을
 *     단독으로 소유하게 한다(HAL_InitTick / TIM6_DAC_IRQHandler → HAL_IncTick).
 *
 * 참고: SystemClock_Config 값은 STM32F407 + 8MHz HSE(예: STM32F4-Discovery,
 *       또는 LAN8720 RMII 보드의 25MHz 크리스털을 8MHz 로 분주 공급하는 구성)
 *       기준의 표준 168MHz 설정이다. HSE 주파수가 다르면 PLLM 을 조정한다.
 * ===========================================================================*/
#include "bsp.h"
#include "app_config.h"

#include "stm32f4xx_hal.h"

/* ── 상태 LED 핀 ───────────────────────────────────────────────────────────
 * TODO(hw): 보드에 맞게 조정. 기본값은 STM32F4-Discovery 의 녹색 LED(PD12).
 *   - Discovery 계열: PD12(녹), PD13(주황), PD14(빨), PD15(파)
 *   - Nucleo-F4 계열 : PA5 (LD2) 로 바꿀 것
 * 여기서는 단일 "상태 LED" 만 운영 가시성 용도로 사용한다.
 */
#define BSP_LED_PORT            GPIOD
#define BSP_LED_PIN             GPIO_PIN_12
#define BSP_LED_GPIO_CLK_EN()   __HAL_RCC_GPIOD_CLK_ENABLE()

/* bsp_fatal() 고속 점멸 주기(루프 카운트). 정확한 타이밍은 불필요(곧 리셋됨). */
#define BSP_FATAL_BLINK_BUSYLOOP   400000u

/* ── HAL 타임베이스 전용 타이머 핸들 (TIM6) ───────────────────────────────── */
static TIM_HandleTypeDef htim6;

/* =============================================================================
 * SystemClock_Config — HSE 8MHz -> 168MHz
 *   PLL: VCO_in = HSE/PLLM = 8/8 = 1MHz
 *        VCO_out = 1MHz * PLLN(336) = 336MHz
 *        SYSCLK  = VCO_out / PLLP(2) = 168MHz
 *        PLLQ(7) -> 48MHz (USB/SDIO/RNG 클럭)
 *   AHB=/1(168), APB1=/4(42), APB2=/2(84), Flash latency 5WS.
 * ===========================================================================*/
static void SystemClock_Config(void)
{
    RCC_OscInitTypeDef osc = {0};
    RCC_ClkInitTypeDef clk = {0};

    /* 전압 레귤레이터 클럭 게이팅 + Scale1 (168MHz 동작에 필수). */
    __HAL_RCC_PWR_CLK_ENABLE();
    __HAL_PWR_VOLTAGE_SCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

    /* HSE -> PLL */
    osc.OscillatorType       = RCC_OSCILLATORTYPE_HSE;
    osc.HSEState             = RCC_HSE_ON;
    osc.PLL.PLLState         = RCC_PLL_ON;
    osc.PLL.PLLSource        = RCC_PLLSOURCE_HSE;
    osc.PLL.PLLM             = 8;    /* 8MHz HSE -> 1MHz VCO 입력 */
    osc.PLL.PLLN             = 336;  /* -> 336MHz VCO 출력 */
    osc.PLL.PLLP             = RCC_PLLP_DIV2;  /* -> 168MHz SYSCLK */
    osc.PLL.PLLQ             = 7;    /* -> 48MHz (RNG/USB) */
    if (HAL_RCC_OscConfig(&osc) != HAL_OK) {
        bsp_fatal("RCC_OscConfig");
    }

    /* 버스 클럭: SYSCLK=PLL, AHB=/1, APB1=/4, APB2=/2. Flash 5WS. */
    clk.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK |
                    RCC_CLOCKTYPE_PCLK1  | RCC_CLOCKTYPE_PCLK2;
    clk.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK;
    clk.AHBCLKDivider  = RCC_SYSCLK_DIV1;   /* HCLK  = 168MHz */
    clk.APB1CLKDivider = RCC_HCLK_DIV4;     /* PCLK1 =  42MHz */
    clk.APB2CLKDivider = RCC_HCLK_DIV2;     /* PCLK2 =  84MHz */
    if (HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_5) != HAL_OK) {
        bsp_fatal("RCC_ClockConfig");
    }
}

/* ── 공통 GPIO 클럭 게이팅 ──────────────────────────────────────────────────
 * RMII(LAN8720)/I2C/UART 등 다른 모듈이 자기 핀을 켜기 전에 흔히 쓰는 포트들을
 * 미리 enable 해 둔다(중복 enable 은 무해). 각 드라이버가 자기 MspInit 에서
 * 추가로 켜도 된다.
 */
static void bsp_gpio_clocks_init(void)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();   /* RMII: PA1/PA2/PA7 */
    __HAL_RCC_GPIOB_CLK_ENABLE();   /* RMII: PB11/PB12/PB13 · SHT30 I2C1: PB6/PB7 */
    __HAL_RCC_GPIOC_CLK_ENABLE();   /* RMII: PC1/PC4/PC5 */
    __HAL_RCC_GPIOD_CLK_ENABLE();   /* 로그 UART USART3: PD8/PD9 · 상태 LED: PD12 */
}

/* ── 상태 LED 초기화 ────────────────────────────────────────────────────── */
static void bsp_led_init(void)
{
    GPIO_InitTypeDef g = {0};
    BSP_LED_GPIO_CLK_EN();
    g.Pin   = BSP_LED_PIN;
    g.Mode  = GPIO_MODE_OUTPUT_PP;
    g.Pull  = GPIO_NOPULL;
    g.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(BSP_LED_PORT, &g);
    HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN, GPIO_PIN_RESET);
}

/* =============================================================================
 * 공개 API
 * ===========================================================================*/
void bsp_init(void)
{
    HAL_Init();             /* NVIC 그룹/Flash prefetch/기본 타임베이스 설정 */
    SystemClock_Config();   /* 168MHz */
    bsp_gpio_clocks_init();
    bsp_led_init();
}

void bsp_led_set(int on)
{
    HAL_GPIO_WritePin(BSP_LED_PORT, BSP_LED_PIN,
                      on ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

void bsp_led_toggle(void)
{
    HAL_GPIO_TogglePin(BSP_LED_PORT, BSP_LED_PIN);
}

void bsp_fatal(const char *reason)
{
    (void)reason; /* 로그는 호출측 책임. 여기선 안전 정지만 한다. */

    /* 인터럽트 전면 차단: 더 이상 어떤 태스크/ISR 도 실행되지 않게 한다.
     * 이 함수는 워치독을 갱신하지 않으므로 IWDG 가 곧 MCU 를 리셋한다. */
    __disable_irq();

    for (;;) {
        bsp_led_toggle();
        for (volatile uint32_t i = 0; i < BSP_FATAL_BLINK_BUSYLOOP; ++i) {
            __NOP();
        }
    }
}

/* =============================================================================
 * bsp_rand32 — 하드웨어 RNG 기반 32비트 난수 (강한 심볼)
 *
 * lwipopts.h 의 LWIP_RAND() 백엔드. net.c 의 약한(weak) 예측 가능 xorshift 대체
 * 구현을 이 강한 심볼이 덮어쓴다. TCP 초기 시퀀스 번호 / DHCP·DNS 트랜잭션 ID 의
 * 예측 가능성을 줄인다.
 *
 * 주의: RNG 주변장치는 TLS(tls_mbedtls.c)도 사용한다. 서로 다른 HAL 핸들이지만
 * 둘 다 RNG->DR 을 읽기만 하므로 공존 가능하다(드물게 상관된 값이 나올 수 있으나
 * lwip 용도에는 충분). RNG 클럭 소스는 PLLQ=48MHz (SystemClock_Config 참조).
 * RNG 불가 시 마지막 수단으로 틱 혼합값을 반환한다(예측 가능 — 정상 동작에선 미사용).
 * ===========================================================================*/
static RNG_HandleTypeDef s_bsp_rng;
static int               s_bsp_rng_ready = 0;

uint32_t bsp_rand32(void)
{
    if (!s_bsp_rng_ready) {
        __HAL_RCC_RNG_CLK_ENABLE();
        s_bsp_rng.Instance = RNG;
        if (HAL_RNG_Init(&s_bsp_rng) != HAL_OK) {
            return 0x9E3779B9u ^ (uint32_t)HAL_GetTick();   /* RNG 불가: 폴백 */
        }
        s_bsp_rng_ready = 1;
    }

    uint32_t v = 0;
    if (HAL_RNG_GenerateRandomNumber(&s_bsp_rng, &v) != HAL_OK) {
        return 0x9E3779B9u ^ (uint32_t)HAL_GetTick();        /* 일시 오류: 폴백 */
    }
    return v;
}

/* =============================================================================
 * HAL 타임베이스 오버라이드 (TIM6)
 *
 * 기본 HAL 은 SysTick 을 타임베이스로 쓴다. 그러나 FreeRTOS 도 SysTick 을
 * 스케줄러 틱으로 사용하므로 충돌한다. 아래 약(weak) 함수를 재정의하여 HAL
 * 타임베이스를 TIM6(기본 우선순위) 으로 옮긴다. FreeRTOS 는 SysTick 을 단독
 * 소유하고, HAL_GetTick()/HAL_Delay() 는 TIM6 인터럽트로 동작한다.
 *
 * HAL_Init() 내부에서 TICK_INT_PRIORITY 로 HAL_InitTick() 이 호출된다.
 * ===========================================================================*/
HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
    RCC_ClkInitTypeDef clkcfg;
    uint32_t flashLatency;
    uint32_t pclk1;
    uint32_t tim_clk;
    uint32_t prescaler;

    /* TIM6 클럭 = APB1 타이머 클럭. APB1 분주(>1)면 타이머 클럭은 PCLK1 x2. */
    HAL_RCC_GetClockConfig(&clkcfg, &flashLatency);
    pclk1 = HAL_RCC_GetPCLK1Freq();
    if (clkcfg.APB1CLKDivider == RCC_HCLK_DIV1) {
        tim_clk = pclk1;
    } else {
        tim_clk = pclk1 * 2u;  /* APB1 prescaler != 1 → 타이머 클럭 2배 (84MHz) */
    }

    /* 1MHz(=1us) 카운팅이 되도록 프리스케일러 계산 → 1ms 마다 업데이트 IRQ. */
    prescaler = (tim_clk / 1000000u) - 1u;

    __HAL_RCC_TIM6_CLK_ENABLE();

    htim6.Instance               = TIM6;
    htim6.Init.Prescaler         = prescaler;       /* → 1MHz */
    htim6.Init.CounterMode       = TIM_COUNTERMODE_UP;
    htim6.Init.Period            = 1000u - 1u;      /* 1000 카운트 = 1ms */
    htim6.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    if (HAL_TIM_Base_Init(&htim6) != HAL_OK) {
        return HAL_ERROR;
    }

    /* TIM6 업데이트 인터럽트 활성화. uwTickPrio 는 HAL 내부에서 참조됨. */
    if (TickPriority < (1uL << __NVIC_PRIO_BITS)) {
        HAL_NVIC_SetPriority(TIM6_DAC_IRQn, TickPriority, 0);
        uwTickPrio = TickPriority;
    } else {
        return HAL_ERROR;
    }
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);

    return HAL_TIM_Base_Start_IT(&htim6);
}

/* HAL_SuspendTick/ResumeTick 도 TIM6 기준으로 재정의(HAL_Delay 등 일관성). */
void HAL_SuspendTick(void)
{
    __HAL_TIM_DISABLE_IT(&htim6, TIM_IT_UPDATE);
}

void HAL_ResumeTick(void)
{
    __HAL_TIM_ENABLE_IT(&htim6, TIM_IT_UPDATE);
}

/* TIM6 업데이트 인터럽트 → HAL 틱 증가.
 * (startup_stm32f407xx.s 의 벡터 이름과 일치해야 한다: TIM6_DAC_IRQHandler) */
void TIM6_DAC_IRQHandler(void)
{
    if (__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE) != RESET &&
        __HAL_TIM_GET_IT_SOURCE(&htim6, TIM_IT_UPDATE) != RESET) {
        __HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
        HAL_IncTick();
    }
}
