/* * Copyright (c) 2022 TOKITA Hiroshi * * SPDX-License-Identifier: Apache-2.0 */ #define DT_DRV_COMPAT gd_gd32_wwdgt #include #include #include #include #include #include #include #include LOG_MODULE_REGISTER(wdt_wwdgt_gd32, CONFIG_WDT_LOG_LEVEL); #define WWDGT_PRESCALER_EXP_MAX (3U) #define WWDGT_COUNTER_MIN (0x40U) #define WWDGT_COUNTER_MAX (0x7fU) #define WWDGT_INTERNAL_DIVIDER (4096ULL) struct gd32_wwdgt_config { uint16_t clkid; struct reset_dt_spec reset; }; /* mutable driver data */ struct gd32_wwdgt_data { /* counter update value*/ uint8_t counter; /* user defined callback */ wdt_callback_t callback; }; static void gd32_wwdgt_irq_config(const struct device *dev); /** * @param timeout Timeout value in milliseconds. * @param exp exponent part of prescaler * * @return ticks count calculated by this formula. * * timeout = pclk * INTERNAL_DIVIDER * (2^prescaler_exp) * (count + 1) * transform as * count = (timeout * pclk / INTERNAL_DIVIDER * (2^prescaler_exp) ) - 1 * * and add WWDGT_COUNTER_MIN to this as a offset value. */ static inline uint32_t gd32_wwdgt_calc_ticks(const struct device *dev, uint32_t timeout, uint32_t exp) { const struct gd32_wwdgt_config *config = dev->config; uint32_t pclk; (void)clock_control_get_rate(GD32_CLOCK_CONTROLLER, (clock_control_subsys_t)&config->clkid, &pclk); return ((timeout * pclk) / (WWDGT_INTERNAL_DIVIDER * (1 << exp) * MSEC_PER_SEC) - 1) + WWDGT_COUNTER_MIN; } /** * @brief Calculates WWDGT config value from timeout window. * * @param win Pointer to timeout window struct. * @param counter Pointer to the storage of counter value. * @param wval Pointer to the storage of window value. * @param prescaler Pointer to the storage of prescaler value. * * @return 0 on success, -EINVAL if the window-max is out of range */ static int gd32_wwdgt_calc_window(const struct device *dev, const struct wdt_window *win, uint32_t *counter, uint32_t *wval, uint32_t *prescaler) { for (uint32_t shift = 0U; shift <= WWDGT_PRESCALER_EXP_MAX; shift++) { uint32_t max_count = gd32_wwdgt_calc_ticks(dev, win->max, shift); if (max_count <= WWDGT_COUNTER_MAX) { *counter = max_count; *prescaler = CFG_PSC(shift); if (win->min == 0U) { *wval = max_count; } else { *wval = gd32_wwdgt_calc_ticks(dev, win->min, shift); } return 0; } } return -EINVAL; } static int gd32_wwdgt_setup(const struct device *dev, uint8_t options) { ARG_UNUSED(dev); if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { #if CONFIG_GD32_DBG_SUPPORT dbg_periph_enable(DBG_WWDGT_HOLD); #else LOG_ERR("Debug support not enabled"); return -ENOTSUP; #endif } if (options & WDT_OPT_PAUSE_IN_SLEEP) { LOG_ERR("WDT_OPT_PAUSE_IN_SLEEP not supported"); return -ENOTSUP; } wwdgt_enable(); wwdgt_flag_clear(); wwdgt_interrupt_enable(); return 0; } static int gd32_wwdgt_disable(const struct device *dev) { /* watchdog cannot be stopped once started */ ARG_UNUSED(dev); return -EPERM; } static int gd32_wwdgt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *config) { uint32_t prescaler = 0U; uint32_t counter = 0U; uint32_t window = 0U; struct gd32_wwdgt_data *data = dev->data; if (config->window.max == 0U) { LOG_ERR("window.max must be non-zero"); return -EINVAL; } if (gd32_wwdgt_calc_window(dev, &config->window, &counter, &window, &prescaler) != 0) { LOG_ERR("window.max in out of range"); return -EINVAL; } data->callback = config->callback; data->counter = counter; wwdgt_config(counter, window, prescaler); return 0; } static int gd32_wwdgt_feed(const struct device *dev, int channel_id) { struct gd32_wwdgt_data *data = dev->data; ARG_UNUSED(channel_id); wwdgt_counter_update(data->counter); return 0; } static void gd32_wwdgt_isr(const struct device *dev) { struct gd32_wwdgt_data *data = dev->data; if (wwdgt_flag_get() != 0) { wwdgt_flag_clear(); if (data->callback != NULL) { data->callback(dev, 0); } } } static void gd32_wwdgt_irq_config(const struct device *dev) { ARG_UNUSED(dev); IRQ_CONNECT(DT_INST_IRQN(0), DT_INST_IRQ(0, priority), gd32_wwdgt_isr, DEVICE_DT_INST_GET(0), 0); irq_enable(DT_INST_IRQN(0)); } static const struct wdt_driver_api wwdgt_gd32_api = { .setup = gd32_wwdgt_setup, .disable = gd32_wwdgt_disable, .install_timeout = gd32_wwdgt_install_timeout, .feed = gd32_wwdgt_feed, }; static int gd32_wwdgt_init(const struct device *dev) { const struct gd32_wwdgt_config *config = dev->config; (void)clock_control_on(GD32_CLOCK_CONTROLLER, (clock_control_subsys_t)&config->clkid); (void)reset_line_toggle_dt(&config->reset); gd32_wwdgt_irq_config(dev); return 0; } static const struct gd32_wwdgt_config wwdgt_cfg = { .clkid = DT_INST_CLOCKS_CELL(0, id), .reset = RESET_DT_SPEC_INST_GET(0), }; static struct gd32_wwdgt_data wwdgt_data = { .counter = WWDGT_COUNTER_MIN, .callback = NULL }; DEVICE_DT_INST_DEFINE(0, gd32_wwdgt_init, NULL, &wwdgt_data, &wwdgt_cfg, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wwdgt_gd32_api);