drivers: watchdog: Add the OpenTitan watchdog driver

The OpenTitan watchdog driver is a watchdog driver that can be configured
with two stages: a watchdog "bark", which generates an interrupt, and a
watchdog "bite", which resets the system. The two-stage watchdog can be
enabled by setting CONFIG_WDT_MULTISTAGE=y. Otherwise, the driver
functions as a single-stage watchdog.

A callback function may be set for the bark interrupt through the
wdt setup interface, but will only be used if the two-stage watchdog is
enabled. It must be configured for the first watchdog stage.

The driver controls only the watchdog portion of the OpenTitan AON timer.

Signed-off-by: Tyler Ng <tkng@rivosinc.com>
diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt
index f7a00aa..cd40a5a 100644
--- a/drivers/watchdog/CMakeLists.txt
+++ b/drivers/watchdog/CMakeLists.txt
@@ -34,6 +34,7 @@
 zephyr_library_sources_ifdef(CONFIG_WDT_TI_TPS382X wdt_ti_tps382x.c)
 zephyr_library_sources_ifdef(CONFIG_WDT_XILINX_AXI wdt_xilinx_axi.c)
 zephyr_library_sources_ifdef(CONFIG_WDT_INFINEON_CAT1 wdt_ifx_cat1.c)
+zephyr_library_sources_ifdef(CONFIG_WDT_OPENTITAN wdt_opentitan.c)
 
 zephyr_library_sources_ifdef(CONFIG_WDT_DW wdt_dw.c wdt_dw_common.c)
 zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c)
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 96f3248..cae4b52 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -108,4 +108,6 @@
 
 source "drivers/watchdog/Kconfig.ifx_cat1"
 
+source "drivers/watchdog/Kconfig.opentitan"
+
 endif # WATCHDOG
diff --git a/drivers/watchdog/Kconfig.opentitan b/drivers/watchdog/Kconfig.opentitan
new file mode 100644
index 0000000..7a9c10a
--- /dev/null
+++ b/drivers/watchdog/Kconfig.opentitan
@@ -0,0 +1,13 @@
+# OpenTitan Always-On Timer support
+
+# Copyright (c) 2023, Rivos Inc.
+# SPDX-License-Identifier: Apache-2.0
+
+config WDT_OPENTITAN
+	bool "OpenTitan Always-On (AON) Timer"
+	depends on DT_HAS_LOWRISC_OPENTITAN_AONTIMER_ENABLED
+	default y
+	select HAS_WDT_MULTISTAGE
+	help
+	  This option enables support for the watchdog portion of the OpenTitan AON
+	  timer.
diff --git a/drivers/watchdog/wdt_opentitan.c b/drivers/watchdog/wdt_opentitan.c
new file mode 100644
index 0000000..7f6fde2
--- /dev/null
+++ b/drivers/watchdog/wdt_opentitan.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2023 by Rivos Inc.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define DT_DRV_COMPAT lowrisc_opentitan_aontimer
+
+#include <zephyr/kernel.h>
+#include <zephyr/sys/util.h>
+#include <zephyr/drivers/watchdog.h>
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_REGISTER(wdt_opentitan, CONFIG_WDT_LOG_LEVEL);
+
+#define OT_REG_WDOG_REGWEN_OFFSET 0x10
+#define OT_REG_WDOG_CTRL_OFFSET 0x14
+#define OT_REG_WDOG_BARK_THOLD_OFFSET 0x18
+#define OT_REG_WDOG_BITE_THOLD_OFFSET 0x1C
+#define OT_REG_WDOG_COUNT_OFFSET 0x20
+#define OT_REG_INTR_STATE_OFFSET 0x24
+
+struct wdt_ot_aontimer_cfg {
+	uintptr_t regs;
+	uint32_t clk_freq;
+	bool wdog_lock;
+};
+
+struct wdt_ot_aontimer_data {
+	wdt_callback_t bark;
+};
+
+static int ot_aontimer_setup(const struct device *dev, uint8_t options)
+{
+	const struct wdt_ot_aontimer_cfg *const cfg = dev->config;
+	volatile uintptr_t regs = cfg->regs;
+
+	sys_write32(0, regs + OT_REG_WDOG_COUNT_OFFSET);
+	sys_write32(1, regs + OT_REG_WDOG_CTRL_OFFSET);
+	if (cfg->wdog_lock) {
+		/* Force a read to ensure the timer was enabled. */
+		(void) sys_read32(regs + OT_REG_WDOG_CTRL_OFFSET);
+		sys_write32(0, regs + OT_REG_WDOG_REGWEN_OFFSET);
+	}
+	return 0;
+}
+
+static int ot_aontimer_disable(const struct device *dev)
+{
+	const struct wdt_ot_aontimer_cfg *const cfg = dev->config;
+	volatile uintptr_t regs = cfg->regs;
+
+	if (!sys_read32(regs + OT_REG_WDOG_REGWEN_OFFSET)) {
+		LOG_ERR("Cannot disable - watchdog settings locked.");
+		return -EPERM;
+	}
+
+	uint32_t ctrl_val = sys_read32(regs + OT_REG_WDOG_CTRL_OFFSET);
+
+	if (!(ctrl_val & BIT(0))) {
+		return -EFAULT;
+	}
+	sys_write32(ctrl_val & ~BIT(0), regs + OT_REG_WDOG_CTRL_OFFSET);
+
+	return 0;
+}
+
+/*
+ * The OpenTitan AON Timer includes a multi-level watchdog timer.
+ * While the first stage supports an interrupt callback, the second does not.
+ * The second stage is mandatory to adjust the "bite" time window.
+ *
+ * Some boundaries are enforced to prevent behavior that is technically correct
+ * but is not likely intended.
+ * Bark:
+ * Minimum must be 0. Maximum must be > min.
+ * The bark interrupt occurs at max (or if the timeout is too long to be
+ * supported, the value x s.t. min < x < max and x is the largest valid timeout)
+ * Bite:
+ * Minimum must be >= bark.min, and maximum >= bark.max. If the timeout is too
+ * long to fit, it tries to find the value x s.t. min < x < max where x is the
+ * largest valid timeout.
+ * The bite action occurs max.
+ */
+static int ot_aontimer_install_timeout(const struct device *dev,
+					const struct wdt_timeout_cfg *cfg)
+{
+	struct wdt_ot_aontimer_data *data = dev->data;
+	const struct wdt_ot_aontimer_cfg *const dev_cfg = dev->config;
+	volatile uintptr_t reg_base = dev_cfg->regs;
+	const uint64_t max_window = (uint64_t) UINT32_MAX * 1000 / dev_cfg->clk_freq;
+	uint64_t bite_thold;
+#ifdef CONFIG_WDT_MULTISTAGE
+	/* When multistage is selected, add an intermediate bark stage */
+	uint64_t bark_thold;
+	struct wdt_timeout_cfg *bite = cfg->next;
+
+	if (bite == NULL || bite->window.max < cfg->window.max ||
+			(uint64_t) bite->window.min > max_window) {
+		return -EINVAL;
+	}
+	/*
+	 * Flag must be clear for stage 1, and reset SOC for stage 2.
+	 * CPU not supported
+	 */
+	if (cfg->flags != WDT_FLAG_RESET_NONE || bite->flags != WDT_FLAG_RESET_SOC) {
+		return -ENOTSUP;
+	}
+#endif
+
+	if (cfg->window.min > cfg->window.max || (uint64_t) cfg->window.min > max_window) {
+		return -EINVAL;
+	}
+
+	if (!sys_read32(reg_base + OT_REG_WDOG_REGWEN_OFFSET)) {
+		LOG_ERR("Cannot install timeout - watchdog settings locked.");
+		return -ENOMEM;
+	}
+
+	/* Watchdog is already enabled! */
+	if (sys_read32(reg_base + OT_REG_WDOG_CTRL_OFFSET) & BIT(0)) {
+		return -EBUSY;
+	}
+
+#ifdef CONFIG_WDT_MULTISTAGE
+	/* Force 64-bit ops to ensure thresholds fits in the timer reg. */
+	bark_thold = ((uint64_t) cfg->window.max * dev_cfg->clk_freq / 1000);
+	bite_thold = ((uint64_t) bite->window.max * dev_cfg->clk_freq / 1000);
+	/* Saturate these config values; min is verified to be < max_window */
+	if (bark_thold > UINT32_MAX) {
+		bark_thold = UINT32_MAX;
+	}
+	if (bite_thold > UINT32_MAX) {
+		bite_thold = UINT32_MAX;
+	}
+	data->bark = cfg->callback;
+	sys_write32((uint32_t) bark_thold, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET);
+	sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET);
+#else
+	bite_thold = ((uint64_t) cfg->window.max * dev_cfg->clk_freq / 1000);
+	/* Saturate this config value; min is verified to be < max_window */
+	if (bite_thold > UINT32_MAX) {
+		bite_thold = UINT32_MAX;
+	}
+	if (cfg->flags == WDT_FLAG_RESET_NONE) {
+		/* Set bite -> bark, so we generate an interrupt instead of resetting */
+		sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET);
+		/* Disable bite by writing it to max. Edge case is the bark = max. */
+		sys_write32(UINT32_MAX, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET);
+		data->bark = cfg->callback;
+	} else {
+		data->bark = NULL;
+		/* Effectively disable bark by setting it to max */
+		sys_write32(UINT32_MAX, reg_base + OT_REG_WDOG_BARK_THOLD_OFFSET);
+		sys_write32((uint32_t) bite_thold, reg_base + OT_REG_WDOG_BITE_THOLD_OFFSET);
+	}
+#endif
+
+	return 0;
+}
+
+static int ot_aontimer_feed(const struct device *dev, int channel_id)
+{
+	ARG_UNUSED(channel_id);
+	const struct wdt_ot_aontimer_cfg *const cfg = dev->config;
+	volatile uintptr_t regs = cfg->regs;
+
+	sys_write32(0, regs + OT_REG_WDOG_COUNT_OFFSET);
+
+	/* Deassert the interrupt line */
+	sys_write32(BIT(1), regs + OT_REG_INTR_STATE_OFFSET);
+	return 0;
+}
+
+static void wdt_ot_isr(const struct device *dev)
+{
+	const struct wdt_ot_aontimer_cfg *const cfg = dev->config;
+	struct wdt_ot_aontimer_data *data = dev->data;
+	volatile uintptr_t regs = cfg->regs;
+
+	if (data->bark != NULL) {
+		data->bark(dev, 0);
+	}
+
+	/* Deassert the interrupt line */
+	sys_write32(BIT(1), regs + OT_REG_INTR_STATE_OFFSET);
+}
+
+static int ot_aontimer_init(const struct device *dev)
+{
+	IRQ_CONNECT(
+		DT_INST_IRQ_BY_IDX(0, 0, irq),
+		DT_INST_IRQ_BY_IDX(0, 0, priority), wdt_ot_isr,
+		DEVICE_DT_INST_GET(0), 0);
+	irq_enable(DT_INST_IRQ_BY_IDX(0, 0, irq));
+
+	return 0;
+}
+
+static struct wdt_ot_aontimer_data ot_aontimer_data;
+
+static struct wdt_ot_aontimer_cfg ot_aontimer_cfg = {
+	.regs = (volatile uintptr_t) DT_INST_REG_ADDR(0),
+	.clk_freq = DT_INST_PROP(0, clock_frequency),
+	.wdog_lock = DT_INST_PROP(0, wdog_lock),
+};
+
+static const struct wdt_driver_api ot_aontimer_api = {
+	.setup = ot_aontimer_setup,
+	.disable = ot_aontimer_disable,
+	.install_timeout = ot_aontimer_install_timeout,
+	.feed = ot_aontimer_feed,
+};
+
+DEVICE_DT_INST_DEFINE(0, ot_aontimer_init, NULL,
+	&ot_aontimer_data, &ot_aontimer_cfg, PRE_KERNEL_1,
+	CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
+	&ot_aontimer_api);