274 lines
6.3 KiB
C
274 lines
6.3 KiB
C
/*
|
|
* 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 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_INST_REG_ADDR(0),
|
|
};
|
|
|
|
DEVICE_DT_INST_DEFINE(0, wdt_init, NULL, &wdt_data, &wdt_config,
|
|
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
|
&tco_driver_api);
|