|
| 1 | +From: Markus Stockhausen < [email protected]> |
| 2 | +Date: Sat, 19 Jul 2025 18:22:21 +0200 |
| 3 | +Subject: [PATCH] realtek: fix stall after restart of otto timer |
| 4 | + |
| 5 | +With kernel 6.9 the kernel scheduler has been redesigned. This uncovered |
| 6 | +a bug in the realtek timer hardware and a misconception in the driver. |
| 7 | + |
| 8 | +Regarding the driver: Software cannot set the current counter value to |
| 9 | +zero directly. This is automatically done when writing a new target value. |
| 10 | +Drop function rttm_set_counter(). Additionally do not use stop timer |
| 11 | +during normal operation because it acknowledges interrupts. This should |
| 12 | +only be done from the interrupt handler. Replace this with disable_timer(). |
| 13 | + |
| 14 | +Regarding the hardware: There is a minimal chance that a timer dies if it |
| 15 | +is reprogrammed within the 5us before its expiration time. Let's call this |
| 16 | +the "critical time window". Work around this issue by introducing a |
| 17 | +bounce() function. It restarts the timer directly before the normal |
| 18 | +restart functions as follows: |
| 19 | + |
| 20 | +- Stop timer |
| 21 | +- Restart timer with a slow frequency. |
| 22 | +- Target time will be >5us |
| 23 | +- The subsequent normal restart will be outside the critical window |
| 24 | + |
| 25 | +While we are here clarify documentation and double the timer frequency to |
| 26 | +6.25 Mhz. This allows for more detailed timestamps. |
| 27 | + |
| 28 | +Signed-off-by: Markus Stockhausen < [email protected]> |
| 29 | +--- |
| 30 | + |
| 31 | +--- a/drivers/clocksource/timer-rtl-otto.c |
| 32 | ++++ b/drivers/clocksource/timer-rtl-otto.c |
| 33 | +@@ -25,12 +25,11 @@ |
| 34 | + |
| 35 | + /* |
| 36 | + * The Otto platform provides multiple 28 bit timers/counters with the following |
| 37 | +- * operating logic. If enabled the timer counts up. Per timer one can set a |
| 38 | +- * maximum counter value as an end marker. If end marker is reached the timer |
| 39 | +- * fires an interrupt. If the timer "overflows" by reaching the end marker or |
| 40 | +- * by adding 1 to 0x0fffffff the counter is reset to 0. When this happens and |
| 41 | +- * the timer is in operating mode COUNTER it stops. In mode TIMER it will |
| 42 | +- * continue to count up. |
| 43 | ++ * operating logic. If enabled the timer counts up. Per timer a counter target |
| 44 | ++ * value can be set with the minimum being 0x2 and the maximumu being 0xfffffff. |
| 45 | ++ * If the the target value is reached the timer is reset to 0. Depending on its |
| 46 | ++ * configuration the timer will then fire an interrupt. In case the timer is in |
| 47 | ++ * operating mode COUNTER it stops. In mode TIMER it will continue to count up. |
| 48 | + */ |
| 49 | + #define RTTM_CTRL_COUNTER 0 |
| 50 | + #define RTTM_CTRL_TIMER BIT(24) |
| 51 | +@@ -38,16 +37,15 @@ |
| 52 | + #define RTTM_BIT_COUNT 28 |
| 53 | + #define RTTM_MIN_DELTA 8 |
| 54 | + #define RTTM_MAX_DELTA CLOCKSOURCE_MASK(28) |
| 55 | ++#define RTTM_MAX_DIVISOR GENMASK(15, 0) |
| 56 | + |
| 57 | + /* |
| 58 | +- * Timers are derived from the LXB clock frequency. Usually this is a fixed |
| 59 | +- * multiple of the 25 MHz oscillator. The 930X SOC is an exception from that. |
| 60 | +- * Its LXB clock has only dividers and uses the switch PLL of 2.45 GHz as its |
| 61 | +- * base. The only meaningful frequencies we can achieve from that are 175.000 |
| 62 | +- * MHz and 153.125 MHz. The greatest common divisor of all explained possible |
| 63 | +- * speeds is 3125000. Pin the timers to this 3.125 MHz reference frequency. |
| 64 | ++ * Timers are derived from the lexra bus (LXB) clock frequency. This is 175 MHz |
| 65 | ++ * on RTL930x and 200 MHz on the other platforms. With 6.25 MHz choose a common |
| 66 | ++ * divisor to have enough range and detail. This even allows to compare the |
| 67 | ++ * different platforms more easily. |
| 68 | + */ |
| 69 | +-#define RTTM_TICKS_PER_SEC 3125000 |
| 70 | ++#define RTTM_TICKS_PER_SEC 6250000 |
| 71 | + |
| 72 | + struct rttm_cs { |
| 73 | + struct timer_of to; |
| 74 | +@@ -55,11 +53,6 @@ struct rttm_cs { |
| 75 | + }; |
| 76 | + |
| 77 | + /* Simple internal register functions */ |
| 78 | +-static inline void rttm_set_counter(void __iomem *base, unsigned int counter) |
| 79 | +-{ |
| 80 | +- iowrite32(counter, base + RTTM_CNT); |
| 81 | +-} |
| 82 | +- |
| 83 | + static inline unsigned int rttm_get_counter(void __iomem *base) |
| 84 | + { |
| 85 | + return ioread32(base + RTTM_CNT); |
| 86 | +@@ -112,6 +105,22 @@ static irqreturn_t rttm_timer_interrupt( |
| 87 | + return IRQ_HANDLED; |
| 88 | + } |
| 89 | + |
| 90 | ++static void rttm_bounce_timer(void __iomem *base, u32 mode) |
| 91 | ++{ |
| 92 | ++ /* |
| 93 | ++ * When a running timer has less than ~5us left, a stop/start sequence |
| 94 | ++ * might fail. While the details are unknown the most evident effect is |
| 95 | ++ * that the subsequent interrupt will not be fired. |
| 96 | ++ * |
| 97 | ++ * As a workaround issue an intermediate restart with a very slow |
| 98 | ++ * frequency of ~3kHz keeping the target value. So the actual follow |
| 99 | ++ * up restart will always be issued outside the critical window. |
| 100 | ++ */ |
| 101 | ++ |
| 102 | ++ rttm_disable_timer(base); |
| 103 | ++ rttm_enable_timer(base, mode, RTTM_MAX_DIVISOR); |
| 104 | ++} |
| 105 | ++ |
| 106 | + static void rttm_stop_timer(void __iomem *base) |
| 107 | + { |
| 108 | + rttm_disable_timer(base); |
| 109 | +@@ -120,7 +129,6 @@ static void rttm_stop_timer(void __iomem |
| 110 | + |
| 111 | + static void rttm_start_timer(struct timer_of *to, u32 mode) |
| 112 | + { |
| 113 | +- rttm_set_counter(to->of_base.base, 0); |
| 114 | + rttm_enable_timer(to->of_base.base, mode, to->of_clk.rate / RTTM_TICKS_PER_SEC); |
| 115 | + } |
| 116 | + |
| 117 | +@@ -129,7 +137,8 @@ static int rttm_next_event(unsigned long |
| 118 | + struct timer_of *to = to_timer_of(clkevt); |
| 119 | + |
| 120 | + RTTM_DEBUG(to->of_base.base); |
| 121 | +- rttm_stop_timer(to->of_base.base); |
| 122 | ++ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_COUNTER); |
| 123 | ++ rttm_disable_timer(to->of_base.base); |
| 124 | + rttm_set_period(to->of_base.base, delta); |
| 125 | + rttm_start_timer(to, RTTM_CTRL_COUNTER); |
| 126 | + |
| 127 | +@@ -141,7 +150,8 @@ static int rttm_state_oneshot(struct clo |
| 128 | + struct timer_of *to = to_timer_of(clkevt); |
| 129 | + |
| 130 | + RTTM_DEBUG(to->of_base.base); |
| 131 | +- rttm_stop_timer(to->of_base.base); |
| 132 | ++ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_COUNTER); |
| 133 | ++ rttm_disable_timer(to->of_base.base); |
| 134 | + rttm_set_period(to->of_base.base, RTTM_TICKS_PER_SEC / HZ); |
| 135 | + rttm_start_timer(to, RTTM_CTRL_COUNTER); |
| 136 | + |
| 137 | +@@ -153,7 +163,8 @@ static int rttm_state_periodic(struct cl |
| 138 | + struct timer_of *to = to_timer_of(clkevt); |
| 139 | + |
| 140 | + RTTM_DEBUG(to->of_base.base); |
| 141 | +- rttm_stop_timer(to->of_base.base); |
| 142 | ++ rttm_bounce_timer(to->of_base.base, RTTM_CTRL_TIMER); |
| 143 | ++ rttm_disable_timer(to->of_base.base); |
| 144 | + rttm_set_period(to->of_base.base, RTTM_TICKS_PER_SEC / HZ); |
| 145 | + rttm_start_timer(to, RTTM_CTRL_TIMER); |
| 146 | + |
0 commit comments