From: Yassine Oudjana <yassine.oudjana@xxxxxxxxx>
On Mon, 9 May 2022 23:07:40 +0200, AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx> wrote:
Some MediaTek platforms with a buggy TrustZone ATF firmware will not
initialize the AArch64 System Timer correctly: in these cases, the
System Timer address is correctly programmed, as well as the CNTFRQ_EL0
register (reading 13MHz, as it should be), but the assigned hardware
timers are never started before (or after) booting Linux.
In this condition, any call to function get_cycles() will be returning
zero, as CNTVCT_EL0 will always read zero.
I spent a lot of time trying to figure out why the arch timer didn't
work on MT6737T and never got any results. Turns out this is why...
I ended up using the GPT (@ 0x10004000) as a system timer and it
worked fine.
With this patch the arch timer started to work finally. Thanks for
the fix! See below for one comment on this patch.
One common critical symptom of that is trying to use the udelay()
function (calling __delay()), which executes the following loop:
start = get_cycles();
while ((get_cycles() - start) < cycles)
cpu_relax();
which, when CNTVCT_EL0 always reads zero, translates to:
while((0 - 0) < 0) ==> while(0 < 0)
... generating an infinite loop, even though zero is never less
than zero, but always equal to it (this has to be researched,
but it's out of the scope of this commit).
To fix this issue on the affected MediaTek platforms, the solution
is to simply start the timers that are designed to be System Timer(s).
These timers, downstream, are called "CPUXGPT" and there is one
timer per CPU core; luckily, it is not necessary to set a start bit
on each CPUX General Purpose Timer, but it's conveniently enough to:
- Set the clock divider (input = 26MHz, divider = 2, output = 13MHz);
- Set the ENABLE bit on a global register (starts all CPUX timers).
The only small hurdle with this setup is that it's all done through
the MCUSYS wrapper, where it is needed, for each read or write, to
select a register address (by writing it to an index register) and
then to perform any R/W on a "CON" register.
For example, writing "0x1" to the CPUXGPT register offset 0x4:
- Write 0x4 to mcusys INDEX register
- Write 0x1 to mcusys CON register
Reading from CPUXGPT register offset 0x4:
- Write 0x4 to mcusys INDEX register
- Read mcusys CON register.
Finally, starting this timer makes platforms affected by this issue
to work correctly.
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@xxxxxxxxxxxxx>
---
drivers/clocksource/timer-mediatek.c | 119 +++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
diff --git a/drivers/clocksource/timer-mediatek.c b/drivers/clocksource/timer-mediatek.c
index 7bcb4a3f26fb..a3e90047f9ac 100644
--- a/drivers/clocksource/timer-mediatek.c
+++ b/drivers/clocksource/timer-mediatek.c
+
+ /*
+ * Check if we're given a clock with the right frequency for this
+ * timer, otherwise warn but keep going with the setup anyway, as
+ * that makes it possible to still boot the kernel, even though
+ * it may not work correctly (random lockups, etc).
+ * The reason behind this is that having an early UART may not be
+ * possible for everyone and this gives a chance to retrieve kmsg
+ * for eventual debugging even on consumer devices.
+ */
+ freq = timer_of_rate(&to_cpux);
+ if (freq > 13000000)
Input clock is 26MHz and is then divided by 2 in CPUXGPT, so shouldn't
this be 26000000 instead? I get a warning here with 26MHz system clock
supplied:
clocks {
...
clk26m: clk26m {
compatible = "fixed-clock";
clock-frequency = <26000000>;
#clock-cells = <0>;
};
...
};
...
soc {
...
cpuxgpt: timer@10200670 {
compatible = "mediatek,mt6795-systimer";
reg = <0 0x10200670 0 0x8>;