blob: 5fca002680502cf5578a277a526983f341d08779 [file] [log] [blame]
/*
* Copyright (c) 2023 Andes Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT andestech_atcwdt200
#include <zephyr/kernel.h>
#include <zephyr/drivers/watchdog.h>
#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL
#include <zephyr/logging/log.h>
#include <zephyr/irq.h>
#include <zephyr/drivers/counter.h>
#include <zephyr/drivers/syscon.h>
LOG_MODULE_REGISTER(wdt_andes);
/* Watchdog register */
#define REG_IDR 0x00
#define REG_CTRL 0x10
#define REG_RESTAR 0x14
#define REG_WREN 0x18
#define REG_STATUS 0x1c
#define WDT_CTRL(addr) (addr + REG_CTRL)
#define WDT_RESTAR(addr) (addr + REG_RESTAR)
#define WDT_WREN(addr) (addr + REG_WREN)
#define WDT_STATUS(addr) (addr + REG_STATUS)
/* Atcwdt200 magic number */
/* 0x10 Control Register */
#define WDT_CTRL_RSTTIME_POW_2_7 0x000
#define WDT_CTRL_RSTTIME_POW_2_8 0x100
#define WDT_CTRL_RSTTIME_POW_2_9 0x200
#define WDT_CTRL_RSTTIME_POW_2_10 0x300
#define WDT_CTRL_RSTTIME_POW_2_11 0x400
#define WDT_CTRL_RSTTIME_POW_2_12 0x500
#define WDT_CTRL_RSTTIME_POW_2_13 0x600
#define WDT_CTRL_RSTTIME_POW_2_14 0x700
#define WDT_CTRL_INTTIME_POW_2_6 0x000
#define WDT_CTRL_INTTIME_POW_2_8 0x010
#define WDT_CTRL_INTTIME_POW_2_10 0x020
#define WDT_CTRL_INTTIME_POW_2_11 0x030
#define WDT_CTRL_INTTIME_POW_2_12 0x040
#define WDT_CTRL_INTTIME_POW_2_13 0x050
#define WDT_CTRL_INTTIME_POW_2_14 0x060
#define WDT_CTRL_INTTIME_POW_2_15 0x070
#define WDT_CTRL_INTTIME_POW_2_17 0x080
#define WDT_CTRL_INTTIME_POW_2_19 0x090
#define WDT_CTRL_INTTIME_POW_2_21 0x0A0
#define WDT_CTRL_INTTIME_POW_2_23 0x0B0
#define WDT_CTRL_INTTIME_POW_2_25 0x0C0
#define WDT_CTRL_INTTIME_POW_2_27 0x0D0
#define WDT_CTRL_INTTIME_POW_2_29 0x0E0
#define WDT_CTRL_INTTIME_POW_2_31 0x0F0
#define WDT_CTRL_RSTEN 0x8
#define WDT_CTRL_INTEN 0x4
#define WDT_CTRL_APBCLK 0x2
#define WDT_CTRL_EXTCLK 0x0
#define WDT_CTRL_EN 0x1
/* Magic Number for Restart Register */
#define WDT_RESTART_NUM 0xcafe
/* Magic Number for Write Enable Register */
#define WDT_WREN_NUM 0x5aa5
/* 0x1C Status Register */
#define WDT_ST_INTEXPIRED 0x1
#define WDT_ST_INTEXPIRED_CLR 0x1
/*
* SMU(System Management Unit) Registers for hwinfo driver
*/
/* Register offset*/
#define SMU_RESET_WRSR 0x10
#define SMU_RESET_REGHI 0x60
#define SMU_RESET_REGLO 0x50
#define SMU_CMD 0x14
#define SMU_RESET_CMD 0x3c
#define WDOGCFG_PERIOD_MIN BIT(7)
#define WDOGCFG_PERIOD_MAX BIT(14)
#define EXT_CLOCK_FREQ BIT(15)
static const struct device *const syscon_dev =
DEVICE_DT_GET(DT_NODELABEL(syscon));
static const struct device *const pit_counter_dev =
DEVICE_DT_GET(DT_NODELABEL(pit0));
struct counter_alarm_cfg alarm_cfg;
struct wdt_atcwdt200_config {
uintptr_t base;
};
struct wdt_atcwdt200_dev_data {
bool timeout_valid;
counter_alarm_callback_t counter_callback;
struct k_spinlock lock;
};
static int wdt_atcwdt200_disable(const struct device *dev);
static void wdt_counter_cb(const struct device *counter_dev, uint8_t chan_id,
uint32_t counter,
void *user_data)
{
const struct device *dev = DEVICE_DT_INST_GET(0);
struct wdt_atcwdt200_dev_data *wdt_data = dev->data;
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
k_spinlock_key_t key;
key = k_spin_lock(&wdt_data->lock);
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(WDT_RESTART_NUM, WDT_RESTAR(wdt_addr));
counter_set_channel_alarm(counter_dev, 2, &alarm_cfg);
k_spin_unlock(&wdt_data->lock, key);
}
/**
* @brief Set maximum length of timeout to watchdog
*
* @param dev Watchdog device struct
*/
static void wdt_atcwdt200_set_max_timeout(const struct device *dev)
{
struct wdt_atcwdt200_dev_data *data = dev->data;
k_spinlock_key_t key;
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
uint32_t reg, counter_freq;
key = k_spin_lock(&data->lock);
counter_freq = counter_get_frequency(pit_counter_dev);
alarm_cfg.flags = 0;
alarm_cfg.callback = wdt_counter_cb;
alarm_cfg.user_data = &alarm_cfg;
alarm_cfg.ticks = ((WDOGCFG_PERIOD_MAX * counter_freq) / EXT_CLOCK_FREQ) >> 1;
reg = WDT_CTRL_RSTTIME_POW_2_14;
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(reg, WDT_CTRL(wdt_addr));
data->timeout_valid = true;
k_spin_unlock(&data->lock, key);
}
static int wdt_atcwdt200_disable(const struct device *dev)
{
struct wdt_atcwdt200_dev_data *data = dev->data;
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
k_spinlock_key_t key;
uint32_t reg;
key = k_spin_lock(&data->lock);
reg = sys_read32(WDT_CTRL(wdt_addr));
reg &= ~(WDT_CTRL_RSTEN | WDT_CTRL_EN);
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(reg, WDT_CTRL(wdt_addr));
k_spin_unlock(&data->lock, key);
wdt_atcwdt200_set_max_timeout(dev);
counter_cancel_channel_alarm(pit_counter_dev, 2);
return 0;
}
static int wdt_atcwdt200_setup(const struct device *dev, uint8_t options)
{
struct wdt_atcwdt200_dev_data *data = dev->data;
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
k_spinlock_key_t key;
uint32_t reg;
uint32_t ret = 0;
if (!data->timeout_valid) {
LOG_ERR("No valid timeouts installed");
return -EINVAL;
}
key = k_spin_lock(&data->lock);
reg = sys_read32(WDT_CTRL(wdt_addr));
reg |= (WDT_CTRL_RSTEN | WDT_CTRL_EN);
if ((options & WDT_OPT_PAUSE_HALTED_BY_DBG) ==
WDT_OPT_PAUSE_HALTED_BY_DBG) {
counter_cancel_channel_alarm(pit_counter_dev, 2);
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(reg, WDT_CTRL(wdt_addr));
goto out;
} else {
ret = counter_set_channel_alarm(pit_counter_dev, 2, &alarm_cfg);
if (ret != 0) {
ret = -EINVAL;
goto out;
}
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(reg, WDT_CTRL(wdt_addr));
}
out:
k_spin_unlock(&data->lock, key);
return ret;
}
/**
* @brief Calculates the watchdog counter value (wdogcmp0) and
* scaler (wdogscale) to be installed in the watchdog timer
*
* @param timeout Timeout value in milliseconds.
* @param scaler Pointer to return scaler power of 2
*
* @return Watchdog counter value
*/
static uint32_t wdt_atcwdt200_convtime(uint32_t timeout, uint32_t *scaler)
{
int i;
uint32_t rst_period, cnt;
cnt = (uint32_t)((timeout * EXT_CLOCK_FREQ) / 1000);
rst_period = cnt;
for (i = 0; i < 14 && cnt > 0; i++) {
cnt >>= 1;
}
*scaler = i;
return rst_period;
}
static int wdt_atcwdt200_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
struct wdt_atcwdt200_dev_data *data = dev->data;
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
k_spinlock_key_t key;
uint32_t rst_period, reg, counter_freq, scaler;
if (cfg->window.min != 0U || cfg->window.max == 0U) {
return -EINVAL;
}
counter_freq = counter_get_frequency(pit_counter_dev);
rst_period = wdt_atcwdt200_convtime(cfg->window.max, &scaler);
if (rst_period < 0 || WDOGCFG_PERIOD_MAX < rst_period) {
LOG_ERR("Unsupported watchdog timeout\n");
return -EINVAL;
}
wdt_atcwdt200_disable(dev);
key = k_spin_lock(&data->lock);
switch (cfg->flags) {
case WDT_FLAG_RESET_SOC:
if (scaler < 7) {
reg = WDT_CTRL_RSTTIME_POW_2_7;
} else {
scaler = scaler - 7;
reg = scaler << 8;
}
alarm_cfg.flags = 0;
alarm_cfg.callback = wdt_counter_cb;
alarm_cfg.user_data = &alarm_cfg;
alarm_cfg.ticks = (((cfg->window.max * counter_freq) / 1000) >> 1);
break;
case WDT_FLAG_RESET_NONE:
case WDT_FLAG_RESET_CPU_CORE:
default:
LOG_ERR("Unsupported watchdog config flags\n");
k_spin_unlock(&data->lock, key);
return -ENOTSUP;
}
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(reg, WDT_CTRL(wdt_addr));
k_spin_unlock(&data->lock, key);
return 0;
}
static int wdt_atcwdt200_feed(const struct device *dev, int channel_id)
{
uint32_t wdt_addr = ((const struct wdt_atcwdt200_config *)(dev->config))->base;
ARG_UNUSED(channel_id);
sys_write32(WDT_WREN_NUM, WDT_WREN(wdt_addr));
sys_write32(WDT_RESTART_NUM, WDT_RESTAR(wdt_addr));
return 0;
}
static const struct wdt_driver_api wdt_atcwdt200_api = {
.setup = wdt_atcwdt200_setup,
.disable = wdt_atcwdt200_disable,
.install_timeout = wdt_atcwdt200_install_timeout,
.feed = wdt_atcwdt200_feed,
};
static int wdt_atcwdt200_init(const struct device *dev)
{
struct wdt_atcwdt200_dev_data *data = dev->data;
data->timeout_valid = false;
data->counter_callback = wdt_counter_cb;
uint32_t ret;
counter_start(pit_counter_dev);
ret = syscon_write_reg(syscon_dev, SMU_RESET_REGLO,
((uint32_t)((unsigned long)
Z_MEM_PHYS_ADDR(CONFIG_KERNEL_ENTRY))));
if (ret < 0) {
return -EINVAL;
}
ret = syscon_write_reg(syscon_dev, SMU_RESET_REGHI,
((uint32_t)((uint64_t)((unsigned long)
Z_MEM_PHYS_ADDR(CONFIG_KERNEL_ENTRY)) >> 32)));
if (ret < 0) {
return -EINVAL;
}
#ifdef CONFIG_WDT_DISABLE_AT_BOOT
wdt_atcwdt200_disable(dev);
#else
data->timeout_valid = true;
wdt_atcwdt200_set_max_timeout(dev);
wdt_atcwdt200_setup(dev, 0x0);
#endif
return 0;
}
static struct wdt_atcwdt200_dev_data wdt_atcwdt200_data;
static const struct wdt_atcwdt200_config wdt_atcwdt200_cfg = {
.base = DT_INST_REG_ADDR(0),
};
DEVICE_DT_INST_DEFINE(0, wdt_atcwdt200_init, NULL,
&wdt_atcwdt200_data, &wdt_atcwdt200_cfg, PRE_KERNEL_2,
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_atcwdt200_api);