From: Jan Kiszka <[email protected]> While the real hardware might be in the museum now, this allows to test watchdog integrations against QEMU virtual machines (-device i6300esb).
Signed-off-by: Jan Kiszka <[email protected]> --- drivers/watchdog/Kconfig | 9 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/i6300esb-wdt.c | 147 ++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 drivers/watchdog/i6300esb-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3c56c541505..225e23ce247 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -494,4 +494,13 @@ config WDT_FTWDT010 help Faraday Technology ftwdt010 watchdog is an architecture independent watchdog. It is usually used in SoC chip design. + +config WDT_I6300ESB + bool "Intel 6300ESB Watchdog" + depends on WDT && PCI + help + Hardware driver for the watchdog timer built into the Intel + 6300ESB controller hub. Also emulated by QEMU, thus useful for + testing watchdog functionality in virtual systems. + endmenu diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 84faefdb4c2..cfe3f3dac5a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -58,3 +58,4 @@ obj-$(CONFIG_WDT_TEGRA) += tegra_wdt.o obj-$(CONFIG_WDT_XILINX) += xilinx_wwdt.o obj-$(CONFIG_WDT_ADI) += adi_wdt.o obj-$(CONFIG_WDT_QCOM) += qcom-wdt.o +obj-$(CONFIG_WDT_I6300ESB) += i6300esb-wdt.o diff --git a/drivers/watchdog/i6300esb-wdt.c b/drivers/watchdog/i6300esb-wdt.c new file mode 100644 index 00000000000..7b969216e8a --- /dev/null +++ b/drivers/watchdog/i6300esb-wdt.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) Siemens AG, 2026 + * + * Based on Linux i6300esb.c + * (c) Copyright 2004 Google Inc. + * (c) Copyright 2005 David Härdeman <[email protected]> + */ + +#include <dm.h> +#include <dm/device_compat.h> +#include <pci.h> +#include <wdt.h> +#include <asm/io.h> + +/* PCI configuration registers */ +#define ESB_CONFIG_REG 0x60 /* Config register */ +#define ESB_LOCK_REG 0x68 /* WDT lock register */ + +/* Memory mapped registers */ +#define ESB_TIMER1_REG(w) ((w)->base + 0x00)/* Timer1 value after each reset */ +#define ESB_TIMER2_REG(w) ((w)->base + 0x04)/* Timer2 value after each reset */ +#define ESB_RELOAD_REG(w) ((w)->base + 0x0c)/* Reload register */ + +/* Lock register bits */ +#define ESB_WDT_ENABLE (0x01 << 1) /* Enable WDT */ +#define ESB_WDT_LOCK (0x01 << 0) /* Lock (nowayout) */ + +/* Reload register bits */ +#define ESB_WDT_RELOAD (0x01 << 8) /* prevent timeout */ + +/* Magic constants */ +#define ESB_UNLOCK1 0x80 /* Step 1 to unlock reset registers */ +#define ESB_UNLOCK2 0x86 /* Step 2 to unlock reset registers */ + +struct i6300_wdt { + ulong base; +}; + +static void unlock_timer_regs(struct i6300_wdt *wdt) +{ + writew(ESB_UNLOCK1, ESB_RELOAD_REG(wdt)); + writew(ESB_UNLOCK2, ESB_RELOAD_REG(wdt)); +} + +static int i6300esb_wdt_start(struct udevice *dev, u64 ms, ulong flags) +{ + struct i6300_wdt *wdt = dev_get_priv(dev); + uint32_t val; + + /* + * Scale to ns, then divide by 983040 ns, the period of the timer + * (30 ns PCI bus tick * 2^15). This is timer 1 (IRQ). Timer 2 + * (reboot) is set to immediate expiry. + */ + val = ms * 1000000ULL / 983040; + + unlock_timer_regs(wdt); + writel(val, ESB_TIMER1_REG(wdt)); + + unlock_timer_regs(wdt); + writel(0, ESB_TIMER2_REG(wdt)); + + dm_pci_write_config8(dev, ESB_LOCK_REG, ESB_WDT_ENABLE); + + return 0; +} + +static int i6300esb_wdt_stop(struct udevice *dev) +{ + struct i6300_wdt *wdt = dev_get_priv(dev); + uint8_t val; + + /* First, reset timers as suggested by the docs */ + unlock_timer_regs(wdt); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(wdt)); + /* Then disable the WDT */ + dm_pci_write_config8(dev, ESB_LOCK_REG, 0x0); + dm_pci_read_config8(dev, ESB_LOCK_REG, &val); + + /* Returns 0 if the timer was disabled, non-zero otherwise */ + return val & ESB_WDT_ENABLE; +} + +static int i6300esb_wdt_reset(struct udevice *dev) +{ + struct i6300_wdt *wdt = dev_get_priv(dev); + + unlock_timer_regs(wdt); + writew(ESB_WDT_RELOAD, ESB_RELOAD_REG(wdt)); + + return 0; +} + +static int i6300esb_wdt_probe(struct udevice *dev) +{ + struct i6300_wdt *wdt = dev_get_priv(dev); + uint8_t val; + + wdt->base = (ulong)dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, 0, 0, + PCI_REGION_TYPE, PCI_REGION_MEM); + + /* + * Config register: + * Bit 5 : 0 = Enable WDT_OUTPUT + * Bit 2 : 0 = set the timer frequency to the PCI clock + * divided by 2^15 (approx 1KHz). + * Bits 1:0 : 11 = WDT_INT_TYPE Disabled. + * The watchdog has two timers, it can be setup so that the + * expiry of timer1 results in an interrupt and the expiry of + * timer2 results in a reboot. We set it to not generate + * any interrupts as there is not much we can do with it + * right now. + */ + dm_pci_write_config16(dev, ESB_CONFIG_REG, 0x0003); + + /* Check that the WDT isn't already locked */ + dm_pci_read_config8(dev, ESB_LOCK_REG, &val); + if (val & ESB_WDT_LOCK) + dev_warn(dev, "nowayout already set\n"); + + /* Set the timer to watchdog mode and disable it for now */ + dm_pci_write_config8(dev, ESB_LOCK_REG, 0x00); + + return 0; +} + +static const struct wdt_ops i6300esb_wdt_ops = { + .start = i6300esb_wdt_start, + .stop = i6300esb_wdt_stop, + .reset = i6300esb_wdt_reset, +}; + +U_BOOT_DRIVER(i6300esb_wdt) = { + .name = "i6300esb_wdt", + .id = UCLASS_WDT, + .probe = i6300esb_wdt_probe, + .priv_auto = sizeof(struct i6300_wdt), + .ops = &i6300esb_wdt_ops, +}; + +static struct pci_device_id i6300esb_wdt_supported[] = { + { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_ESB_9) }, + { }, +}; + +U_BOOT_PCI_DEVICE(i6300esb_wdt, i6300esb_wdt_supported); -- 2.47.3
