/* =============================================================================
 * watchdog.c - 독립 워치독(IWDG)  (RPi systemd 자동재시작 대응)
 *
 * IWDG 는 LSI(저속 내부 RC, STM32F407 기준 약 32kHz) 로 구동된다.
 * timeout_ms 동안 watchdog_refresh() 호출이 없으면 MCU 가 하드 리셋된다.
 *
 * 타임아웃 계산:
 *   t_IWDG[s] = (reload + 1) * prescaler / f_LSI
 *   → reload  = timeout_s * f_LSI / prescaler - 1
 *   prescaler 는 4,8,16,32,64,128,256 중 선택, reload 는 12비트(0..4095) 클램프.
 *
 * 주의: LSI 실주파수는 부품별로 17~47kHz 편차가 있다. 본 구현은 공칭 32kHz 로
 *       reload 를 계산하므로 실제 타임아웃은 ±오차가 있다. 안전을 위해 메인
 *       헬스 루프의 refresh 주기는 설정 타임아웃의 1/2 이하로 둘 것.
 * ===========================================================================*/
#include "watchdog.h"
#include "stm32f4xx_hal.h"
#include <stddef.h>   /* size_t */

/* LSI 공칭 주파수(Hz). 데이터시트 typ. 32kHz. */
#define WDG_LSI_HZ              32000u

/* IWDG reload 레지스터는 12비트. */
#define WDG_RELOAD_MAX          0x0FFFu   /* 4095 */

/* IWDG 핸들(이 모듈 소유). */
static IWDG_HandleTypeDef hiwdg;

/* prescaler 코드 → 분주값 매핑(인덱스 = HAL 의 IWDG_PRESCALER_*). */
typedef struct {
    uint32_t code;   /* IWDG_PRESCALER_x */
    uint32_t div;    /* 실제 분주값 */
} wdg_presc_t;

static const wdg_presc_t WDG_PRESC[] = {
    { IWDG_PRESCALER_4,   4u   },
    { IWDG_PRESCALER_8,   8u   },
    { IWDG_PRESCALER_16,  16u  },
    { IWDG_PRESCALER_32,  32u  },
    { IWDG_PRESCALER_64,  64u  },
    { IWDG_PRESCALER_128, 128u },
    { IWDG_PRESCALER_256, 256u },
};
#define WDG_PRESC_COUNT  (sizeof(WDG_PRESC) / sizeof(WDG_PRESC[0]))

void watchdog_init(uint32_t timeout_ms)
{
    uint32_t chosen_code   = IWDG_PRESCALER_256; /* 안전 기본(최대 타임아웃) */
    uint32_t chosen_reload = WDG_RELOAD_MAX;
    size_t   i;

    if (timeout_ms == 0u) {
        timeout_ms = 1u; /* 0 방지 */
    }

    /* 요청 타임아웃을 12비트 reload 안에 담을 수 있는 가장 작은 prescaler 선택
     * (분해능을 최대화). 안 되면 가장 큰 prescaler(256)로 최대 범위 사용. */
    for (i = 0; i < WDG_PRESC_COUNT; ++i) {
        uint32_t div = WDG_PRESC[i].div;
        /* reload = timeout_ms * f_LSI / (1000 * div) - 1
         * 오버플로 방지를 위해 64비트 중간 계산. */
        uint64_t ticks = ((uint64_t)timeout_ms * (uint64_t)WDG_LSI_HZ) / 1000u;
        uint64_t reload64;

        if (ticks == 0u) {
            reload64 = 0u;
        } else {
            reload64 = (ticks / div);
            if (reload64 > 0u) {
                reload64 -= 1u; /* reload 는 (값+1) 카운트 */
            }
        }

        if (reload64 <= WDG_RELOAD_MAX) {
            chosen_code   = WDG_PRESC[i].code;
            chosen_reload = (uint32_t)reload64;
            break;
        }
    }

    /* 루프가 끝까지 못 맞췄다면(타임아웃이 너무 큼) 256 분주 + 최대 reload 클램프.
     * (32kHz, /256, reload=4095 → 약 32.7초가 IWDG 최대 타임아웃) */
    if (i >= WDG_PRESC_COUNT) {
        chosen_code   = IWDG_PRESCALER_256;
        chosen_reload = WDG_RELOAD_MAX;
    }

    if (chosen_reload > WDG_RELOAD_MAX) {
        chosen_reload = WDG_RELOAD_MAX;
    }

    hiwdg.Instance       = IWDG;
    hiwdg.Init.Prescaler = chosen_code;
    hiwdg.Init.Reload    = chosen_reload;
    /* IWDG_Init 은 카운터를 시작하고 즉시 한 번 refresh 한다. */
    if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
        /* 워치독을 켜지 못하면 시스템 보호가 불가하므로 무한 대기 → 외부 관찰 가능.
         * (여기서 bsp_fatal 을 부르지 않는 이유: 의존성 최소화.) */
        for (;;) {
            __NOP();
        }
    }
}

void watchdog_refresh(void)
{
    /* 카운터를 reload 값으로 재적재. 실패해도 다음 주기에 재시도. */
    (void)HAL_IWDG_Refresh(&hiwdg);
}
