Files
E3/e3_176_ref/drivers/source/watchdog/sdrv_watchdog.c
2025-10-21 19:40:27 +08:00

599 lines
17 KiB
C

/**
* @file sdrv_watchdog.c
* @brief Sdrv watchdog driver source.
*
* @copyright Copyright (c) 2022 Semidrive Semiconductor.
* All rights reserved.
*/
#include <sdrv_watchdog.h>
#include <sdrv_ckgen.h>
#include <clock_ip.h>
#include <udelay/udelay.h>
#include <part.h>
#include "sdrv_wdt_reg.h"
#define DEFAULT_REG_POLL_RETRY 10000
/* watchdog clock source */
#define MHZ (1000 * 1000)
#define KHZ (1000)
#define WDT_MAIN_CLK (24 * MHZ)
#define WDT_LP_CLK (32 * KHZ)
static uint32_t sdrv_wdt_reg_poll(volatile uint32_t *reg, uint32_t start,
uint32_t width, uint32_t expect)
{
uint32_t retry = DEFAULT_REG_POLL_RETRY;
volatile uint32_t v = 0;
do {
v = *reg;
if (((v>>start) & ((1<<width)-1)) == expect) {
return retry;
}
} while (--retry);
return retry;
}
static void sdrv_wdt_set_refresh_mode(sdrv_wdt_t *base, sdrv_wdt_refresh_e refresh)
{
/* clear bit0-2 */
base->wrc_ctrl &= ~0x7u;
switch (refresh) {
case WDT_DIRECT_REFRESH:
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM0);
break;
case WDT_WINDOW_REFRESH:
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM0);
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM1);
break;
case WDT_SEQUENCE_REFRESH:
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_SEQ_REFR);
break;
case WDT_DIRECT_SEQUENCE_REFRESH:
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM0);
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_SEQ_REFR);
break;
case WDT_WINDOW_SEQUENCE_REFRESH:
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM0);
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_MODEM1);
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_SEQ_REFR);
break;
default:
break;
}
}
static void sdrv_wdt_set_intr_enable(sdrv_wdt_t *base, sdrv_wdt_intr_e intr, bool enable)
{
uint32_t en_bit;
if (intr == WDT_ILL_WINDOW_REFRESH) {
en_bit = WDT_INT_ILL_WIN_REFE_INT_EN;
} else if (intr == WDT_ILL_SEQUENCE_RESRESH) {
en_bit = WDT_INT_ILL_SEQ_REFE_INT_EN;
} else if (intr == WDT_OVERFLOW) {
en_bit = WDT_INT_OVERFLOW_INT_EN;
}
base->intr &= ~(0x1u << en_bit);
if (enable) {
base->intr |= (0x1u << en_bit);
}
}
static void sdrv_wdt_set_internal_reset(sdrv_wdt_t *base, sdrv_wdt_internal_reset_t *int_rst)
{
base->rst_ctl = 0;
if (int_rst->reset_en) {
base->rst_ctl |= (0x1u << WDT_RST_CTRL_INT_RST_EN);
}
if (int_rst->reset_mode) {
base->rst_ctl |= (0x1u << WDT_RST_CTRL_INT_RST_MODE);
}
base->rst_ctl |= ((int_rst->reset_win & WDT_RST_CTRL_RST_WIN_MASK) << WDT_RST_CTRL_RST_WIN_LSB);
base->rst_ctl |= (int_rst->reset_cnt & WDT_RST_CTRL_RST_CNT_MASK);
if (int_rst->wdt_reset_en) {
base->rst_ctl |= (0x1u << WDT_RST_CTRL_WDT_RST_EN);
}
}
static void sdrv_wdt_set_external_reset(sdrv_wdt_t *base, sdrv_wdt_external_reset_t *ext_rst)
{
base->ext_rst_ctl = 0;
if (ext_rst->reset_en) {
base->ext_rst_ctl |= (0x1u << WDT_EXT_RST_CTRL_EXT_RST_EN);
}
if (ext_rst->reset_mode) {
base->ext_rst_ctl |= (0x1u << WDT_EXT_RST_CTRL_EXT_RST_MODE);
}
if (ext_rst->reset_pol) {
base->ext_rst_ctl |= (0x1u << WDT_EXT_RST_CTRL_EXT_RST_REQ_POL);
}
base->ext_rst_ctl |= ((ext_rst->reset_win & WDT_EXT_RST_CTRL_RST_WIN_MASK) << WDT_EXT_RST_CTRL_RST_WIN_LSB);
base->ext_rst_ctl |= (ext_rst->reset_cnt & WDT_EXT_RST_CTRL_RST_CNT_MASK);
}
static inline uint32_t sdrv_wdt_get_counter_cycle(sdrv_wdt_t *base)
{
sdrv_wdt_clock_e clk_src;
uint32_t pre_divide;
uint32_t freq;
clk_src = (sdrv_wdt_clock_e)((base->ctrl >> WDT_CTRL_CLK_SRC_LSB) & WDT_CTRL_CLK_SRC_MASK);
pre_divide = (base->ctrl >> WDT_CTRL_PRE_DIV_NUM_LSB) & WDT_CTRL_PRE_DIV_NUM_MASK;
pre_divide++;
if (clk_src == WDT_MAIN_CLOCK) {
freq = WDT_MAIN_CLK;
} else if (clk_src == WDT_LP_CLOCK) {
freq = WDT_LP_CLK;
} else if (clk_src == WDT_BUS_CLOCK) {
freq = sdrv_ckgen_bus_get_rate(CLK_NODE(g_ckgen_bus_cr5_sf), CKGEN_BUS_CLK_OUT_P);
} else {
return 0;
}
return (freq / pre_divide);
}
static uint32_t sdrv_wdt_get_counter_from_ms(sdrv_wdt_t *base, uint32_t ms)
{
uint32_t clk;
clk = sdrv_wdt_get_counter_cycle(base);
return clk ? (ms * (clk / 1000)) : 0;
}
#if (CONFIG_E3 == 1) || (CONFIG_E3L == 1) || (CONFIG_D3 == 1)
static status_t sdrv_wdt_register_init_after_lbist(sdrv_wdt_t *base)
{
base->ctrl |= (0x1u << WDT_CTRL_WDT_EN);
base->ctrl &= ~(0x1u << WDT_CTRL_DBG_HALT_EN);
base->ctrl |= (0x1u << WDT_CTRL_WDT_EN_SRC);
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_WDT_EN_STA, 1, 1)) {
return SDRV_STATUS_FAIL;
}
base->ctrl |= (0x1u << WDT_CTRL_SOFT_RST);
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_SOFT_RST, 1, 0)) {
return SDRV_STATUS_FAIL;
}
base->lock &= ~(0x1u << WDT_LOCK_EXT_RST_LOCK);
base->ext_rst_ctl &= ~(0x1u << WDT_EXT_RST_CTRL_EXT_RST_EN);
base->ext_rst_ctl &= ~(WDT_EXT_RST_CTRL_RST_WIN_MASK << WDT_EXT_RST_CTRL_RST_WIN_LSB);
base->ext_rst_ctl &= ~(0x1u << WDT_EXT_RST_CTRL_EXT_RST_MODE);
base->ext_rst_ctl = ((base->ext_rst_ctl & ~WDT_EXT_RST_CTRL_RST_CNT_MASK) | (0xffu));
base->rst_req_mon |= (0x1u << WDT_RST_REQ_MON_INT_RST_REQ_REC);
base->ctrl &= ~(0x1u << WDT_CTRL_WDT_EN);
base->ctrl |= (0x1u << WDT_CTRL_DBG_HALT_EN);
base->ctrl &= ~(0x1u << WDT_CTRL_WDT_EN_SRC);
return SDRV_STATUS_OK;
}
#endif
/**
* @brief Get the default configuration for watchdog.
*
* This function get the default configuration for watchdog. When you want initialize,
* you can call this function first and modify some of them, then call sdrv_wdt_init.
*
* @param [in] config WDT config struct.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_get_default_config(sdrv_wdt_config_t *config)
{
if (config) {
config->clk_src = WDT_MAIN_CLOCK;
config->pre_divide = 11999U;
config->wdt_en_src = 1;
config->wtc_src = 1;
config->auto_restart = 0;
config->refresh = WDT_DIRECT_REFRESH;
config->timeout = 0xFFFFFFFF;
config->window_low = 0xFFFFFFFF;
config->seq_delta = 0xFFFFFFFF;
config->intr_bitmap = WDT_OVERFLOW;
config->int_rst.reset_en = false;
config->int_rst.reset_mode = 1;
config->int_rst.reset_win = 0xF;
config->int_rst.reset_cnt = 0xFF;
config->int_rst.wdt_reset_en = false;
config->ext_rst.reset_en = false;
config->ext_rst.reset_mode = 1;
config->ext_rst.reset_pol = 0;
config->ext_rst.reset_win = 0xF;
config->ext_rst.reset_cnt = 0xFF;
}
return SDRV_STATUS_OK;
}
/**
* @brief Initialize watchdog with config parameters.
*
* This function config watchdog with parameters in sdrv_wdt_config_t.
*
* @param [in] base WDT control base.
* @param [in] config WDT config struct.
* @return Return SDRV_STATUS_OK or error code.
*/
status_t sdrv_wdt_init(sdrv_wdt_t *base, sdrv_wdt_config_t *config)
{
base->lock = 0;
if (base->lock) {
return SDRV_STATUS_FAIL;
}
#if (CONFIG_E3 == 1) || (CONFIG_E3L == 1) || (CONFIG_D3 == 1)
if (sdrv_wdt_register_init_after_lbist(base) != SDRV_STATUS_OK) {
return SDRV_STATUS_FAIL;
}
#endif
if (config->clk_src == WDT_EXT_CLOCK
|| config->clk_src == WDT_TIE_OFF) {
return SDRV_STATUS_FAIL;
}
/* WDT_CTRL */
base->ctrl = 0;
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_WDT_EN_STA, 1, 0)) {
return SDRV_STATUS_FAIL;
}
base->ctrl |= (0x1u << WDT_CTRL_DBG_HALT_EN);
base->ctrl |= ((config->clk_src & WDT_CTRL_CLK_SRC_MASK) << WDT_CTRL_CLK_SRC_LSB);
base->ctrl |= ((config->pre_divide & WDT_CTRL_PRE_DIV_NUM_MASK) << WDT_CTRL_PRE_DIV_NUM_LSB);
if (config->wdt_en_src) {
base->ctrl |= (0x1u << WDT_CTRL_WDT_EN_SRC);
}
if (config->wtc_src) {
base->ctrl |= (0x1u << WDT_CTRL_WTC_SRC);
}
if (config->auto_restart) {
base->ctrl |= (0x1u << WDT_CTRL_AUTO_RESTART);
}
sdrv_wdt_set_refresh_mode(base, config->refresh);
if(sdrv_wdt_clear_intr_status(base, WDT_ILL_WINDOW_REFRESH) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
if(sdrv_wdt_clear_intr_status(base, WDT_ILL_SEQUENCE_RESRESH) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
if(sdrv_wdt_clear_intr_status(base, WDT_OVERFLOW) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
sdrv_wdt_set_intr_enable(base, WDT_ILL_WINDOW_REFRESH, !!(WDT_ILL_WINDOW_REFRESH & config->intr_bitmap));
sdrv_wdt_set_intr_enable(base, WDT_ILL_SEQUENCE_RESRESH, !!(WDT_ILL_SEQUENCE_RESRESH & config->intr_bitmap));
sdrv_wdt_set_intr_enable(base, WDT_OVERFLOW, !!(WDT_OVERFLOW & config->intr_bitmap));
sdrv_wdt_set_internal_reset(base, &config->int_rst);
sdrv_wdt_set_external_reset(base, &config->ext_rst);
if(sdrv_wdt_set_timeout(base, config->timeout) < 0){
return SDRV_STATUS_TIMEOUT_FAILED;
};
if(sdrv_wdt_set_window_low(base, config->window_low) < 0){
return SDRV_STATUS_WINDOW_LOW_FAILED;
};
if(sdrv_wdt_set_sequence_delta(base, config->seq_delta) < 0){
return SDRV_STATUS_DELTA_FAILED;
};
return SDRV_STATUS_OK;
}
/**
* @brief De-Initialize watchdog.
*
* This function disable watchdog interrupt and clear interrupt status.
* Disable internal and external reset config.
*
* @param [in] base WDT control base.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed,
* SDRV_STATUS_INTR_UNCLEARED represents interrupt not cleared.
*/
status_t sdrv_wdt_deinit(sdrv_wdt_t *base)
{
sdrv_wdt_set_intr_enable(base, WDT_ILL_WINDOW_REFRESH, false);
sdrv_wdt_set_intr_enable(base, WDT_ILL_SEQUENCE_RESRESH, false);
sdrv_wdt_set_intr_enable(base, WDT_OVERFLOW, false);
if(sdrv_wdt_clear_intr_status(base, WDT_ILL_WINDOW_REFRESH) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
if(sdrv_wdt_clear_intr_status(base, WDT_ILL_SEQUENCE_RESRESH) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
if(sdrv_wdt_clear_intr_status(base, WDT_OVERFLOW) < 0){
return SDRV_STATUS_INTR_UNCLEARED;
};
base->rst_ctl &= ~(0x1u << WDT_RST_CTRL_INT_RST_EN);
base->ext_rst_ctl &= ~(0x1u << WDT_EXT_RST_CTRL_EXT_RST_EN);
return SDRV_STATUS_OK;
}
/**
* @brief Enable watchdog.
*
* This function enable watchdog timer counter.
*
* @param [in] base WDT control base.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_enable(sdrv_wdt_t *base)
{
base->ctrl |= (0x1u << WDT_CTRL_WDT_EN);
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_WDT_EN_STA, 1, 1)) {
return SDRV_STATUS_FAIL;
}
base->ctrl |= (0x1u << WDT_CTRL_SOFT_RST);
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_SOFT_RST, 1, 0)) {
return SDRV_STATUS_FAIL;
}
return SDRV_STATUS_OK;
}
/**
* @brief Disable watchdog.
*
* This function disable watchdog timer counter.
*
* @param [in] base WDT control base
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_disable(sdrv_wdt_t *base)
{
base->ctrl |= (0x1u << WDT_CTRL_SOFT_RST);
if (!sdrv_wdt_reg_poll(&base->ctrl, WDT_CTRL_SOFT_RST, 1, 0)) {
return SDRV_STATUS_FAIL;
}
base->ctrl &= ~(0x1u << WDT_CTRL_WDT_EN);
return SDRV_STATUS_OK;
}
/**
* @brief Set watchdog timeout value.
*
* This function get timeout value in milliseconds and convert to timer counter according
* to clock source pre-configed.
*
* @param [in] base WDT control base.
* @param [in] timeout unit: ms.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed,
* SDRV_STATUS_INVALID_PARAM represents invalid paramemt.
*/
status_t sdrv_wdt_set_timeout(sdrv_wdt_t *base, uint32_t timeout)
{
uint32_t count;
if(timeout > 0){
count = sdrv_wdt_get_counter_from_ms(base, timeout);
}
else{
return SDRV_STATUS_INVALID_PARAM;
}
sdrv_wdt_refresh(base);
base->wtc = 0;
base->wtc |= count;
sdrv_wdt_refresh(base);
return SDRV_STATUS_OK;
}
/**
* @brief Set watchdog time window low limit value.
*
* This function get time window low limit value in milliseconds and convert to timer
* counter according to clock source pre-configed.
*
* @param [in] base WDT control base.
* @param [in] low_limit unit: ms.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed,
* SDRV_STATUS_INVALID_PARAM represents invalid paramemt.
*/
status_t sdrv_wdt_set_window_low(sdrv_wdt_t *base, uint32_t low_limit)
{
uint32_t count;
if(low_limit > 0){
count = sdrv_wdt_get_counter_from_ms(base, low_limit);
}
else{
return SDRV_STATUS_INVALID_PARAM;
}
base->wrc_val = 0;
base->wrc_val |= count;
return SDRV_STATUS_OK;
}
/**
* @brief Set watchdog sequence delta value.
*
* This function get sequence delta value in milliseconds and convert to timer
* counter according to clock source pre-configed.
*
* @param [in] base WDT control base.
* @param [in] delta unit: ms.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed,
* SDRV_STATUS_INVALID_PARAM represents invalid paramemt.
*/
status_t sdrv_wdt_set_sequence_delta(sdrv_wdt_t *base, uint32_t delta)
{
uint32_t count;
if(delta > 0){
count = sdrv_wdt_get_counter_from_ms(base, delta);
}
else{
return SDRV_STATUS_INVALID_PARAM;
}
base->wrc_seq = 0;
base->wrc_seq |= count;
return SDRV_STATUS_OK;
}
/**
* @brief Feed watchdog.
*
* This function trigger watchdog do refresh.
*
* @param [in] base WDT control base.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_refresh(sdrv_wdt_t *base)
{
base->wrc_ctrl |= (0x1u << WDT_WRC_CTRL_REFR_TRIG);
return SDRV_STATUS_OK;
}
/**
* @brief Read watchdog current counter.
*
* This function read watchdog current counter. When you enable sequence refresh, you need call
* this function first and store read value. Before time delta, use sdrv_wdt_write_timestamp with
* stored value. Otherwise, illeage sequential refresh interrupt status will set.
*
* @param [in] base WDT control base.
* @return current timer counter.
*/
uint32_t sdrv_wdt_read_timestamp(sdrv_wdt_t *base)
{
return base->cnt;
}
/**
* @brief Write watchdog counter to TSW register.
*
* This function write counter to TSW register. When config sequence base refresh,
* read timestamp first, then write read value to TSW before sequence delta.
*
* @param [in] base WDT control base.
* @param [in] timestamp last read wdt conunter.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_write_timestamp(sdrv_wdt_t *base, uint32_t timestamp)
{
bool int_en = false;
uint32_t clk = 0;
uint32_t us = 1;
clk = sdrv_wdt_get_counter_cycle(base);
if (clk) {
us = (uint32_t)(1000000 * 6 / clk) + 1;
}
if (base->intr & (0x1U << WDT_INT_ILL_SEQ_REFE_INT_EN)) {
int_en = true;
base->intr &= ~(0x1U << WDT_INT_ILL_SEQ_REFE_INT_EN);
}
base->tsw = timestamp;
/* delay at least 6 wdt counter cycle */
udelay(us);
if (base->intr & (0x1U << WDT_INT_ILL_SEQ_REFE_INT_STA)) {
base->intr |= (0x1U << WDT_INT_ILL_SEQ_REFE_INT_CLR);
base->tsw = timestamp + 1;
}
if (int_en) {
base->intr |= (0x1U << WDT_INT_ILL_SEQ_REFE_INT_EN);
}
return SDRV_STATUS_OK;
}
/**
* @brief Get watchdog interrupt status of specific type.
*
* This function get specific type interrupt status.
*
* @param [in] base WDT control base.
* @param [in] intr interrupt type defined in sdrv_wdt_intr_e.
* @return true represents status is set, false represents status not set.
*/
status_t sdrv_wdt_get_intr_status(sdrv_wdt_t *base, sdrv_wdt_intr_e intr)
{
uint32_t status_bit;
if (intr == WDT_ILL_WINDOW_REFRESH) {
status_bit = WDT_INT_ILL_WIN_REFE_INT_STA;
} else if (intr == WDT_ILL_SEQUENCE_RESRESH) {
status_bit = WDT_INT_ILL_SEQ_REFE_INT_STA;
} else if (intr == WDT_OVERFLOW) {
status_bit = WDT_INT_OVERFLOW_INT_STA;
} else {
return SDRV_STATUS_OK;
}
return (base->intr >> status_bit) & 0x1u;
}
/**
* @brief Clear watchdog interrupt status of specific type.
*
* @param [in] base WDT control base.
* @param [in] intr interrupt type defined in sdrv_wdt_intr_e.
* @return SDRV_STATUS_OK represents success, SDRV_STATUS_FAIL represents failed.
*/
status_t sdrv_wdt_clear_intr_status(sdrv_wdt_t *base, sdrv_wdt_intr_e intr)
{
uint32_t clear_bit;
if (intr == WDT_ILL_WINDOW_REFRESH) {
clear_bit = WDT_INT_ILL_WIN_REFE_INT_CLR;
} else if (intr == WDT_ILL_SEQUENCE_RESRESH) {
clear_bit = WDT_INT_ILL_SEQ_REFE_INT_CLR;
} else if (intr == WDT_OVERFLOW) {
clear_bit = WDT_INT_OVERFLOW_INT_CLR;
} else {
return SDRV_STATUS_FAIL;
}
base->intr |= (0x1u << clear_bit);
return SDRV_STATUS_OK;
}