* [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function
@ 2026-11-12 18:55 Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 1/4] clocksource/drivers/timer-vt8500: Add defines for magic constants Alexey Charkov
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Alexey Charkov @ 2026-11-12 18:55 UTC (permalink / raw)
To: Alexey Charkov, Krzysztof Kozlowski, Daniel Lezcano,
Thomas Gleixner, Wim Van Sebroeck, Guenter Roeck
Cc: linux-arm-kernel, linux-kernel, linux-watchdog
Add named defines for the registers and bits in timer-vt8500.
Move the system events timer from channel 0 to channel 1 when enough
information is provided by the device tree (i.e. more than one IRQ).
Use channel 0 for the system watchdog, and let the watchdog restart the
SoC, which the machine's own restart hook fails to do on WM8950.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
---
Changes in v6:
- Dropped the binding patch, which Rob has applied
- Rebased onto next-20261111
- Moved the shared header under include/clocksource (thanks Daniel)
- Pass the watchdog a single register base, and keep the watchdog
register offsets in its own driver (thanks Daniel)
- Build the watchdog driver in rather than as a module: the timer it
depends on is always built in, and the driver is tiny (thanks Daniel)
- kfree the correct structure upon aux child release (thanks Daniel)
- Return -ENODEV rather than -ENOMEM if the timer failed to initialize,
and drop the message on allocation failure (thanks Daniel)
- Unmap the timer registers if its interrupt is missing, so that no
watchdog gets created on a timer that never started
- Improved code comments and commit messages (thanks Daniel)
- Give the watchdog a default timeout, so that opening the device
before setting one no longer arms it for the next counter wraparound
- Wait for a pending match register write to settle before rewriting it,
as the clock event code does
- Honour the nowayout parameter, and disallow unbinding the watchdog,
which would leave it armed with nothing to ping it
- Added a restart handler, so that reboot works on WM8950
- Link to v5: https://lore.kernel.org/r/20250521-vt8500-timer-updates-v5-0-7e4bd11df72e@gmail.com
Changes in v5:
- Make sure the watchdog timeout value is clamped to hardware limits
before writing it to the registers (thanks Guenter)
- Add a comment about the watchdog match value (deadline) wrapping around
the U32_MAX boundary, which is expected behavior (thanks Guenter)
- Avoid moving register definitions to the shared header file to reduce
churn: the watchdog driver gets direct pointers to the two registers it
uses anyway, so it doesn't have to know all those offsets and bits
- Put the watchdog Kconfig entry with other ARM platforms where it belongs
- Link to v4: https://lore.kernel.org/r/20250521-vt8500-timer-updates-v4-0-2d4306a16ae4@gmail.com
Changes in v4:
- Changed the watchdog driver to use auxiliary bus instead of platform
(thanks Guenter)
- Split out the register definitions into a header file shared between
the clocksource driver and the watchdog driver
- Replaced -1UL with U32_MAX in watchdog max_heartbeat_ms to make builds
on non-32bit platforms happier
- Link to v3: https://lore.kernel.org/r/20250515-vt8500-timer-updates-v3-0-2197a1b062bd@gmail.com
Changes in v3:
- Dropped the DTS patch already applied by Krzysztof
- Rebased onto v6.15-rc5 as requested by Daniel
- Split out the watchdog code into a dedicated platform driver, like
timer-gxp does (thanks Daniel)
- Link to v2: https://lore.kernel.org/r/20250507-vt8500-timer-updates-v2-0-65e5d1b0855e@gmail.com
Changes in v2:
- Included the previously reviewed binding change that is directly related
to this series as the first patch here (thanks Krzysztof)
- Created a separate config symbol for the watchdog function to let users
build a kernel without forcing watchdog functionality upon them
(thanks Krzysztof)
- Link to the previous binding submission: https://lore.kernel.org/all/20250506-via_vt8500_timer_binding-v3-1-88450907503f@gmail.com/
---
Alexey Charkov (4):
clocksource/drivers/timer-vt8500: Add defines for magic constants
clocksource/drivers/timer-vt8500: Prepare for watchdog functionality
watchdog: Add support for VIA/WonderMedia SoC watchdog functionality
watchdog: vt8500: Add a restart handler
Documentation/watchdog/watchdog-parameters.rst | 7 +
MAINTAINERS | 2 +
drivers/clocksource/Kconfig | 1 +
drivers/clocksource/timer-vt8500.c | 179 +++++++++++++++++++++----
drivers/watchdog/Kconfig | 12 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/vt8500-wdt.c | 145 ++++++++++++++++++++
include/clocksource/vt8500-timer.h | 22 +++
8 files changed, 342 insertions(+), 27 deletions(-)
---
base-commit: aaa2f954c21b205a0bcd0961e5b2be4e82e58455
change-id: 20250506-vt8500-timer-updates-44a0d22cd720
Best regards,
--
Alexey Charkov <alchark@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 1/4] clocksource/drivers/timer-vt8500: Add defines for magic constants
2026-11-12 18:55 [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function Alexey Charkov
@ 2026-11-12 18:55 ` Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 2/4] clocksource/drivers/timer-vt8500: Prepare for watchdog functionality Alexey Charkov
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Alexey Charkov @ 2026-11-12 18:55 UTC (permalink / raw)
To: Alexey Charkov, Krzysztof Kozlowski, Daniel Lezcano,
Thomas Gleixner, Wim Van Sebroeck, Guenter Roeck
Cc: linux-arm-kernel, linux-kernel, linux-watchdog
Add defines for the timer registers and their bits to make the code more
self-explanatory. While at that, replace _VAL suffixes with more
intuitive _REG suffixes on register offsets.
No functional changes.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
---
drivers/clocksource/timer-vt8500.c | 62 ++++++++++++++++++++++++--------------
1 file changed, 39 insertions(+), 23 deletions(-)
diff --git a/drivers/clocksource/timer-vt8500.c b/drivers/clocksource/timer-vt8500.c
index a469b1b5f972..c4694cf8bea6 100644
--- a/drivers/clocksource/timer-vt8500.c
+++ b/drivers/clocksource/timer-vt8500.c
@@ -24,15 +24,28 @@
#define VT8500_TIMER_OFFSET 0x0100
#define VT8500_TIMER_HZ 3000000
-#define TIMER_MATCH_VAL 0x0000
-#define TIMER_COUNT_VAL 0x0010
-#define TIMER_STATUS_VAL 0x0014
-#define TIMER_IER_VAL 0x001c /* interrupt enable */
-#define TIMER_CTRL_VAL 0x0020
-#define TIMER_AS_VAL 0x0024 /* access status */
-#define TIMER_COUNT_R_ACTIVE (1 << 5) /* not ready for read */
-#define TIMER_COUNT_W_ACTIVE (1 << 4) /* not ready for write */
-#define TIMER_MATCH_W_ACTIVE (1 << 0) /* not ready for write */
+
+#define TIMER_MATCH_REG(x) (4 * (x))
+#define TIMER_COUNT_REG 0x0010 /* clocksource counter */
+
+#define TIMER_STATUS_REG 0x0014
+#define TIMER_STATUS_MATCH(x) BIT((x))
+#define TIMER_STATUS_CLEARALL (TIMER_STATUS_MATCH(0) | \
+ TIMER_STATUS_MATCH(1) | \
+ TIMER_STATUS_MATCH(2) | \
+ TIMER_STATUS_MATCH(3))
+
+#define TIMER_INT_EN_REG 0x001c /* interrupt enable */
+#define TIMER_INT_EN_MATCH(x) BIT((x))
+
+#define TIMER_CTRL_REG 0x0020
+#define TIMER_CTRL_ENABLE BIT(0) /* enable clocksource counter */
+#define TIMER_CTRL_RD_REQ BIT(1) /* request counter read */
+
+#define TIMER_ACC_STS_REG 0x0024 /* access status */
+#define TIMER_ACC_WR_MATCH(x) BIT((x)) /* writing Match (x) value */
+#define TIMER_ACC_WR_COUNTER BIT(4) /* writing clocksource counter */
+#define TIMER_ACC_RD_COUNTER BIT(5) /* reading clocksource counter */
#define msecs_to_loops(t) (loops_per_jiffy / 1000 * HZ * t)
@@ -43,11 +56,12 @@ static void __iomem *regbase;
static u64 vt8500_timer_read(struct clocksource *cs)
{
int loops = msecs_to_loops(10);
- writel(3, regbase + TIMER_CTRL_VAL);
- while ((readl((regbase + TIMER_AS_VAL)) & TIMER_COUNT_R_ACTIVE)
- && --loops)
+
+ writel(TIMER_CTRL_ENABLE | TIMER_CTRL_RD_REQ, regbase + TIMER_CTRL_REG);
+ while (readl(regbase + TIMER_ACC_STS_REG) & TIMER_ACC_RD_COUNTER &&
+ --loops)
cpu_relax();
- return readl(regbase + TIMER_COUNT_VAL);
+ return readl(regbase + TIMER_COUNT_REG);
}
static struct clocksource clocksource = {
@@ -63,23 +77,25 @@ static int vt8500_timer_set_next_event(unsigned long cycles,
{
int loops = msecs_to_loops(10);
u64 alarm = clocksource.read(&clocksource) + cycles;
- while ((readl(regbase + TIMER_AS_VAL) & TIMER_MATCH_W_ACTIVE)
- && --loops)
+
+ while (readl(regbase + TIMER_ACC_STS_REG) & TIMER_ACC_WR_MATCH(0) &&
+ --loops)
cpu_relax();
- writel((unsigned long)alarm, regbase + TIMER_MATCH_VAL);
+ writel((unsigned long)alarm, regbase + TIMER_MATCH_REG(0));
if ((signed)(alarm - clocksource.read(&clocksource)) <= MIN_OSCR_DELTA)
return -ETIME;
- writel(1, regbase + TIMER_IER_VAL);
+ writel(TIMER_INT_EN_MATCH(0), regbase + TIMER_INT_EN_REG);
return 0;
}
static int vt8500_shutdown(struct clock_event_device *evt)
{
- writel(readl(regbase + TIMER_CTRL_VAL) | 1, regbase + TIMER_CTRL_VAL);
- writel(0, regbase + TIMER_IER_VAL);
+ writel(readl(regbase + TIMER_CTRL_REG) | TIMER_CTRL_ENABLE,
+ regbase + TIMER_CTRL_REG);
+ writel(0, regbase + TIMER_INT_EN_REG);
return 0;
}
@@ -95,7 +111,7 @@ static struct clock_event_device clockevent = {
static irqreturn_t vt8500_timer_interrupt(int irq, void *dev_id)
{
struct clock_event_device *evt = dev_id;
- writel(0xf, regbase + TIMER_STATUS_VAL);
+ writel(TIMER_STATUS_CLEARALL, regbase + TIMER_STATUS_REG);
evt->event_handler(evt);
return IRQ_HANDLED;
@@ -119,9 +135,9 @@ static int __init vt8500_timer_init(struct device_node *np)
return -EINVAL;
}
- writel(1, regbase + TIMER_CTRL_VAL);
- writel(0xf, regbase + TIMER_STATUS_VAL);
- writel(~0, regbase + TIMER_MATCH_VAL);
+ writel(TIMER_CTRL_ENABLE, regbase + TIMER_CTRL_REG);
+ writel(TIMER_STATUS_CLEARALL, regbase + TIMER_STATUS_REG);
+ writel(~0, regbase + TIMER_MATCH_REG(0));
ret = clocksource_register_hz(&clocksource, VT8500_TIMER_HZ);
if (ret) {
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 2/4] clocksource/drivers/timer-vt8500: Prepare for watchdog functionality
2026-11-12 18:55 [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 1/4] clocksource/drivers/timer-vt8500: Add defines for magic constants Alexey Charkov
@ 2026-11-12 18:55 ` Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 3/4] watchdog: Add support for VIA/WonderMedia SoC " Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 4/4] watchdog: vt8500: Add a restart handler Alexey Charkov
3 siblings, 0 replies; 5+ messages in thread
From: Alexey Charkov @ 2026-11-12 18:55 UTC (permalink / raw)
To: Alexey Charkov, Krzysztof Kozlowski, Daniel Lezcano,
Thomas Gleixner, Wim Van Sebroeck, Guenter Roeck
Cc: linux-arm-kernel, linux-kernel, linux-watchdog
VIA/WonderMedia system timer can generate a watchdog reset when its
clocksource counter matches the value in the match register 0 and
watchdog function is enabled. For this to work, obviously the clock event
device must use a different match register (1~3) and respective interrupt.
Check if at least two interrupts are provided by the device tree, then use
match register 1 for system clock events and reserve match register 0 for
the watchdog. Keep match register 0 for clock events on device trees that
list a single interrupt, and offer no watchdog there.
Instantiate the watchdog as an auxiliary device of the timer from a
platform driver bound to the timer node, as the timer itself comes up too
early to have a struct device. Share what the watchdog needs through a new
header, include/clocksource/vt8500-timer.h.
Unmap the registers if the timer interrupt is missing, so that no watchdog
gets created on a timer whose counter never started.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
---
MAINTAINERS | 1 +
drivers/clocksource/Kconfig | 1 +
drivers/clocksource/timer-vt8500.c | 127 ++++++++++++++++++++++++++++++++++---
include/clocksource/vt8500-timer.h | 22 +++++++
4 files changed, 142 insertions(+), 9 deletions(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 27461b2161a7..acc4d2915460 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4010,6 +4010,7 @@ F: drivers/tty/serial/vt8500_serial.c
F: drivers/video/fbdev/vt8500lcdfb.*
F: drivers/video/fbdev/wm8505fb*
F: drivers/video/fbdev/wmt_ge_rops.*
+F: include/clocksource/vt8500-timer.h
ARM/ZTE ZX29 SOC SUPPORT
M: Stefan Dösinger <stefandoesinger@gmail.com>
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index da83483296d5..d060863fcc07 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -197,6 +197,7 @@ config TEGRA186_TIMER
config VT8500_TIMER
bool "VT8500 timer driver" if COMPILE_TEST
depends on HAS_IOMEM
+ select AUXILIARY_BUS
help
Enables support for the VT8500 driver.
diff --git a/drivers/clocksource/timer-vt8500.c b/drivers/clocksource/timer-vt8500.c
index c4694cf8bea6..b54ed5d692d7 100644
--- a/drivers/clocksource/timer-vt8500.c
+++ b/drivers/clocksource/timer-vt8500.c
@@ -11,6 +11,7 @@
* Alexey Charkov. Minor changes have been made for Device Tree Support.
*/
+#include <linux/auxiliary_bus.h>
#include <linux/io.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
@@ -21,9 +22,9 @@
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
+#include <linux/platform_device.h>
-#define VT8500_TIMER_OFFSET 0x0100
-#define VT8500_TIMER_HZ 3000000
+#include <clocksource/vt8500-timer.h>
#define TIMER_MATCH_REG(x) (4 * (x))
#define TIMER_COUNT_REG 0x0010 /* clocksource counter */
@@ -53,6 +54,9 @@
static void __iomem *regbase;
+/* Which match register to use for the system timer */
+static unsigned int sys_timer_ch;
+
static u64 vt8500_timer_read(struct clocksource *cs)
{
int loops = msecs_to_loops(10);
@@ -72,21 +76,26 @@ static struct clocksource clocksource = {
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
+static u64 vt8500_timer_next(u64 cycles)
+{
+ return clocksource.read(&clocksource) + cycles;
+}
+
static int vt8500_timer_set_next_event(unsigned long cycles,
struct clock_event_device *evt)
{
int loops = msecs_to_loops(10);
- u64 alarm = clocksource.read(&clocksource) + cycles;
+ u64 alarm = vt8500_timer_next(cycles);
- while (readl(regbase + TIMER_ACC_STS_REG) & TIMER_ACC_WR_MATCH(0) &&
- --loops)
+ while (readl(regbase + TIMER_ACC_STS_REG) &
+ TIMER_ACC_WR_MATCH(sys_timer_ch) && --loops)
cpu_relax();
- writel((unsigned long)alarm, regbase + TIMER_MATCH_REG(0));
+ writel((unsigned long)alarm, regbase + TIMER_MATCH_REG(sys_timer_ch));
if ((signed)(alarm - clocksource.read(&clocksource)) <= MIN_OSCR_DELTA)
return -ETIME;
- writel(TIMER_INT_EN_MATCH(0), regbase + TIMER_INT_EN_REG);
+ writel(TIMER_INT_EN_MATCH(sys_timer_ch), regbase + TIMER_INT_EN_REG);
return 0;
}
@@ -128,16 +137,29 @@ static int __init vt8500_timer_init(struct device_node *np)
return -ENXIO;
}
- timer_irq = irq_of_parse_and_map(np, 0);
+ /*
+ * The watchdog function is available on channel 0, but not on other
+ * channels. So to use it, the clock event device must use a different
+ * match register (1~3) and respective interrupt.
+ *
+ * Check if at least two interrupts are provided by the device tree,
+ * then use match register 1 for system clock events and reserve match
+ * register 0 for the watchdog.
+ */
+ sys_timer_ch = of_irq_count(np) > 1 ? 1 : 0;
+
+ timer_irq = irq_of_parse_and_map(np, sys_timer_ch);
if (!timer_irq) {
pr_err("%s: Missing irq description in Device Tree\n",
__func__);
+ iounmap(regbase);
+ regbase = NULL;
return -EINVAL;
}
writel(TIMER_CTRL_ENABLE, regbase + TIMER_CTRL_REG);
writel(TIMER_STATUS_CLEARALL, regbase + TIMER_STATUS_REG);
- writel(~0, regbase + TIMER_MATCH_REG(0));
+ writel(~0, regbase + TIMER_MATCH_REG(sys_timer_ch));
ret = clocksource_register_hz(&clocksource, VT8500_TIMER_HZ);
if (ret) {
@@ -163,4 +185,91 @@ static int __init vt8500_timer_init(struct device_node *np)
return 0;
}
+static void vt8500_timer_aux_uninit(void *data)
+{
+ auxiliary_device_uninit(data);
+}
+
+static void vt8500_timer_aux_delete(void *data)
+{
+ auxiliary_device_delete(data);
+}
+
+static void vt8500_timer_aux_release(struct device *dev)
+{
+ struct vt8500_wdt_info *wdt_info;
+ struct auxiliary_device *auxdev;
+
+ auxdev = container_of(dev, struct auxiliary_device, dev);
+ wdt_info = container_of(auxdev, struct vt8500_wdt_info, auxdev);
+ kfree(wdt_info);
+}
+
+/*
+ * This probe gets called after vt8500_timer_init(), which leaves regbase set
+ * only once the clocksource counter is running: that counter and match
+ * register 0 are all the watchdog needs, even if registering the clocksource
+ * or the clock event device failed afterwards. Create the watchdog device as
+ * a child, since the registers are shared.
+ */
+static int vt8500_timer_probe(struct platform_device *pdev)
+{
+ struct vt8500_wdt_info *wdt_info;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ if (!regbase)
+ return dev_err_probe(dev, -ENODEV,
+ "Timer not initialized, cannot create watchdog\n");
+
+ if (!sys_timer_ch) {
+ dev_info(dev, "Not enabling watchdog: only one irq was given\n");
+ return 0;
+ }
+
+ wdt_info = kzalloc_obj(*wdt_info);
+ if (!wdt_info)
+ return -ENOMEM;
+
+ wdt_info->timer_next = &vt8500_timer_next;
+ wdt_info->regbase = regbase;
+ wdt_info->auxdev.name = "vt8500-wdt";
+ wdt_info->auxdev.dev.parent = dev;
+ wdt_info->auxdev.dev.release = &vt8500_timer_aux_release;
+
+ ret = auxiliary_device_init(&wdt_info->auxdev);
+ if (ret) {
+ kfree(wdt_info);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, vt8500_timer_aux_uninit,
+ &wdt_info->auxdev);
+ if (ret)
+ return ret;
+
+ ret = auxiliary_device_add(&wdt_info->auxdev);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, vt8500_timer_aux_delete,
+ &wdt_info->auxdev);
+}
+
+static const struct of_device_id vt8500_timer_of_match[] = {
+ { .compatible = "via,vt8500-timer", },
+ {},
+};
+
+static struct platform_driver vt8500_timer_driver = {
+ .probe = vt8500_timer_probe,
+ .driver = {
+ .name = "vt8500-timer",
+ .of_match_table = vt8500_timer_of_match,
+ .suppress_bind_attrs = true,
+ },
+};
+
+builtin_platform_driver(vt8500_timer_driver);
+
TIMER_OF_DECLARE(vt8500, "via,vt8500-timer", vt8500_timer_init);
diff --git a/include/clocksource/vt8500-timer.h b/include/clocksource/vt8500-timer.h
new file mode 100644
index 000000000000..956863d50d02
--- /dev/null
+++ b/include/clocksource/vt8500-timer.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef CLOCKSOURCE_VT8500_TIMER_H_
+#define CLOCKSOURCE_VT8500_TIMER_H_
+
+#include <linux/auxiliary_bus.h>
+#include <linux/types.h>
+
+#define VT8500_TIMER_HZ 3000000
+
+/**
+ * struct vt8500_wdt_info - timer resources shared with the watchdog
+ * @auxdev: auxiliary device the watchdog driver binds to
+ * @timer_next: returns the clocksource counter value @cycles from now
+ * @regbase: timer register block; the watchdog uses match register 0
+ */
+struct vt8500_wdt_info {
+ struct auxiliary_device auxdev;
+ u64 (*timer_next)(u64 cycles);
+ void __iomem *regbase;
+};
+
+#endif /* CLOCKSOURCE_VT8500_TIMER_H_ */
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 3/4] watchdog: Add support for VIA/WonderMedia SoC watchdog functionality
2026-11-12 18:55 [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 1/4] clocksource/drivers/timer-vt8500: Add defines for magic constants Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 2/4] clocksource/drivers/timer-vt8500: Prepare for watchdog functionality Alexey Charkov
@ 2026-11-12 18:55 ` Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 4/4] watchdog: vt8500: Add a restart handler Alexey Charkov
3 siblings, 0 replies; 5+ messages in thread
From: Alexey Charkov @ 2026-11-12 18:55 UTC (permalink / raw)
To: Alexey Charkov, Krzysztof Kozlowski, Daniel Lezcano,
Thomas Gleixner, Wim Van Sebroeck, Guenter Roeck
Cc: linux-arm-kernel, linux-kernel, linux-watchdog
VIA/WonderMedia SoCs can use their system timer's first channel as a
watchdog device which will reset the system if the clocksource counter
matches the value given in its match register 0 and if the watchdog
function is enabled.
Since the watchdog function is tightly coupled to the timer itself, it
is implemented as an auxiliary device of the timer device. Build it in
rather than as a module, as the timer it depends on is always built in
and the driver is tiny.
Report a watchdog found running at probe, as one armed before a kexec
would be, so that the core keeps it fed until userspace takes over.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
---
Documentation/watchdog/watchdog-parameters.rst | 7 ++
MAINTAINERS | 1 +
drivers/watchdog/Kconfig | 12 +++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/vt8500-wdt.c | 132 +++++++++++++++++++++++++
5 files changed, 153 insertions(+)
diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst
index 348229ffcb43..27453b659545 100644
--- a/Documentation/watchdog/watchdog-parameters.rst
+++ b/Documentation/watchdog/watchdog-parameters.rst
@@ -637,6 +637,13 @@ uniphier_wdt:
-------------------------------------------------
+vt8500_wdt:
+ nowayout:
+ Watchdog cannot be stopped once started
+ (default=kernel config parameter)
+
+-------------------------------------------------
+
w83627hf_wdt:
wdt_io:
w83627hf/thf WDT io port (default 0x2E)
diff --git a/MAINTAINERS b/MAINTAINERS
index acc4d2915460..80aefe2f5481 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4010,6 +4010,7 @@ F: drivers/tty/serial/vt8500_serial.c
F: drivers/video/fbdev/vt8500lcdfb.*
F: drivers/video/fbdev/wm8505fb*
F: drivers/video/fbdev/wmt_ge_rops.*
+F: drivers/watchdog/vt8500-wdt.c
F: include/clocksource/vt8500-timer.h
ARM/ZTE ZX29 SOC SUPPORT
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index c884f86be4e3..a252f9c9c2c8 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1183,6 +1183,18 @@ config SUNPLUS_WATCHDOG
To compile this driver as a module, choose M here: the
module will be called sunplus_wdt.
+config VT8500_WATCHDOG
+ bool "VIA/WonderMedia VT8500 watchdog support"
+ depends on ARCH_VT8500 || COMPILE_TEST
+ select WATCHDOG_CORE
+ select AUXILIARY_BUS
+ help
+ VIA/WonderMedia SoCs can use their system timer as a hardware
+ watchdog, as long as the first timer channel is free from other
+ uses and respective function is enabled in its registers. To
+ make use of it, say Y here and ensure that the device tree
+ lists at least two interrupts for the VT8500 timer device.
+
# X86 (i386 + ia64 + x86_64) Architecture
config ACQUIRE_WDT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 19cb25f51624..5a5a111b4815 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -106,6 +106,7 @@ obj-$(CONFIG_SUNPLUS_WATCHDOG) += sunplus_wdt.o
obj-$(CONFIG_MARVELL_GTI_WDT) += marvell_gti_wdt.o
obj-$(CONFIG_GUNYAH_WATCHDOG) += gunyah_wdt.o
obj-$(CONFIG_MA35D1_WDT) += ma35d1_wdt.o
+obj-$(CONFIG_VT8500_WATCHDOG) += vt8500-wdt.o
# X86 (i386 + ia64 + x86_64) Architecture
obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o
diff --git a/drivers/watchdog/vt8500-wdt.c b/drivers/watchdog/vt8500-wdt.c
new file mode 100644
index 000000000000..e0f838fd1d50
--- /dev/null
+++ b/drivers/watchdog/vt8500-wdt.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: 2026 Alexey Charkov <alchark@gmail.com>
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/limits.h>
+#include <linux/minmax.h>
+#include <linux/moduleparam.h>
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+
+#include <clocksource/vt8500-timer.h>
+
+/* Offsets within the timer register block */
+#define VT8500_WDT_MATCH_REG 0x0000 /* timer match register 0 */
+#define VT8500_WDT_EN_REG 0x0018
+#define VT8500_WDT_EN BIT(0)
+#define VT8500_WDT_ACC_STS_REG 0x0024 /* timer access status */
+#define VT8500_WDT_ACC_WR_MATCH BIT(0) /* match register 0 write pending */
+
+#define VT8500_WDT_DEFAULT_TIMEOUT 30 /* seconds */
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+ __stringify(WATCHDOG_NOWAYOUT) ")");
+
+/* Arm the watchdog to reset the SoC @cycles counter ticks from now */
+static int vt8500_watchdog_arm(struct vt8500_wdt_info *info, u64 cycles)
+{
+ u32 deadline, sts;
+ int ret;
+
+ /* Let a previous match write settle, as the timer driver does */
+ ret = readl_poll_timeout_atomic(info->regbase + VT8500_WDT_ACC_STS_REG,
+ sts, !(sts & VT8500_WDT_ACC_WR_MATCH),
+ 1, 10000);
+ if (ret)
+ return ret;
+
+ /*
+ * The deadline is matched against the hardware clocksource counter,
+ * which is a u32 value incrementing at VT8500_TIMER_HZ and continuing
+ * past wraparound. When the return value of timer_next is greater than
+ * U32_MAX then the match should occur after the hardware counter wraps
+ * around, thus we take only the lower 32 bits of timer_next return val
+ */
+ deadline = info->timer_next(cycles);
+ writel(deadline, info->regbase + VT8500_WDT_MATCH_REG);
+ writel(VT8500_WDT_EN, info->regbase + VT8500_WDT_EN_REG);
+
+ return 0;
+}
+
+static int vt8500_watchdog_start(struct watchdog_device *wdd)
+{
+ struct vt8500_wdt_info *info = watchdog_get_drvdata(wdd);
+ u32 timeout_ms = min(wdd->timeout * 1000, wdd->max_hw_heartbeat_ms);
+
+ return vt8500_watchdog_arm(info,
+ (u64)timeout_ms * (VT8500_TIMER_HZ / 1000));
+}
+
+static int vt8500_watchdog_stop(struct watchdog_device *wdd)
+{
+ struct vt8500_wdt_info *info = watchdog_get_drvdata(wdd);
+
+ writel(0, info->regbase + VT8500_WDT_EN_REG);
+
+ return 0;
+}
+
+static const struct watchdog_ops vt8500_watchdog_ops = {
+ .start = vt8500_watchdog_start,
+ .stop = vt8500_watchdog_stop,
+};
+
+static const struct watchdog_info vt8500_watchdog_info = {
+ .identity = "VIA VT8500 watchdog",
+ .options = WDIOF_MAGICCLOSE |
+ WDIOF_KEEPALIVEPING |
+ WDIOF_SETTIMEOUT,
+};
+
+static int vt8500_wdt_probe(struct auxiliary_device *auxdev,
+ const struct auxiliary_device_id *id)
+{
+ struct vt8500_wdt_info *info;
+ struct watchdog_device *wdd;
+
+ wdd = devm_kzalloc_obj(&auxdev->dev, *wdd);
+ if (!wdd)
+ return -ENOMEM;
+
+ wdd->info = &vt8500_watchdog_info;
+ wdd->ops = &vt8500_watchdog_ops;
+ wdd->min_timeout = 1;
+ wdd->timeout = VT8500_WDT_DEFAULT_TIMEOUT;
+ wdd->max_hw_heartbeat_ms = U32_MAX / (VT8500_TIMER_HZ / 1000);
+ wdd->parent = &auxdev->dev;
+
+ info = container_of(auxdev, struct vt8500_wdt_info, auxdev);
+ watchdog_set_drvdata(wdd, info);
+ watchdog_set_nowayout(wdd, nowayout);
+
+ /* Let the core feed a watchdog left running, e.g. across kexec */
+ if (readl(info->regbase + VT8500_WDT_EN_REG) & VT8500_WDT_EN)
+ set_bit(WDOG_HW_RUNNING, &wdd->status);
+
+ return devm_watchdog_register_device(&auxdev->dev, wdd);
+}
+
+static const struct auxiliary_device_id vt8500_wdt_ids[] = {
+ { .name = "timer_vt8500.vt8500-wdt" },
+ {}
+};
+
+static struct auxiliary_driver vt8500_wdt_driver = {
+ .name = "vt8500-wdt",
+ .probe = vt8500_wdt_probe,
+ .id_table = vt8500_wdt_ids,
+ .driver = {
+ /* Unbinding would leave the hardware armed with no one to ping */
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_driver(vt8500_wdt_driver, auxiliary_driver_register);
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v6 4/4] watchdog: vt8500: Add a restart handler
2026-11-12 18:55 [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function Alexey Charkov
` (2 preceding siblings ...)
2026-11-12 18:55 ` [PATCH v6 3/4] watchdog: Add support for VIA/WonderMedia SoC " Alexey Charkov
@ 2026-11-12 18:55 ` Alexey Charkov
3 siblings, 0 replies; 5+ messages in thread
From: Alexey Charkov @ 2026-11-12 18:55 UTC (permalink / raw)
To: Alexey Charkov, Krzysztof Kozlowski, Daniel Lezcano,
Thomas Gleixner, Wim Van Sebroeck, Guenter Roeck
Cc: linux-arm-kernel, linux-kernel, linux-watchdog
Reset the SoC through the watchdog when the machine's own restart hook
leaves it running: the PMSR write in vt8500_restart() has no effect on
WM8950, so a reboot there ends in "Reboot failed -- System halted"
unless a watchdog armed from userspace happens to fire.
Arm the match a millisecond ahead of the current count, which leaves
ample margin for the register writes to land.
Register at priority 128, the default for restart handlers and also the
priority of the ARM machine restart hook. That hook is registered first,
from setup_arch(), and handlers of equal priority run in registration
order, so this one runs right after it and only matters where the hook
fails to reset the SoC.
Signed-off-by: Alexey Charkov <alchark@gmail.com>
---
drivers/watchdog/vt8500-wdt.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/drivers/watchdog/vt8500-wdt.c b/drivers/watchdog/vt8500-wdt.c
index e0f838fd1d50..0ce1e2b06410 100644
--- a/drivers/watchdog/vt8500-wdt.c
+++ b/drivers/watchdog/vt8500-wdt.c
@@ -25,6 +25,9 @@
#define VT8500_WDT_DEFAULT_TIMEOUT 30 /* seconds */
+/* How far ahead of the current count to arm the match for a restart */
+#define VT8500_WDT_RESTART_TICKS (VT8500_TIMER_HZ / 1000) /* 1 ms */
+
static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
@@ -75,9 +78,18 @@ static int vt8500_watchdog_stop(struct watchdog_device *wdd)
return 0;
}
+static int vt8500_watchdog_restart(struct watchdog_device *wdd,
+ unsigned long action, void *data)
+{
+ struct vt8500_wdt_info *info = watchdog_get_drvdata(wdd);
+
+ return vt8500_watchdog_arm(info, VT8500_WDT_RESTART_TICKS);
+}
+
static const struct watchdog_ops vt8500_watchdog_ops = {
.start = vt8500_watchdog_start,
.stop = vt8500_watchdog_stop,
+ .restart = vt8500_watchdog_restart,
};
static const struct watchdog_info vt8500_watchdog_info = {
@@ -107,6 +119,7 @@ static int vt8500_wdt_probe(struct auxiliary_device *auxdev,
info = container_of(auxdev, struct vt8500_wdt_info, auxdev);
watchdog_set_drvdata(wdd, info);
watchdog_set_nowayout(wdd, nowayout);
+ watchdog_set_restart_priority(wdd, 128);
/* Let the core feed a watchdog left running, e.g. across kexec */
if (readl(info->regbase + VT8500_WDT_EN_REG) & VT8500_WDT_EN)
--
2.56.0
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-12 18:55 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-12 18:55 [PATCH v6 0/4] clocksource/drivers/timer-vt8500: clean up and add watchdog function Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 1/4] clocksource/drivers/timer-vt8500: Add defines for magic constants Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 2/4] clocksource/drivers/timer-vt8500: Prepare for watchdog functionality Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 3/4] watchdog: Add support for VIA/WonderMedia SoC " Alexey Charkov
2026-11-12 18:55 ` [PATCH v6 4/4] watchdog: vt8500: Add a restart handler Alexey Charkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®