226 lines
5.2 KiB
C
226 lines
5.2 KiB
C
/*
|
|
* Copyright (c) 2022 TOKITA Hiroshi
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT gd_gd32_wwdgt
|
|
|
|
#include <zephyr/drivers/clock_control.h>
|
|
#include <zephyr/drivers/clock_control/gd32.h>
|
|
#include <zephyr/drivers/reset.h>
|
|
#include <zephyr/drivers/watchdog.h>
|
|
#include <zephyr/irq.h>
|
|
#include <zephyr/logging/log.h>
|
|
#include <zephyr/sys_clock.h>
|
|
|
|
#include <gd32_wwdgt.h>
|
|
|
|
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);
|