drivers: watchdog: Add Intel TCO Watchdog driver
This adds a basic Intel TCO watchdog driver. The driver doesn't support windowed timeouts (a non-zero window.min value) or callbacks. The driver currently assumes TCO version 6, which can be found e.g. on Elkhart Lake and Raptor Lake platforms. The driver also expects the TCOBA base address to be specified through DTS, rather than doing runtime lookup (using e.g. ACPI or PCIe). Signed-off-by: Johan Hedberg <johan.hedberg@intel.com>
This commit is contained in:
parent
a5057e96d1
commit
2fe4a4a218
|
@ -26,6 +26,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_RPI_PICO wdt_rpi_pico.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_WDT_SAM wdt_sam.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_SAM0 wdt_sam0.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_SIFIVE wdt_sifive.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_TCO wdt_tco.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_XEC wdt_mchp_xec.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_COUNTER wdt_counter.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_NXP_S32 wdt_nxp_s32.c)
|
||||
|
|
|
@ -100,4 +100,6 @@ source "drivers/watchdog/Kconfig.smartbond"
|
|||
|
||||
source "drivers/watchdog/Kconfig.ti_tps382x"
|
||||
|
||||
source "drivers/watchdog/Kconfig.tco"
|
||||
|
||||
endif # WATCHDOG
|
||||
|
|
11
drivers/watchdog/Kconfig.tco
Normal file
11
drivers/watchdog/Kconfig.tco
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Intel TCO WDT support
|
||||
|
||||
# Copyright (c) 2022 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config WDT_TCO
|
||||
bool "Intel TCO Watchdog driver"
|
||||
default y
|
||||
depends on DT_HAS_INTEL_TCO_WDT_ENABLED
|
||||
help
|
||||
Enable support for Intel TCO WDT driver.
|
275
drivers/watchdog/wdt_tco.c
Normal file
275
drivers/watchdog/wdt_tco.c
Normal file
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* 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,
|
||||
PRE_KERNEL_1, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
|
||||
&tco_driver_api);
|
8
dts/bindings/watchdog/intel,tco-wdt.yaml
Normal file
8
dts/bindings/watchdog/intel,tco-wdt.yaml
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Copyright (c) 2022 Intel Corporation
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: Intel TCO Watchdog driver binding
|
||||
|
||||
compatible: "intel,tco-wdt"
|
||||
|
||||
include: base.yaml
|
|
@ -413,5 +413,10 @@
|
|||
reg = <0x70 0x0D 0x71 0x0D>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
tco_wdt: tco_wdt@400 {
|
||||
compatible = "intel,tco-wdt";
|
||||
reg = <0x0400 0x20>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue