blob: 4da0813ed5cb4c0a375fbadf720f6891db771d91 [file] [log] [blame]
/*
* Copyright (c) 2025 Silicon Laboratories Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/irq.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/sys/util.h>
#include <zephyr/sys/bitarray.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/drivers/clock_control.h>
#include <math.h>
#include "rsi_wwdt.h"
#include "rsi_sysrtc.h"
#define DT_DRV_COMPAT silabs_siwx91x_wdt
#define SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK 0x0000001F
struct siwx91x_wdt_config {
/* WDT register base address */
MCU_WDT_Type *reg;
/* Pointer to the clock device structure */
const struct device *clock_dev;
/* Clock control subsystem */
clock_control_subsys_t clock_subsys;
/* Function pointer for the IRQ (Interrupt Request) configuration */
void (*irq_config)(void);
};
struct siwx91x_wdt_data {
/* Callback function to be called on watchdog timer events */
wdt_callback_t callback;
/* WDT operating clock (LF-FSM) frequency */
uint32_t clock_frequency;
/* Timer system reset duration in ms */
uint8_t delay_reset;
/* Timer interrupt duration in ms */
uint8_t delay_irq;
/* Flag indicating the timeout install status */
bool timeout_install_status;
/* Flag indicating the setup status */
bool setup_status;
};
/* Function to get the delay in milliseconds from the register value */
static uint32_t siwx91x_wdt_delay_from_hw(uint8_t value, int clock_frequency)
{
uint32_t ticks = BIT(value);
float timeout = (float)ticks / clock_frequency;
timeout *= 1000;
/* Return the timeout value as an unsigned 32-bit integer in milliseconds */
return (uint32_t)timeout;
}
/* Function to get the register value from the delay in milliseconds */
static uint8_t siwx91x_wdt_delay_to_hw(uint32_t delay, int clock_frequency)
{
/* reg_value = log((timeout * clock_frequency)/1000)base2 */
float value = ((float)delay * (float)clock_frequency) / 1000;
float result = log2f(value);
/* Round the result to nearest integer */
result = roundf(result);
return (uint8_t)result;
}
static int siwx91x_wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
{
struct siwx91x_wdt_data *data = dev->data;
/* Check the WDT setup status */
if (data->setup_status) {
/* WDT setup is already done */
return -EBUSY;
}
/* Check the WDT timeout status */
if (data->timeout_install_status) {
/* Only single timeout can be installed */
return -ENOMEM;
}
if (cfg->window.max > siwx91x_wdt_delay_from_hw(SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK,
data->clock_frequency) ||
cfg->window.max == 0) {
/* Requested value is out of range */
return -EINVAL;
}
if (cfg->window.min > 0) {
/* This feature is currently not supported */
return -ENOTSUP;
}
switch (cfg->flags) {
case WDT_FLAG_RESET_SOC:
case WDT_FLAG_RESET_CPU_CORE:
if (cfg->callback != NULL) {
/* Callback is not supported for reset flags */
return -ENOTSUP;
}
data->delay_reset = siwx91x_wdt_delay_to_hw(cfg->window.max, data->clock_frequency);
/* During a system or CPU core reset, interrupts are not needed. Thus, we set
* the interrupt time to 0 to ensure no interrupts occur while resetting.
*/
data->delay_irq = 0;
/* Mask the WWDT interrupt */
RSI_WWDT_IntrMask();
break;
case WDT_FLAG_RESET_NONE:
/* Set the reset time to maximum value */
data->delay_reset = SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK;
data->delay_irq = siwx91x_wdt_delay_to_hw(cfg->window.max, data->clock_frequency);
if (cfg->callback != NULL) {
data->callback = cfg->callback;
}
break;
default:
/* Unsupported WDT config options */
return -ENOTSUP;
}
data->timeout_install_status = true;
return 0;
}
/* Function to setup and start WDT */
static int siwx91x_wdt_setup(const struct device *dev, uint8_t options)
{
const struct siwx91x_wdt_config *config = dev->config;
struct siwx91x_wdt_data *data = dev->data;
/* Check the WDT setup status */
if (data->setup_status) {
/* WDT is already running */
return -EBUSY;
}
/* Check the WDT timeout status */
if (!data->timeout_install_status) {
/* Timeout need to be set before setup */
return -ENOTSUP;
}
if (options & (WDT_OPT_PAUSE_IN_SLEEP)) {
return -ENOTSUP;
}
RSI_WWDT_ConfigSysRstTimer(config->reg, data->delay_reset);
RSI_WWDT_ConfigIntrTimer(config->reg, data->delay_irq);
RSI_WWDT_Start(config->reg);
data->setup_status = true;
return 0;
}
static int siwx91x_wdt_disable(const struct device *dev)
{
const struct siwx91x_wdt_config *config = dev->config;
struct siwx91x_wdt_data *data = dev->data;
if (!data->timeout_install_status) {
/* No timeout installed */
return -EFAULT;
}
RSI_WWDT_Disable(config->reg);
data->timeout_install_status = false;
data->setup_status = false;
return 0;
}
static int siwx91x_wdt_feed(const struct device *dev, int channel_id)
{
const struct siwx91x_wdt_config *config = dev->config;
struct siwx91x_wdt_data *data = dev->data;
if (!(data->timeout_install_status && data->setup_status)) {
/* WDT is not configured */
return -EINVAL;
}
if (channel_id != 0) {
/* Channel id must be 0 */
return -EINVAL;
}
RSI_WWDT_ReStart(config->reg);
return 0;
}
static void siwx91x_wdt_isr(const struct device *dev)
{
const struct siwx91x_wdt_config *config = dev->config;
struct siwx91x_wdt_data *data = dev->data;
/* Clear WDT interrupt */
RSI_WWDT_IntrClear();
if (data->delay_irq) {
/* Restart the timer */
RSI_WWDT_ReStart(config->reg);
}
if (data->callback != NULL) {
data->callback(dev, 0);
}
}
static int siwx91x_wdt_init(const struct device *dev)
{
const struct siwx91x_wdt_config *config = dev->config;
struct siwx91x_wdt_data *data = dev->data;
int ret;
ret = clock_control_on(config->clock_dev, config->clock_subsys);
if (ret) {
return ret;
}
ret = clock_control_get_rate(config->clock_dev, config->clock_subsys,
&data->clock_frequency);
if (ret) {
return ret;
}
RSI_WWDT_Init(config->reg);
config->irq_config();
RSI_WWDT_IntrUnMask();
return 0;
}
static DEVICE_API(wdt, siwx91x_wdt_driver_api) = {
.setup = siwx91x_wdt_setup,
.disable = siwx91x_wdt_disable,
.install_timeout = siwx91x_wdt_install_timeout,
.feed = siwx91x_wdt_feed,
};
#define siwx91x_WDT_INIT(inst) \
static struct siwx91x_wdt_data siwx91x_wdt_data_##inst; \
static void siwx91x_wdt_irq_configure_##inst(void) \
{ \
IRQ_CONNECT(DT_INST_IRQ(inst, irq), DT_INST_IRQ(inst, priority), siwx91x_wdt_isr, \
DEVICE_DT_INST_GET(inst), 0); \
irq_enable(DT_INST_IRQ(inst, irq)); \
} \
static const struct siwx91x_wdt_config siwx91x_wdt_config_##inst = { \
.reg = (MCU_WDT_Type *)DT_INST_REG_ADDR(inst), \
.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)), \
.clock_subsys = (clock_control_subsys_t)DT_INST_PHA(inst, clocks, clkid), \
.irq_config = siwx91x_wdt_irq_configure_##inst, \
}; \
DEVICE_DT_INST_DEFINE(inst, &siwx91x_wdt_init, NULL, &siwx91x_wdt_data_##inst, \
&siwx91x_wdt_config_##inst, PRE_KERNEL_1, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &siwx91x_wdt_driver_api);
DT_INST_FOREACH_STATUS_OKAY(siwx91x_WDT_INIT)