blob: 4e3726fc01bbe45a7db0cf672afd4b8c8f5c3e1d [file] [log] [blame]
/*
* Copyright (C) 2020 Katsuhiro Suzuki
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Watchdog (WDT) Driver for SiFive Freedom
*/
#define DT_DRV_COMPAT sifive_wdt
#include <zephyr/kernel.h>
#include <soc.h>
#include <zephyr/drivers/watchdog.h>
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wdt_sifive);
#define WDOGCFG_SCALE_MAX 0xf
#define WDOGCFG_SCALE_SHIFT 0
#define WDOGCFG_SCALE_MASK (WDOGCFG_SCALE_MAX << WDOGCFG_SCALE_SHIFT)
#define WDOGCFG_RSTEN BIT(8)
#define WDOGCFG_ZEROCMP BIT(9)
#define WDOGCFG_ENALWAYS BIT(12)
#define WDOGCFG_COREAWAKE BIT(13)
#define WDOGCFG_IP0 BIT(28)
#define WDOGCMP_MAX 0xffff
#define WDOG_KEY 0x51f15e
#define WDOG_FEED 0xd09f00d
#define WDOG_CLK 32768
struct wdt_sifive_reg {
/* offset: 0x000 */
uint32_t wdogcfg;
uint32_t dummy0;
uint32_t wdogcount;
uint32_t dummy1;
/* offset: 0x010 */
uint32_t wdogs;
uint32_t dummy2;
uint32_t wdogfeed;
uint32_t wdogkey;
/* offset: 0x020 */
uint32_t wdogcmp0;
};
struct wdt_sifive_device_config {
uintptr_t regs;
};
struct wdt_sifive_dev_data {
wdt_callback_t cb;
bool enable_cb;
bool timeout_valid;
};
#define DEV_REG(dev) \
((struct wdt_sifive_reg *) \
((const struct wdt_sifive_device_config *const)(dev)->config)->regs)
/**
* @brief Set maximum length of timeout to watchdog
*
* @param dev Watchdog device struct
*/
static void wdt_sifive_set_max_timeout(const struct device *dev)
{
volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
uint32_t t;
t = wdt->wdogcfg;
t |= WDOGCFG_SCALE_MASK;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcfg = t;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcmp0 = WDOGCMP_MAX;
}
static void wdt_sifive_isr(const struct device *dev)
{
volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
struct wdt_sifive_dev_data *data = dev->data;
uint32_t t;
wdt_sifive_set_max_timeout(dev);
t = wdt->wdogcfg;
t &= ~WDOGCFG_IP0;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcfg = t;
if (data->enable_cb && data->cb) {
data->enable_cb = false;
data->cb(dev, 0);
}
}
static int wdt_sifive_disable(const struct device *dev)
{
struct wdt_sifive_dev_data *data = dev->data;
wdt_sifive_set_max_timeout(dev);
data->enable_cb = false;
return 0;
}
static int wdt_sifive_setup(const struct device *dev, uint8_t options)
{
volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
struct wdt_sifive_dev_data *data = dev->data;
uint32_t t, mode;
if (!data->timeout_valid) {
LOG_ERR("No valid timeouts installed");
return -EINVAL;
}
mode = WDOGCFG_ENALWAYS;
if ((options & WDT_OPT_PAUSE_IN_SLEEP) ==
WDT_OPT_PAUSE_IN_SLEEP) {
mode = WDOGCFG_COREAWAKE;
}
if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) ==
WDT_OPT_PAUSE_HALTED_BY_DBG) {
mode = WDOGCFG_COREAWAKE;
}
t = wdt->wdogcfg;
t &= ~(WDOGCFG_ENALWAYS | WDOGCFG_COREAWAKE);
t |= mode;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcfg = t;
return 0;
}
/**
* @brief Calculates the watchdog counter value (wdogcmp0) and
* scaler (wdogscale) to be installed in the watchdog timer
*
* @param timeout Timeout value in milliseconds.
* @param clk Clock of watchdog in Hz.
* @param scaler Pointer to return scaler power of 2
*
* @return Watchdog counter value
*/
static int wdt_sifive_convtime(uint32_t timeout, int clk, int *scaler)
{
uint64_t cnt;
int i;
cnt = (uint64_t)timeout * clk / 1000;
for (i = 0; i < 16; i++) {
if (cnt <= WDOGCMP_MAX) {
break;
}
cnt >>= 1;
}
if (i == 16) {
/* Maximum counter and scaler */
LOG_ERR("Invalid timeout value allowed range");
*scaler = WDOGCFG_SCALE_MAX;
return WDOGCMP_MAX;
}
*scaler = i;
return cnt;
}
static int wdt_sifive_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
struct wdt_sifive_dev_data *data = dev->data;
uint32_t mode = 0, t;
int cmp, scaler;
if (data->timeout_valid) {
LOG_ERR("No more timeouts can be installed");
return -ENOMEM;
}
if (cfg->window.min != 0U || cfg->window.max == 0U) {
return -EINVAL;
}
/*
* Freedom watchdog does not support window timeout config.
* So use max field of window.
*/
cmp = wdt_sifive_convtime(cfg->window.max, WDOG_CLK, &scaler);
if (cmp < 0 || WDOGCMP_MAX < cmp) {
LOG_ERR("Unsupported watchdog timeout\n");
return -EINVAL;
}
switch (cfg->flags) {
case WDT_FLAG_RESET_SOC:
/* WDT supports global SoC reset but cannot callback. */
mode = WDOGCFG_RSTEN | WDOGCFG_ZEROCMP;
break;
case WDT_FLAG_RESET_NONE:
/* No reset */
mode = WDOGCFG_ZEROCMP;
break;
case WDT_FLAG_RESET_CPU_CORE:
default:
LOG_ERR("Unsupported watchdog config flags\n");
wdt_sifive_disable(dev);
return -ENOTSUP;
}
t = wdt->wdogcfg;
t &= ~(WDOGCFG_RSTEN | WDOGCFG_ZEROCMP | WDOGCFG_SCALE_MASK);
t |= mode | scaler;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcfg = t;
wdt->wdogkey = WDOG_KEY;
wdt->wdogcmp0 = cmp;
data->cb = cfg->callback;
data->enable_cb = true;
data->timeout_valid = true;
return 0;
}
static int wdt_sifive_feed(const struct device *dev, int channel_id)
{
volatile struct wdt_sifive_reg *wdt = DEV_REG(dev);
wdt->wdogkey = WDOG_KEY;
wdt->wdogfeed = WDOG_FEED;
return 0;
}
static const struct wdt_driver_api wdt_sifive_api = {
.setup = wdt_sifive_setup,
.disable = wdt_sifive_disable,
.install_timeout = wdt_sifive_install_timeout,
.feed = wdt_sifive_feed,
};
static void wdt_sifive_irq_config(void)
{
IRQ_CONNECT(DT_INST_IRQN(0),
DT_INST_IRQ(0, priority), wdt_sifive_isr,
DEVICE_DT_INST_GET(0), 0);
irq_enable(DT_INST_IRQN(0));
}
static int wdt_sifive_init(const struct device *dev)
{
#ifdef CONFIG_WDT_DISABLE_AT_BOOT
wdt_sifive_disable(dev);
#endif
wdt_sifive_irq_config();
return 0;
}
static struct wdt_sifive_dev_data wdt_sifive_data;
static const struct wdt_sifive_device_config wdt_sifive_cfg = {
.regs = DT_INST_REG_ADDR(0),
};
DEVICE_DT_INST_DEFINE(0, wdt_sifive_init, NULL,
&wdt_sifive_data, &wdt_sifive_cfg, PRE_KERNEL_1,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_sifive_api);