blob: bc0e95386f2c5ed3b262148754b298e04b262cd5 [file] [log] [blame] [edit]
/*
* Copyright (c) 2022 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT intel_tco_wdt
#include <zephyr/kernel.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wdt_tco, CONFIG_WDT_LOG_LEVEL);
#define TCO_WDT_NODE DT_NODELABEL(tco_wdt)
#define BASE(d) ((struct tco_config *)(d)->config)->base
#define TCO_RLD(d) (BASE(d) + 0x00) /* TCO Timer Reload/Curr. Value */
#define TCO_DAT_IN(d) (BASE(d) + 0x02) /* TCO Data In Register */
#define TCO_DAT_OUT(d) (BASE(d) + 0x03) /* TCO Data Out Register */
#define TCO1_STS(d) (BASE(d) + 0x04) /* TCO1 Status Register */
#define TCO2_STS(d) (BASE(d) + 0x06) /* TCO2 Status Register */
#define TCO1_CNT(d) (BASE(d) + 0x08) /* TCO1 Control Register */
#define TCO2_CNT(d) (BASE(d) + 0x0a) /* TCO2 Control Register */
#define TCO_MSG(d) (BASE(d) + 0x0c) /* TCO Message Registers */
#define TCO_WDSTATUS(d) (BASE(d) + 0x0e) /* TCO Watchdog Status Register */
#define TCO_TMR(d) (BASE(d) + 0x12) /* TCO Timer Register */
/* TCO1_STS bits */
#define STS_NMI2SMI BIT(0)
#define STS_OS_TCO_SMI BIT(1)
#define STS_TCO_INT BIT(2)
#define STS_TIMEOUT BIT(3)
#define STS_NEWCENTURY BIT(7)
#define STS_BIOSWR BIT(8)
#define STS_CPUSCI BIT(9)
#define STS_CPUSMI BIT(10)
#define STS_CPUSERR BIT(12)
#define STS_SLVSEL BIT(13)
/* TCO2_STS bits */
#define STS_INTRD_DET BIT(0)
#define STS_SECOND_TO BIT(1)
#define STS_NRSTRAP BIT(2)
#define STS_SMLINK_SLAVE_SMI BIT(3)
/* TCO1_CNT bits */
#define CNT_NR_MSUS BIT(0)
#define CNT_NMI_NOW BIT(8)
#define CNT_NMI2SMI_EN BIT(9)
#define CNT_TCO_TMR_HALT BIT(11)
#define CNT_TCO_LOCK BIT(12)
/* TCO_TMR bits */
#define TMR_TCOTMR BIT_MASK(10)
#define TMR_MIN 0x04
#define TMR_MAX 0x3f
struct tco_data {
struct k_spinlock lock;
bool no_reboot;
};
struct tco_config {
io_port_t base;
};
static int set_no_reboot(const struct device *dev, bool set)
{
uint16_t val, newval;
val = sys_in16(TCO1_CNT(dev));
if (set) {
val |= CNT_NR_MSUS;
} else {
val &= ~CNT_NR_MSUS;
}
sys_out16(val, TCO1_CNT(dev));
newval = sys_in16(TCO1_CNT(dev));
if (val != newval) {
return -EIO;
}
return 0;
}
static int tco_setup(const struct device *dev, uint8_t options)
{
struct tco_data *data = dev->data;
k_spinlock_key_t key;
uint16_t val;
int err;
key = k_spin_lock(&data->lock);
err = set_no_reboot(dev, data->no_reboot);
if (err) {
k_spin_unlock(&data->lock, key);
LOG_ERR("Failed to update no_reboot bit (err %d)", err);
return err;
}
/* Reload the timer */
sys_out16(0x01, TCO_RLD(dev));
/* Enable the timer to start counting by clearing the TCO_TMR_HALT field */
val = sys_in16(TCO1_CNT(dev));
val &= ~CNT_TCO_TMR_HALT;
sys_out16(val, TCO1_CNT(dev));
val = sys_in16(TCO1_CNT(dev));
k_spin_unlock(&data->lock, key);
if ((val & CNT_TCO_TMR_HALT) == CNT_TCO_TMR_HALT) {
return -EIO;
}
return 0;
}
static int tco_disable(const struct device *dev)
{
struct tco_data *data = dev->data;
k_spinlock_key_t key;
uint16_t val;
key = k_spin_lock(&data->lock);
/* Set the TCO_TMR_HALT field so that the timer gets halted */
val = sys_in16(TCO1_CNT(dev));
val |= CNT_TCO_TMR_HALT;
sys_out16(val, TCO1_CNT(dev));
val = sys_in16(TCO1_CNT(dev));
set_no_reboot(dev, true);
k_spin_unlock(&data->lock, key);
if ((val & CNT_TCO_TMR_HALT) == 0) {
return -EIO;
}
return 0;
}
static uint16_t msec_to_ticks(uint32_t msec)
{
/* Convert from milliseconds to timer ticks. The timer is clocked at
* approximately 0.6 seconds.
*/
return ((msec / MSEC_PER_SEC) * 10) / 6;
}
static int tco_install_timeout(const struct device *dev,
const struct wdt_timeout_cfg *cfg)
{
struct tco_data *data = dev->data;
k_spinlock_key_t key;
uint16_t val, ticks;
/* TCO watchdog doesn't support windowed timeouts */
if (cfg->window.min != 0) {
return -EINVAL;
}
/* No callback support */
if (cfg->callback != NULL) {
return -ENOTSUP;
}
ticks = msec_to_ticks(cfg->window.max);
LOG_DBG("window.max %u -> ticks %u", cfg->window.max, ticks);
if (ticks < TMR_MIN || ticks > TMR_MAX) {
return -EINVAL;
}
switch (cfg->flags) {
case WDT_FLAG_RESET_SOC:
data->no_reboot = false;
break;
case WDT_FLAG_RESET_NONE:
data->no_reboot = true;
break;
case WDT_FLAG_RESET_CPU_CORE:
LOG_ERR("CPU-only reset not supported");
return -ENOTSUP;
default:
LOG_ERR("Unknown watchdog configuration flags");
return -EINVAL;
}
key = k_spin_lock(&data->lock);
/* Set the TCO_TMR field. This value is loaded into the timer each time
* the TCO_RLD register is written.
*/
val = sys_in16(TCO_TMR(dev));
val &= ~TMR_TCOTMR;
val |= ticks;
sys_out16(val, TCO_TMR(dev));
val = sys_in16(TCO_TMR(dev));
k_spin_unlock(&data->lock, key);
if ((val & TMR_TCOTMR) != ticks) {
LOG_ERR("val %u", val);
return -EIO;
}
return 0;
}
static int tco_feed(const struct device *dev, int channel_id)
{
struct tco_data *data = dev->data;
k_spinlock_key_t key;
key = k_spin_lock(&data->lock);
/* TCORLD: Writing any value to this register will reload the timer to
* prevent the timeout.
*/
sys_out16(0x01, TCO_RLD(dev));
k_spin_unlock(&data->lock, key);
return 0;
}
static const struct wdt_driver_api tco_driver_api = {
.setup = tco_setup,
.disable = tco_disable,
.install_timeout = tco_install_timeout,
.feed = tco_feed,
};
static int wdt_init(const struct device *dev)
{
const struct tco_config *config = dev->config;
struct tco_data *data = dev->data;
k_spinlock_key_t key;
LOG_DBG("Using 0x%04x as TCOBA", config->base);
key = k_spin_lock(&data->lock);
sys_out16(STS_TIMEOUT, TCO1_STS(dev)); /* Clear the Time Out Status bit */
sys_out16(STS_SECOND_TO, TCO2_STS(dev)); /* Clear SECOND_TO_STS bit */
set_no_reboot(dev, data->no_reboot);
k_spin_unlock(&data->lock, key);
if (IS_ENABLED(CONFIG_WDT_DISABLE_AT_BOOT)) {
tco_disable(dev);
}
return 0;
}
static struct tco_data wdt_data = {
};
static const struct tco_config wdt_config = {
.base = DT_REG_ADDR(TCO_WDT_NODE),
};
DEVICE_DT_DEFINE(TCO_WDT_NODE, wdt_init, NULL, &wdt_data, &wdt_config,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&tco_driver_api);