[PATCH 23/27] clocksource: add a driver for the PXA OS timer and its watchdog

Sascha Hauer s.hauer at pengutronix.de
Sun Aug 16 10:56:43 PDT 2026


The PXA clocksource was registered from a core_initcall in mach-pxa which
is not suitable for multiarch. Move it over to a platform driver which probes
from device tree.

Match register 3 of the same timer doubles as a watchdog: arm it by writing
a match value ahead of the counter and setting OWER_WME, and the match
resets the machine. That is the same block the clocksource uses and there
is one device tree node for it, so register the watchdog from the same
driver rather than inventing a second node for the same registers.

OWER_WME cannot be cleared again - the watchdog runs until it resets the
machine - so refuse a timeout of zero once it is armed instead of
pretending to stop it. Verified on a PXA303: the board resets on schedule
whether or not something asked for it to be disabled in between.

The counter is 32 bit and the match has to fit in it, which puts the
maximum timeout at 1321s at this part's 3.25MHz.

Assisted-by: Claude Opus 5
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
 arch/arm/mach-pxa/Makefile      |   1 -
 arch/arm/mach-pxa/clocksource.c |  41 -----------
 drivers/clocksource/Kconfig     |   7 ++
 drivers/clocksource/Makefile    |   1 +
 drivers/clocksource/timer-pxa.c | 151 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 159 insertions(+), 42 deletions(-)

diff --git a/arch/arm/mach-pxa/Makefile b/arch/arm/mach-pxa/Makefile
index 86d9c4ca95..7a9ca83199 100644
--- a/arch/arm/mach-pxa/Makefile
+++ b/arch/arm/mach-pxa/Makefile
@@ -1,6 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
-obj-y += clocksource.o
 obj-y += sleep.o
 obj-y += common.o
 obj-y += devices.o
diff --git a/arch/arm/mach-pxa/clocksource.c b/arch/arm/mach-pxa/clocksource.c
deleted file mode 100644
index 3bc95827d8..0000000000
--- a/arch/arm/mach-pxa/clocksource.c
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * (C) Copyright 2009 Sascha Hauer <s.hauer at pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of
- * the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- */
-
-#include <common.h>
-#include <init.h>
-#include <clock.h>
-#include <asm/io.h>
-
-#define OSCR	0x40A00010
-
-static uint64_t pxa_clocksource_read(void)
-{
-	return readl(OSCR);
-}
-
-static struct clocksource cs = {
-	.read	= pxa_clocksource_read,
-	.mask	= 0xffffffff,
-	.shift	= 20,
-	.priority = 80,
-};
-
-static int clocksource_init(void)
-{
-	cs.mult = clocksource_hz2mult(3250000, cs.shift);
-
-	return init_clock(&cs);
-}
-
-core_initcall(clocksource_init);
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 5ee83d2b38..89e0be7894 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -120,6 +120,13 @@ config ARMV7M_SYSTICK
 	help
 	  This option enables support for the ARMv7M system timer unit.
 
+config CLOCKSOURCE_PXA
+	bool "Clocksource for PXA SoCs"
+	depends on OFDEVICE && (ARCH_PXA || COMPILE_TEST)
+	default ARCH_PXA
+	help
+	  This option enables support for the OS timer found on PXA SoCs.
+
 config CLKSRC_STM32
 	bool "Clocksource for STM32 SoCs"
 	depends on OFDEVICE && (ARCH_STM32 || COMPILE_TEST)
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index dff8255650..0f8a54d819 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -26,3 +26,4 @@ obj-$(CONFIG_CLINT_TIMER) += timer-clint.o
 obj-$(CONFIG_RISCV_TIMER) += timer-riscv.o
 obj-$(CONFIG_ARMV7M_SYSTICK) += armv7m_systick.o
 obj-$(CONFIG_CLKSRC_STM32) += timer-stm32.o
+obj-$(CONFIG_CLOCKSOURCE_PXA) += timer-pxa.o
diff --git a/drivers/clocksource/timer-pxa.c b/drivers/clocksource/timer-pxa.c
new file mode 100644
index 0000000000..8834bbec0c
--- /dev/null
+++ b/drivers/clocksource/timer-pxa.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Clocksource and watchdog for the PXA OS timer.
+ *
+ * The two are one block: match register 3 doubles as the watchdog, so both
+ * live in the same driver rather than fighting over the device tree node.
+ *
+ * (C) Copyright 2009 Sascha Hauer <s.hauer at pengutronix.de>
+ */
+
+#include <common.h>
+#include <clock.h>
+#include <driver.h>
+#include <init.h>
+#include <io.h>
+#include <watchdog.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#define OSMR3		0x0c	/* match register 3, the watchdog match */
+#define OSCR		0x10	/* counter, free running and running out of reset */
+#define OSSR		0x14	/* status */
+#define OWER		0x18	/* watchdog enable */
+
+#define OSSR_M3		(1 << 3)	/* match status channel 3 */
+#define OWER_WME	(1 << 0)	/* watchdog match enable */
+
+struct pxa_timer {
+	void __iomem *base;
+	unsigned long rate;
+	struct watchdog wd;
+};
+
+static inline struct pxa_timer *to_pxa_timer(struct watchdog *wd)
+{
+	return container_of(wd, struct pxa_timer, wd);
+}
+
+static void __iomem *pxa_timer_base;
+
+static uint64_t pxa_clocksource_read(void)
+{
+	return readl(pxa_timer_base + OSCR);
+}
+
+static struct clocksource pxa_cs = {
+	.read     = pxa_clocksource_read,
+	.mask     = CLOCKSOURCE_MASK(32),
+	.shift    = 20,
+	.priority = 80,
+};
+
+static int pxa_wdt_set_timeout(struct watchdog *wd, unsigned timeout)
+{
+	struct pxa_timer *timer = to_pxa_timer(wd);
+
+	if (!timeout) {
+		/*
+		 * OWER_WME only ever reads back the way it was written once:
+		 * the watchdog cannot be stopped again short of the reset it
+		 * is about to cause. Refuse rather than pretend.
+		 */
+		if (wd->running == WDOG_HW_RUNNING)
+			return -ENOSYS;
+
+		return 0;
+	}
+
+	writel(readl(timer->base + OSCR) + (u64)timeout * timer->rate,
+	       timer->base + OSMR3);
+	writel(OSSR_M3, timer->base + OSSR);
+	writel(OWER_WME, timer->base + OWER);
+
+	wd->running = WDOG_HW_RUNNING;
+
+	return 0;
+}
+
+static int pxa_timer_probe(struct device *dev)
+{
+	struct resource *iores;
+	struct pxa_timer *timer;
+	struct clk *clk;
+	int ret;
+
+	iores = dev_request_mem_resource(dev, 0);
+	if (IS_ERR(iores))
+		return PTR_ERR(iores);
+
+	clk = clk_get(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "cannot get clock\n");
+
+	ret = clk_enable(clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "cannot enable clock\n");
+
+	timer = xzalloc(sizeof(*timer));
+	timer->base = IOMEM(iores->start);
+	timer->rate = clk_get_rate(clk);
+	if (!timer->rate)
+		return dev_err_probe(dev, -EINVAL, "clock has no rate\n");
+
+	pxa_timer_base = timer->base;
+	pxa_cs.mult = clocksource_hz2mult(timer->rate, pxa_cs.shift);
+
+	ret = init_clock(&pxa_cs);
+	if (ret)
+		return ret;
+
+	timer->wd.set_timeout = pxa_wdt_set_timeout;
+	timer->wd.hwdev = dev;
+	timer->wd.name = "pxa-wdt";
+	/* the counter is 32 bit, so that is as far ahead as a match reaches */
+	timer->wd.timeout_max = U32_MAX / timer->rate;
+	timer->wd.running = readl(timer->base + OWER) & OWER_WME ?
+			WDOG_HW_RUNNING : WDOG_HW_NOT_RUNNING;
+
+	ret = watchdog_register(&timer->wd);
+	if (ret)
+		dev_warn(dev, "failed to register watchdog: %pe\n",
+			 ERR_PTR(ret));
+
+	return 0;
+}
+
+static const struct of_device_id pxa_timer_dt_ids[] = {
+	{ .compatible = "marvell,pxa-timer" },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, pxa_timer_dt_ids);
+
+static struct driver pxa_timer_driver = {
+	.name = "pxa-timer",
+	.probe = pxa_timer_probe,
+	.of_compatible = pxa_timer_dt_ids,
+};
+postcore_platform_driver(pxa_timer_driver);
+
+/*
+ * Under deep probe nothing refers to the timer by phandle, so it is not probed
+ * until the device tree walk reaches it - and that walk is in device tree
+ * order, which on PXA3xx puts the NAND controller a long way ahead of the
+ * timer. Everything in between would run its timeouts against the dummy
+ * clocksource.
+ */
+static int pxa_timer_of_init(void)
+{
+	return of_devices_ensure_probed_by_dev_id(pxa_timer_dt_ids);
+}
+coredevice_initcall(pxa_timer_of_init);

-- 
2.47.3




More information about the barebox mailing list