diff mbox series

[v2,4/4] arm: rp2350: Add driver for RP2350 hardware timers

Message ID 20260902211501.127915-5-bhargavdas100@gmail.com
State New
Delegated to: Peter Robinson
Headers show
Series Add initial support for Raspberry Pi Pico 2 (RP2350) | expand

Commit Message

Bhargav Das Sept. 2, 2026, 9:14 p.m. UTC
Implement a Driver Model (DM) timer driver for the RP2350 SoC.

Previously, CONFIG_TIMER was enabled purely to satisfy compilation and
linker requirements. Because the platform relied on generic fallback
definitions, invoking the `timer` command or any timing-related
operation (such as delays or timeouts) caused the system to crash.

The early system tick generation is initialized in mach-rp2xxx/rp2350.c
before the main driver probes. Both timer0 and timer1 have been verified
to work correctly. Enabled timer0 by default in the device tree.

Signed-off-by: Bhargav Das <bhargavdas100@gmail.com>
---
 arch/arm/dts/rp2350-pico2.dts             |  4 ++
 arch/arm/dts/rp2350.dtsi                  | 14 +++++
 arch/arm/include/asm/arch-rp2xxx/rp2350.h | 17 ++++++-
 arch/arm/mach-rp2xxx/rp2350.c             | 31 ++++++++++++
 configs/rpi_pico2_defconfig               |  1 +
 drivers/timer/Kconfig                     |  8 +++
 drivers/timer/Makefile                    |  1 +
 drivers/timer/rp2350_timer.c              | 62 +++++++++++++++++++++++
 8 files changed, 137 insertions(+), 1 deletion(-)
 create mode 100644 drivers/timer/rp2350_timer.c
diff mbox series

Patch

diff --git a/arch/arm/dts/rp2350-pico2.dts b/arch/arm/dts/rp2350-pico2.dts
index 391bdd3b62e..e83809bbffa 100644
--- a/arch/arm/dts/rp2350-pico2.dts
+++ b/arch/arm/dts/rp2350-pico2.dts
@@ -23,3 +23,7 @@ 
 &uart1 {
 	status = "okay";
 };
+
+&timer0 {
+	status = "okay";
+};
diff --git a/arch/arm/dts/rp2350.dtsi b/arch/arm/dts/rp2350.dtsi
index 56cf1d37b09..6ae72b531cc 100644
--- a/arch/arm/dts/rp2350.dtsi
+++ b/arch/arm/dts/rp2350.dtsi
@@ -33,5 +33,19 @@ 
 			clock = <150000000>;	/* CLK_PERI = clk_sys 150MHz */
 			status = "disabled";
 		};
+
+		timer0: timer@400b0000 {
+			compatible = "raspberrypi,rp2350-timer";
+			reg = <0x400b0000 0x1000>;
+			clock-frequency = <1000000>;	/* 1MHz microsecond tick */
+			status = "disabled";
+		};
+
+		timer1: timer@400b8000 {
+			compatible = "raspberrypi,rp2350-timer";
+			reg = <0x400b8000 0x1000>;
+			clock-frequency = <1000000>;	/* 1MHz microsecond tick */
+			status = "disabled";
+		};
 	};
 };
diff --git a/arch/arm/include/asm/arch-rp2xxx/rp2350.h b/arch/arm/include/asm/arch-rp2xxx/rp2350.h
index 738fa20be96..cc87f27328f 100644
--- a/arch/arm/include/asm/arch-rp2xxx/rp2350.h
+++ b/arch/arm/include/asm/arch-rp2xxx/rp2350.h
@@ -119,11 +119,13 @@  struct rp2350_resets_regs {
 
 #define RP2350_RESETS           ((struct rp2350_resets_regs *)RP2350_RESETS_BASE)
 
-/* Reset bits for peripherals we care about */
+/* Reset bits for peripherals */
 #define RESETS_RESET_UART0      BIT(26)
 #define RESETS_RESET_UART1      BIT(27)
 #define RESETS_RESET_PLL_SYS    BIT(14)
 #define RESETS_RESET_PLL_USB    BIT(15)
+#define RESETS_RESET_TIMER0     BIT(23)
+#define RESETS_RESET_TIMER1     BIT(24)
 
 /*
  * XOSC (Crystal Oscillator) register map
@@ -330,6 +332,18 @@  struct rp2350_uart_regs {
 #define RP2350_UART_IBRD_115200         81
 #define RP2350_UART_FBRD_115200         24
 
+/*
+ * Tick generator - see Datasheet section 8.5.1
+ * Each consumer (TIMER0/1, watchdog, etc) has its own CTRL/CYCLES/COUNT
+ * triplet. The generators are clocked from clk_ref, so a CYCLES divisor of
+ * (clk_ref_hz / 1MHz) produces the 1MHz (1us) tick the timers expect.
+ */
+#define RP2350_TICKS_TIMER0_CTRL    0x18
+#define RP2350_TICKS_TIMER0_CYCLES  0x1c
+#define RP2350_TICKS_TIMER1_CTRL    0x24
+#define RP2350_TICKS_TIMER1_CYCLES  0x28
+#define TICKS_CTRL_ENABLE           BIT(0)
+
 /*
  * Atomic register access
  * RP2350 (like RP2040) provides atomic set/clear/xor aliases
@@ -346,6 +360,7 @@  struct rp2350_uart_regs {
 
 void rp2350_soc_init(void);
 void rp2350_uart_early_init(void);
+void rp2350_timer_init(void);
 
 #endif /* ! __ASSEMBLY__ */
 
diff --git a/arch/arm/mach-rp2xxx/rp2350.c b/arch/arm/mach-rp2xxx/rp2350.c
index 69ef2daa89b..a871f0e55dc 100644
--- a/arch/arm/mach-rp2xxx/rp2350.c
+++ b/arch/arm/mach-rp2xxx/rp2350.c
@@ -190,6 +190,35 @@  void rp2350_uart_early_init(void)
 	writel(UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE, &uart->cr);
 }
 
+/*
+ * rp2350_timer_init() - Enable the TIMER0/TIMER1 microsecond timer
+ *
+ * The RP2350 64-bit system timer needs an external 1MHz tick. Unlike RP2040,
+ * the RP2350 does not auto-start this tick, so the TICKS block must be
+ * programmed here. The tick is driven from clk_ref (XOSC = 12MHz), so a
+ * CYCLES divisor of 12 yields a 1MHz (1us) tick. See datasheet section 12.8.1
+ */
+void rp2350_timer_init(void)
+{
+	struct rp2350_resets_regs *resets = RP2350_RESETS;
+
+	/* Release TIMER0 from reset */
+	rp2350_reg_clr(&resets->reset, RESETS_RESET_TIMER0);
+	rp2350_wait_reset_done(RESETS_RESET_TIMER0);
+
+	/* Release TIMER1 from reset */
+	rp2350_reg_clr(&resets->reset, RESETS_RESET_TIMER1);
+	rp2350_wait_reset_done(RESETS_RESET_TIMER1);
+
+	/* Start the TIMER0 tick generator at 1MHz */
+	writel(RP2350_XOSC_HZ / 1000000, RP2350_TICKS_BASE + RP2350_TICKS_TIMER0_CYCLES);
+	writel(TICKS_CTRL_ENABLE, RP2350_TICKS_BASE + RP2350_TICKS_TIMER0_CTRL);
+
+	/* Start the TIMER1 tick generator at 1MHz */
+	writel(RP2350_XOSC_HZ / 1000000, RP2350_TICKS_BASE + RP2350_TICKS_TIMER1_CYCLES);
+	writel(TICKS_CTRL_ENABLE, RP2350_TICKS_BASE + RP2350_TICKS_TIMER1_CTRL);
+}
+
 /*
  * rp2350_soc_init() - Full SoC initialization sequence
  *
@@ -210,4 +239,6 @@  void rp2350_soc_init(void)
 	rp2350_clocks_init();
 	/* Now init UART - it will source clk_peri from clk_sys */
 	rp2350_uart_early_init();
+	/* Start the 1MHz microsecond timer (TIMER0) */
+	rp2350_timer_init();
 }
diff --git a/configs/rpi_pico2_defconfig b/configs/rpi_pico2_defconfig
index 51e4361fdbd..963e7b1893e 100644
--- a/configs/rpi_pico2_defconfig
+++ b/configs/rpi_pico2_defconfig
@@ -27,3 +27,4 @@  CONFIG_SPECIFY_CONSOLE_INDEX=y
 CONFIG_DM_SERIAL=y
 CONFIG_PL01X_SERIAL=y
 CONFIG_TIMER=y
+CONFIG_RP2350_TIMER=y
diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 500a25638a9..8a73b4a67b5 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -348,4 +348,12 @@  config GOLDFISH_TIMER
 	  It uses the Goldfish RTC hardware to provide a nanosecond-resolution
 	  timer, commonly found in QEMU virt machines.
 
+config RP2350_TIMER
+	bool "Raspberry Pi RP2350 timer support"
+	depends on TIMER
+	help
+	  Select this to enable support for the 64-bit microsecond timers
+	  found on the Raspberry Pi RP2350 SoC. The timer is fed a 1MHz tick
+	  by the SoC tick generator.
+
 endmenu
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index d8b3f2b65d4..96f995c5bca 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -37,3 +37,4 @@  obj-$(CONFIG_IMX_GPT_TIMER)	+= imx-gpt-timer.o
 obj-$(CONFIG_XILINX_TIMER)	+= xilinx-timer.o
 obj-$(CONFIG_STARFIVE_TIMER)	+= starfive-timer.o
 obj-$(CONFIG_GOLDFISH_TIMER)	+= goldfish_timer.o
+obj-$(CONFIG_RP2350_TIMER)	+= rp2350_timer.o
diff --git a/drivers/timer/rp2350_timer.c b/drivers/timer/rp2350_timer.c
new file mode 100644
index 00000000000..6c7487124db
--- /dev/null
+++ b/drivers/timer/rp2350_timer.c
@@ -0,0 +1,62 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Timer driver for the Raspberry Pi RP2350 64-bit microsecond timers
+ *
+ * Copyright (C) 2026 Bhargav Das <bhargavdas100@gmail.com>
+ */
+
+#include <dm.h>
+#include <timer.h>
+#include <asm/io.h>
+
+#define RP2350_TIMER_TIMEHR	0x08	/* Latched high 32 bits of time */
+#define RP2350_TIMER_TIMELR	0x0c	/* Latched low 32 bits of time */
+
+struct rp2350_timer_priv {
+	void __iomem *base;
+};
+
+static u64 rp2350_timer_get_count(struct udevice *dev)
+{
+	struct rp2350_timer_priv *priv = dev_get_priv(dev);
+	u32 hi, lo;
+
+	/*
+	 * Reading TIMELR latches the current value of TIMEHR, so the low
+	 * word must always be read before the high word to obtain a coherent
+	 * 64-bit value.
+	 */
+	lo = readl(priv->base + RP2350_TIMER_TIMELR);
+	hi = readl(priv->base + RP2350_TIMER_TIMEHR);
+
+	return ((u64)hi << 32) | lo;
+}
+
+static int rp2350_timer_probe(struct udevice *dev)
+{
+	struct rp2350_timer_priv *priv = dev_get_priv(dev);
+
+	priv->base = dev_read_addr_ptr(dev);
+	if (!priv->base)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct timer_ops rp2350_timer_ops = {
+	.get_count = rp2350_timer_get_count,
+};
+
+static const struct udevice_id rp2350_timer_ids[] = {
+	{ .compatible = "raspberrypi,rp2350-timer" },
+	{ }
+};
+
+U_BOOT_DRIVER(rp2350_timer) = {
+	.name		= "rp2350_timer",
+	.id			= UCLASS_TIMER,
+	.of_match	= rp2350_timer_ids,
+	.priv_auto	= sizeof(struct rp2350_timer_priv),
+	.probe		= rp2350_timer_probe,
+	.ops		= &rp2350_timer_ops,
+};