/** * @file sdrv_watchdog.c * @brief Sdrv watchdog driver source. * * @copyright Copyright (c) 2022 Semidrive Semiconductor. * All rights reserved. */ #include #include #include #include #include #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<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; }