realtek: fix stall after restart of otto timer
Some checks are pending
Build Kernel / Build all affected Kernels (push) Waiting to run

Once tested this will go upstream.

Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
Link: https://github.com/openwrt/openwrt/pull/19468
Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
Markus Stockhausen 2025-07-19 16:14:28 -04:00 committed by Robert Marko
parent 522294eeef
commit f21475839f

View file

@ -0,0 +1,146 @@
From: Markus Stockhausen <markus.stockhausen@gmx.de>
Date: Sat, 19 Jul 2025 18:22:21 +0200
Subject: [PATCH] realtek: fix stall after restart of otto timer
With kernel 6.9 the kernel scheduler has been redesigned. This uncovered
a bug in the realtek timer hardware and a misconception in the driver.
Regarding the driver: Software cannot set the current counter value to
zero directly. This is automatically done when writing a new target value.
Drop function rttm_set_counter(). Additionally do not use stop timer
during normal operation because it acknowledges interrupts. This should
only be done from the interrupt handler. Replace this with disable_timer().
Regarding the hardware: There is a minimal chance that a timer dies if it
is reprogrammed within the 5us before its expiration time. Let's call this
the "critical time window". Work around this issue by introducing a
bounce() function. It restarts the timer directly before the normal
restart functions as follows:
- Stop timer
- Restart timer with a slow frequency.
- Target time will be >5us
- The subsequent normal restart will be outside the critical window
While we are here clarify documentation and double the timer frequency to
6.25 Mhz. This allows for more detailed timestamps.
Signed-off-by: Markus Stockhausen <markus.stockhausen@gmx.de>
---
--- a/drivers/clocksource/timer-rtl-otto.c
+++ b/drivers/clocksource/timer-rtl-otto.c
@@ -25,12 +25,11 @@
/*
* The Otto platform provides multiple 28 bit timers/counters with the following
- * operating logic. If enabled the timer counts up. Per timer one can set a
- * maximum counter value as an end marker. If end marker is reached the timer
- * fires an interrupt. If the timer "overflows" by reaching the end marker or
- * by adding 1 to 0x0fffffff the counter is reset to 0. When this happens and
- * the timer is in operating mode COUNTER it stops. In mode TIMER it will
- * continue to count up.
+ * operating logic. If enabled the timer counts up. Per timer a counter target
+ * value can be set with the minimum being 0x2 and the maximumu being 0xfffffff.
+ * If the the target value is reached the timer is reset to 0. Depending on its
+ * configuration the timer will then fire an interrupt. In case the timer is in
+ * operating mode COUNTER it stops. In mode TIMER it will continue to count up.
*/
#define RTTM_CTRL_COUNTER 0
#define RTTM_CTRL_TIMER BIT(24)
@@ -38,16 +37,15 @@
#define RTTM_BIT_COUNT 28
#define RTTM_MIN_DELTA 8
#define RTTM_MAX_DELTA CLOCKSOURCE_MASK(28)
+#define RTTM_MAX_DIVISOR GENMASK(15, 0)
/*
- * Timers are derived from the LXB clock frequency. Usually this is a fixed
- * multiple of the 25 MHz oscillator. The 930X SOC is an exception from that.
- * Its LXB clock has only dividers and uses the switch PLL of 2.45 GHz as its
- * base. The only meaningful frequencies we can achieve from that are 175.000
- * MHz and 153.125 MHz. The greatest common divisor of all explained possible
- * speeds is 3125000. Pin the timers to this 3.125 MHz reference frequency.
+ * Timers are derived from the lexra bus (LXB) clock frequency. This is 175 MHz
+ * on RTL930x and 200 MHz on the other platforms. With 6.25 MHz choose a common
+ * divisor to have enough range and detail. This even allows to compare the
+ * different platforms more easily.
*/
-#define RTTM_TICKS_PER_SEC 3125000
+#define RTTM_TICKS_PER_SEC 6250000
struct rttm_cs {
struct timer_of to;
@@ -55,11 +53,6 @@ struct rttm_cs {
};
/* Simple internal register functions */
-static inline void rttm_set_counter(void __iomem *base, unsigned int counter)
-{
- iowrite32(counter, base + RTTM_CNT);
-}
-
static inline unsigned int rttm_get_counter(void __iomem *base)
{
return ioread32(base + RTTM_CNT);
@@ -112,6 +105,22 @@ static irqreturn_t rttm_timer_interrupt(
return IRQ_HANDLED;
}
+static void rttm_bounce_timer(void __iomem *base, u32 mode)
+{
+ /*
+ * When a running timer has less than ~5us left, a stop/start sequence
+ * might fail. While the details are unknown the most evident effect is
+ * that the subsequent interrupt will not be fired.
+ *
+ * As a workaround issue an intermediate restart with a very slow
+ * frequency of ~3kHz keeping the target value. So the actual follow
+ * up restart will always be issued outside the critical window.
+ */
+
+ rttm_disable_timer(base);
+ rttm_enable_timer(base, mode, RTTM_MAX_DIVISOR);
+}
+
static void rttm_stop_timer(void __iomem *base)
{
rttm_disable_timer(base);
@@ -120,7 +129,6 @@ static void rttm_stop_timer(void __iomem
static void rttm_start_timer(struct timer_of *to, u32 mode)
{
- rttm_set_counter(to->of_base.base, 0);
rttm_enable_timer(to->of_base.base, mode, to->of_clk.rate / RTTM_TICKS_PER_SEC);
}
@@ -129,7 +137,8 @@ static int rttm_next_event(unsigned long
struct timer_of *to = to_timer_of(clkevt);
RTTM_DEBUG(to->of_base.base);
- rttm_stop_timer(to->of_base.base);
+ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_COUNTER);
+ rttm_disable_timer(to->of_base.base);
rttm_set_period(to->of_base.base, delta);
rttm_start_timer(to, RTTM_CTRL_COUNTER);
@@ -141,7 +150,8 @@ static int rttm_state_oneshot(struct clo
struct timer_of *to = to_timer_of(clkevt);
RTTM_DEBUG(to->of_base.base);
- rttm_stop_timer(to->of_base.base);
+ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_COUNTER);
+ rttm_disable_timer(to->of_base.base);
rttm_set_period(to->of_base.base, RTTM_TICKS_PER_SEC / HZ);
rttm_start_timer(to, RTTM_CTRL_COUNTER);
@@ -153,7 +163,8 @@ static int rttm_state_periodic(struct cl
struct timer_of *to = to_timer_of(clkevt);
RTTM_DEBUG(to->of_base.base);
- rttm_stop_timer(to->of_base.base);
+ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_TIMER);
+ rttm_disable_timer(to->of_base.base);
rttm_set_period(to->of_base.base, RTTM_TICKS_PER_SEC / HZ);
rttm_start_timer(to, RTTM_CTRL_TIMER);