Variable refresh on the Nova¶
The goal is integer-multiple variable refresh. Every game frame is scanned exactly twice, and the panel follows the game at twice its rate: PAL at 100 Hz, NTSC at 119.88 Hz, SwanStation's 59.826 at 119.652 Hz. That needs no panel mode per console, and nothing outside 100 to 121 Hz. The panel takes a frame length that changes every frame inside that range (measured below), and patch 1097 exposes it through DRM's standard variable-refresh properties.
What the hardware offers¶
- Android drives the panel, an il97680a 4.5" 1280x960 AMOLED, in command mode with the TE pin. It has two timings, 120 and 60 Hz, switched by DCS register 0x60, and no Qsync properties or commands. The same device tree enables Qsync for Qualcomm's reference panels.
- PortareOS drives it in video mode (kernel patch 0105). The per-console modes change the pixel clock.
- The DPU's INTF has AVR, the block Qualcomm's downstream driver uses
for Qsync in video mode: the vertical front porch runs on until a
trigger, up to a set frame length. Mainline defines its registers and
programs none. Downstream writes the longest frame to
AVR_VTOTAL, sets bit 0 ofAVR_CONTROLand bits 0 and 8 ofAVR_MODEfor one-shot, and writesAVR_TRIGGERafter every commit. The SM8550 driver also setsAVR_SUPPORT_ENABLE, bit 29 of the DSI host's video mode control (DSI_VIDEO_MODE_CTRL, mainline'sREG_DSI_VID_CFG0plus the 6G shift, 0xae94010 on the Nova), when it switches Qsync on. - Kernel patch 1097 adds it as DRM's standard interface:
- The panel driver declares its range, 60 Hz up to its fastest mode,
120.198 Hz rounded up to 121, in the connector's
display_info.monitor_rangeand setsvrr_capable, which DSI connectors now carry. - At mode set the DPU writes the longest frame, from the range's minimum.
- The CRTC's
VRR_ENABLEDswitches AVR on and off in a commit, without a mode set, with the INTF in that commit's flush and the DSI host's bit 29 alongside, in continuous mode. - The kernel logs
intf1: variable refresh onandoff. From DPU 8.1, bit 31 ofAVR_CONTROLreads 1 while AVR is active.
Measured, 2026-10-07¶
A stretched front porch, static¶
vrr-probe static phases: the preferred mode's 156.24 MHz pixel clock and
a longer vertical front porch, set through ordinary mode setting.
| Rate | Front porch, lines | Frames in 10 s |
|---|---|---|
| 119.88 Hz | 12 | 1200 |
| 110 Hz | 102 | 1100 |
| 100 Hz | 211 | 1001 |
| 90 Hz | 344 | 901 |
| 80 Hz | 511 | 801 |
| 72 Hz | 678 | 721 |
| 60 Hz | 1011 | 600 |
Every rate ran exactly, with no DSI or DPU error. By eye, at normal brightness, the picture was stable with no flicker at every rate. Low brightness is not checked yet.
The panel's TE pin, GPIO 86, cannot be read as a GPIO: the pin controller
refuses it while the DSI holds it as mdp_vsync.
Frames on a schedule, fixed refresh¶
vrr-probe dynamic phases on the 120.198 Hz mode, without AVR. The
start is the kernel's flip timestamp against the scheduled commit.
| Schedule | On the 8.32 ms grid | Start after schedule, median | Frame lengths |
|---|---|---|---|
| 120 Hz | 100% | 5.7 ms | 8.3 ms |
| 119.652 Hz | 100% | 6.3 ms | 8.3 ms |
| 110 Hz | 100% | 5.8 ms | 8.3 ms, 16.6 ms in 1 of 11 |
| 100 Hz | 100% | 5.8 ms | 8.3 ms, 16.6 ms in 1 of 5 |
| 100 and 120 alternating | 100% | 5.8 ms | 8.3 ms, 16.6 ms in 1 of 10 |
| Random, 8.34 to 10 ms | 100% | 5.6 ms | 8.3 ms, 16.6 ms in 1 of 10 |
| Game frames of 16.7 to 20 ms, each twice | 100% | 5.7 ms | 8.3 ms, 16.6 ms in 1 of 10 |
With AVR the same schedules should start frames off the grid, within about a millisecond of each commit, and the frame lengths should follow the schedule.
AVR switched on at mode set: inactive¶
The first version of patch 1097 enabled AVR at mode set, before the
timing engine started. The registers read back as programmed:
AVR_CONTROL 0x1, AVR_MODE 0x101, AVR_VTOTAL 1250 lines. The status
bit stayed clear, frames ran at 119.98 Hz with no commits instead of the
96 Hz floor, and every dynamic schedule landed on the 8.32 ms grid as on
the fixed refresh. No flicker was seen, but on fixed timing.
Switched on in the first commit after the mode set, with the INTF in
that flush, it stayed inactive the same way: status bit clear, 119.5
frames/s with no commits. The DSI host's AVR_SUPPORT_ENABLE was still
clear (0x10009130); the patch now sets it.
With the DSI bit set (0x30009130), AVR stayed inactive again: status
bit clear, 120.5 frames/s with no commits. One difference left in plain
sight: downstream always enables programmable fetch in video mode, at
least one line ("prog fetch always enabled case"); mainline turns it off
when the back porch is long enough, as here (PROG_FETCH_START 0). The
patch now carries that and the other candidates as parameters, so one
build can try them all:
| Parameter | Default | What it changes |
|---|---|---|
dpu_avr_prog_fetch |
1 | Programmable fetch lines while AVR is wanted, where the porches need none |
dpu_avr_continuous |
Y | Continuous AVR instead of one-shot |
dpu_avr_at_modeset |
N | Switch AVR on at mode set instead of in the first commit |
dpu_avr_flush_intf |
N | Flush the INTF on every commit |
dpu_avr_dsi |
Y | The DSI host's AVR support bit |
All were under /sys/module/msm/parameters/. Once continuous mode was
found they came out again, for the interface above; the measurements
below used them.
AVR working: continuous mode¶
On the kernel with the parameters (kernel-only build, 2026-10-07), the idle check per combination, floor 96:
| Combination | AVR_CONTROL |
Idle frames/s |
|---|---|---|
| One-shot, any fetch, DSI bit, enable point or per-commit flush | 0x00000001 | 120 |
| Continuous, with fetch and DSI bit | 0x80000001 | 96 to 97 |
| Continuous, without programmable fetch | 0x80000001 | 96.1 |
| Continuous, without the DSI bit | 0x80000001 | 95.9 |
Continuous mode alone engages AVR; one-shot, downstream's Qsync default, never did.
Frames on a schedule, continuous, floor 90, the probe's own receive times against its commits:
| Schedule | Commit to frame, median | Frame length error, median | p95 |
|---|---|---|---|
| 120 Hz | 1.11 ms | 0.03 ms | 0.46 ms |
| 119.652 Hz | 0.85 ms | 0.05 ms | 0.40 ms |
| 110 Hz | 0.50 ms | 0.19 ms | 0.72 ms |
| 100 Hz | 0.78 ms | 0.27 ms | 1.56 ms |
| 100 and 120 alternating | 0.66 ms | 0.20 ms | 0.87 ms |
| Random, 8.34 to 10 ms | 0.36 ms | 0.25 ms | 0.86 ms |
| Game frames of 16.7 to 20 ms, each twice | 0.32 ms | 0.11 ms | 0.66 ms |
On the fixed refresh the same 100 Hz schedule had 4.9 ms from commit to frame and a p95 error of 7.1 ms. A floor of 96 was too tight for 100 Hz: its longest frame, 10.4 ms, left 0.4 ms for a commit to get through the kernel, and missed frames fell back onto the 8.3 ms grid (p95 error 9.6 ms). Floor 90 gives 1.1 ms.
By eye, 2026-10-08, with AVR on (floor 90, continuous) and the backlight at 172 of 3445: no flicker or brightness change in any schedule, the alternating 100 and 120 Hz one included. That run's log confirms AVR was on: 100 Hz frames came 9.9 to 10.1 ms apart, p95 error 0.71 ms.
The kernel's flip timestamps are not usable under AVR: DRM derives them from the scanout position against the mode's fixed frame length, and two flips often came back with the same one. Mesa's and RetroArch's frame timing read those timestamps, so a variable-refresh path has to fix them or work without them.
Running the AVR test¶
On a build with patch 1097. vrr-probe is in the debug set, which only
unofficial builds carry; elsewhere, copy the static build to /storage.
Its fourth argument, vrr, sets the CRTC's VRR_ENABLED for the run and
clears it on the way out; it prints the connector's vrr_capable first.
First, whether AVR holds the front porch at all. idle commits once
and then nothing, so frames should slow to the floor:
systemctl stop portarelauncher
vrr-probe /dev/null 6 idle vrr &
sleep 2
devmem 0xae36270 32 # 0x80000001: AVR active
F=0xae360ac; a=$(devmem $F 32); sleep 2; b=$(devmem $F 32)
echo $(( (b - a) / 2 )) frames/s # about 60, not 120
wait
Then the schedules:
vrr-probe /storage/vrr-avr.csv 10 dynamic vrr
vrr-probe /dev/null 1 reset
systemctl start portarelauncher
The kernel logs intf1: variable refresh on when the CRTC property takes
effect. The big digit is the phase, 7 to 14, and the grey number
the rate (0 for a pattern). Watch the dark grey patches for flicker,
especially in phase 11, where the frame length alternates every frame,
and compare brightness with phase 7. Then repeat at minimum brightness.
Through the interface, 2026-10-08¶
Kernel-only build of patch 1097 with the DRM interface, no parameters:
vrr_capable reads 1 on the DSI connector. With VRR_ENABLED set and
nothing committed, AVR_CONTROL read 0x80000001, the DSI bit was set and
frames ran at 90.01/s, the floor. Clearing VRR_ENABLED, without a mode
set, turned both off again: 119.92 frames/s. The schedules matched the
runs with the parameters; at 100 Hz the median frame-length error was
0.16 ms (p95 0.56 ms).
A schedule that starts behind the frame boundary catches up only by the margin between its frame and the mode's shortest: at 120 Hz on the 120.198 Hz mode that is 14 us a frame, and one run started a whole frame behind (8.3 ms from commit to frame) and was still 4 ms behind after four seconds. At 119.652 Hz the margin is 38 us; lower rates recover faster.
Integer multiples for every game¶
AVR's shortest frame is the frame of the mode in use, so a doubled rate fits only up to that mode's rate. With variable refresh on, the mode has to be the fastest one, 120.198 Hz, twice the SNES. On the 119.652 Hz mode, the PlayStation's, doubled SNES would not fit.
The multiple for a game is the largest k with k times its rate at most
120.198 Hz and at least the 65 Hz floor: 2 for 40.07 to 60.1 Hz, 3 for
30.05 to 40.07 Hz, 4 for 24.04 to 30.05 Hz, and so on. The floor is well
under half the fastest refresh, so every rate below 60.1 Hz has one; at
90 Hz, 40.07 to 45 Hz had none. DRM keeps monitor_range inside the kernel
for a panel without EDID; the repeat below needs only the mode's refresh.
Rates between 60.1 and 65 Hz, a few arcade boards, have no multiple in
range and are below the floor shown once, so a frame there is shown once
and waits on a refresh of the panel's own; the range cannot grow upward.
vrr-probe fast, 2026-10-08:
the fastest mode's 174.651 MHz clock and 12-line front porch, with the
back porch cut from 142 lines to 135 (120.956 Hz) and 126 (121.946 Hz).
The SoC delivered every frame exactly one period apart, within 10 us, at
all three rates. By eye the moving block jumped at 121 and 122 Hz and was
clean at 120.198: the panel does not show frames faster than its 120 Hz
class evenly. Those boards are not supported: RetroArch refuses them
(0021, see the launch below) rather than lock them to the display with
vsync. In FBNeo that is 34 games, 67 sets: Namco System 1 and System 86
at 60.60 Hz (Splatterhouse, Pac-Mania, Galaga '88, Dragon Spirit, Rolling
Thunder and 21 others), Tehkan World Cup, Gridiron Fight and Tee'd Off
(60.60), Yie Ar Kung-Fu and Akazukin (60.58), Drift Out '94 (60.18),
Tetris: The Grand Master 2 (61.68) and Sky Fox (62.65). That test ran on the wide back porch the panel
does not lock to (see the porches below); on its own porches it has not been
repeated, so the range above 120.198 Hz is open again.
Showing a frame more than once¶
The repeat is the display driver's (patch 1107, see
repeats in the kernel). Until 2026-10-09 it
lived in Mesa's KMS backend (mesa-005, dropped then), as described here
with what was measured on it. RetroArch presents
each frame once (0018), aimed at the frame's start plus a budget: what
the last 64 frames needed to be ready, measured after the GPU fence, and
a millisecond. Mesa takes the frame period from the distance between
targets. If that is at least twice the mode's refresh, Mesa commits the
same buffer again at target + period / N once the frame has flipped. The
application keeps both swapchain images and the whole frame period, and a
repeat costs one atomic commit and no rendering. Mesa derives N from the
mode's refresh alone, so it needs no range quirk.
Rejected first: RetroArch re-rendering each frame twice. The second present held the image the next frame needed until it was on screen, so with two images the core had about half a frame instead of a whole one, and the shader ran twice. Tekken 3 at 4x with a 12-pass shader: 20% of frames started late and 62 refreshes in 30 s fell to the 11.1 ms floor. Three images hid that, but the design was the fault, not the image count.
Measured 2026-10-08, VRR on, two swapchain images, flip traces:
| Frames | Refreshes | Self-refreshes | Longest refresh | |
|---|---|---|---|---|
| Super Mario World, 30 s | 1804 | 3607 | 0 | 8.51 ms |
| Tekken 3 attract, 60 s | 3588 | 7181 | 5, all on frames the game delivered late while loading | 11.13 ms |
The SNES is the edge case: half its frame is the fastest refresh, so the panel cannot catch up once it is behind. It sat a steady 7.5 ms behind the targets: fixed latency, as on a fixed 120 Hz mode, with every frame still shown twice. A PlayStation frame leaves 0.038 ms a refresh to catch up, about a second after a loading stall.
The general form is LFC in the DPU driver, which covers every client, not only timed VK_KHR_display presents: patch 1107, which replaced this.
Checked frame by frame¶
Measured 2026-10-08 for #595, with the image of that day and RetroArch
0020. A refresh count or an average frame rate cannot show that every
frame was shown k times, so each run was traced and checked one refresh at
a time: tools/display-trace records every vblank, every flip, the buffer
each commit scans out, Mesa's commits (mesa-007: frame or repeat,
present ID, target, when it was queued and committed) and, for
present-probe, the DPU's CRC of every refresh. tools/display-check pairs
each commit with the vblank its flip completed on. Each refresh is then a
new frame, a repeat, or one with no commit: the panel refreshing on its
own at its 90 Hz floor. Games ran 10 minutes with MangoHud observing
(#596), present-probe 5 minutes per rate with every 600th frame presented
3 ms late, the boundaries 60 s each.
| k, P/k | Frames | Shown k times | Without a commit | Late or tight presents | Not explained | |
|---|---|---|---|---|---|---|
| Super Mario World, 60.099 Hz | 2, 8.320 ms | 35989 | 35986 | 4 | 428 / 3254 | 0 |
| the same without MangoHud | 35989 | 35987 | 3 | 268 / 3750 | 0 | |
| Tekken 3, 59.826 Hz | 2, 8.358 ms | 35818 | 35791 | 30 | 898 / 2476 | 1 phase jump |
| Streets of Rage 2, 59.923 Hz | 2, 8.344 ms | 35884 | 35868 | 16 | 152 / 3480 | 1 frame |
| present-probe, 60.099 Hz | 2, 8.320 ms | 17998 | 17992 | 3 | 77 / 22 | 0 |
| present-probe, 59.94 Hz | 2, 8.342 ms | 17953 | 17907 | 44 | 74 / 3 | 3 phase jumps |
| present-probe, 59.826 Hz | 2, 8.358 ms | 17917 | 17877 | 38 | 50 / 3 | 3 frames, 4 jumps |
| present-probe, 50 Hz | 2, 10.000 ms | 14976 | 14912 | 62 | 36 / 5 | 24 frames, 24 jumps |
| present-probe, 40 Hz | 3, 8.333 ms | 11981 | 11957 | 23 | 33 / 1 | 0 |
| present-probe, 30 Hz | 4, 8.333 ms | 8984 | 8961 | 22 | 25 / 2 | 0 |
Late is a present that reached Mesa after its target; tight, one that reached it after Mesa's own commit point, a millisecond before. Every frame not shown k times sits at one of those, with the exceptions in the last column. The mean display interval matched the content's period to 0.003% or better in every run, and against the content's own clock the panel stayed within a frame, minute by minute (the SNES's 8.9 ms range being the widest).
- Repeats show their frame. Each repeat scanned out the buffer of the frame before it, in all 21 runs. In present-probe, which draws its frame number, the DPU's CRC agreed on all 283,415 refreshes: a repeat or a refresh without a commit has its frame's pixels, a new frame new ones.
- MangoHud's display intervals are the trace's: 189,433 rows in nine runs, each equal to the vblank timestamps to the microsecond. Without MangoHud the panel did the same, 35,987 frames shown twice against 35,986. With it, 428 presents came late against 268, the overlay's own cost, and in two present-probe runs it held one present back about 20 ms some 3 s after start.
- The SNES cannot catch up. Half its frame is the panel's shortest refresh, so after a late frame the panel stays behind. The phase against the targets moved between 5.5 and 10.6 ms, as RetroArch moved them: 91 of 94 jumps were 0018's budget stepping. Against the content's clock it stayed within a frame. The other rates catch up k x (P/k - R) per frame: after a 3 ms late frame, which slips a whole refresh, a median 138 frames at 59.826 Hz, 237 at 59.94, 255 at 40 and 191 at 30 Hz.
- The rest is on the display side, a few times per run: the plane programmed late after Mesa's commit (over 2 ms on 6 to 11 of about 72,000 commits per game, against a median of 0.09 ms; 2.9 to 3.1 ms where it cost a refresh), or a repeat committed up to 2.2 ms after it was due. A frame is held a refresh when that meets the panel's floor. The cause is not found; the commit worker and the wait in patch 1098 are where to look.
- 50 Hz runs close to the floor. A repeat's 10 ms refresh is 1.1 ms short of the 11.1 ms floor, and a repeat committed 0.9 ms late, with the plane programmed 1.0 ms before the floor, did not start a refresh: the panel refreshed on its own and the frame waited. 62 times in 5 minutes, so a PAL game would hitch about every 5 s. The floor is 80 Hz since, below.
The edges of k, present-probe at 60 s each:
| Rate | k, P/k | What the panel did |
|---|---|---|
| 60.05, 40.066, 30.049, 29.9 Hz | fits | every frame exactly k times |
| 60.15, 40.2, 30.1 Hz | mesa-005's R/50 slack picks a k below the shortest refresh | 6, 24, 12 frames a minute shown one refresh short; the content 0.01-0.02% slow |
| 60.5 Hz | 2, 8.264 ms | the panel at its fastest, the content 0.6% slow; RetroArch refuses such content (0021) |
| 45.1 Hz | 2, 11.086 ms, just inside the floor | 39% of frames with a refresh of the panel's own |
| 44.9 Hz | 2, 11.136 ms, past the floor | 46% |
| 42 Hz | 2, 11.905 ms: no k fits | 60% |
The slack exists so a frame of exactly two refreshes, the SNES on its own mode, keeps both, and that takes more than microseconds: RetroArch lowers its budget 0.5 ms at a time (0018), mesa-005's period takes an eighth of the step, and in the 10 minutes above Super Mario World's period fell 62.5 us short of two refreshes 44 times. At R/50, 166 us, it also takes k at 40.07 to 40.33 and 30.05 to 30.20 Hz, which RetroArch's check for content that does not fit twice does not catch. On the 90 Hz floor one refresh fewer would have been worse at all three: 60.15 Hz shown once is past the floor, 40.2 Hz twice is 12.44 ms, past it too, and 30.1 Hz three times is 11.07 ms, 0.03 ms inside it (45.1 Hz above was 0.02). Between 40.07 and 45 Hz nothing fits the 90 Hz floor at all, and the floor's own margin pushes the usable lower edge for k = 2 up towards 50 Hz.
On an 80 Hz floor¶
Patch 1097 declares 80 Hz since, just under two thirds of 120.198 Hz: the longest frame AVR holds is 12.5 ms (1676 lines on the 1116-line mode of the time), and the idle check above reads 80 frames/s. Every rate from 20 to 60.1 Hz has a multiple now, and a repeat at 50 Hz has 2.5 ms to the floor instead of 1.1. Measured 2026-10-08 on a kernel-only build over that day's image, with the same Mesa, RetroArch and runs. Frames over 11.1 ms have not been watched at low backlight yet. Refreshes the panel made on its own:
| P/k | To the 80 Hz floor | At 90 Hz | At 80 Hz | |
|---|---|---|---|---|
| present-probe 50 Hz, 5 min, every 600th frame late | 10.000 ms | 2.49 ms | 62 | 31, all at late presents |
| 49.76 Hz, 60 s | 10.048 ms | 2.45 ms | 4 | |
| 45.1 Hz | 11.086 ms | 1.41 ms | 1039 | 3 |
| 44 Hz | 11.364 ms | 1.13 ms | 8 | |
| 43 Hz | 11.628 ms | 0.87 ms | 19 | |
| 42 Hz | 11.905 ms | 0.59 ms | 1496 | 125 |
| 41 Hz | 12.195 ms | 0.30 ms | 197 | |
| 40.4 Hz | 12.376 ms | 0.12 ms | 693 | |
| Tekken 3, the first 290 s | 8.358 ms | 4.14 ms | 20 | 6 |
| present-probe 59.826 Hz, 5 min, every 600th frame late | 8.358 ms | 4.14 ms | 38 | 34 |
40.066, 40, 30.049 and 29.9 Hz showed every frame k times, and 40.2 and 30.1 Hz one refresh short 24 and 13 times a minute, as before.
- A repeat needs about 1.4 ms to the floor. Below that the panel refreshes on its own more often the closer the repeat comes, as 45.1 Hz did on the 90 Hz floor with 0.02 ms. 50 Hz no longer misses one: the rest of its 31 are at the deliberately late frames, and its 2 phase jumps not explained are the plane programmed 2.3 and 2.5 ms after the commit, the display-side delay above.
- On a 60 Hz game it only matters to a late frame. A frame on time is refreshed every 8.4 ms, far from either floor. A late one is committed at once, and a commit still starts the refresh if it lands 1.6 to 1.8 ms before the floor runs out: 10.9 ms after the refresh before at 80 Hz, 9.3 ms at 90 Hz. For a 60 Hz game that is a present up to about 3 ms after its target, against 1.3 ms; later, the panel refreshes on its own and the frame before is shown a third time. In Tekken 3, frames shown three times in the same 290 s fell from 13 to 4, those 4 presented more than 4 ms late, while loading. present-probe's late frames reach Mesa 3.9 ms after their target, past both floors, and on the lower one the panel's own refresh and the frame after it come 1.4 ms later.
- The slack stays at R/50. One refresh fewer at 40.2 Hz would be 12.44 ms, 0.06 ms from the floor, worse than 40.4 Hz above; at 30.1 Hz, 11.07 ms, 1.42 ms from it, about 45.1 Hz's 3 a minute. So less slack would help only 30.05 to 30.20 Hz, where nothing runs, and the SNES needs more than 62.5 us of it.
tools/display-check takes the floor as --floor-hz (65 by default; 60,
70, 80 and 90 for the runs before; the window that pairs a kernel repeat with
its refresh follows it), and counts a phase jump back towards the usual
phase as the panel catching up, k x (P/k - R) a frame: 3.4 ms at 50 Hz,
over the 2 ms jump. Of the 149 jumps at 50 Hz on the 90 Hz floor, 125
were that.
Choosing the floor¶
Patch 1097 declares 65 Hz since #651, chosen on 2026-10-17 between what
the panel showed clean and what tore, measured on a test kernel with the
floor as a parameter. What separates the floors is the longest hold each
allows a frame shown once, and whether the panel shows such a hold without
tearing. Present-probe at the floor's own rate plus
one, shown once with a few percent of frames waiting on a refresh of the
panel's own, a person watching for tears (tools/tear-tap):
| Floor | Content | Held past the fastest refresh | Run | Tears |
|---|---|---|---|---|
| 50 Hz | 61 fps | 8.1 ms | 60 s | 3, and more with frames held to 20 ms |
| 60 Hz | 61 fps | 8.1 ms | 120 s | a few, small |
| 70 Hz | 71 fps | 5.8 ms | 120 s | none |
| 80 Hz | 81 fps | 4.0 ms | 120 s | none |
Every frame left the DPU whole and on time in all of them: the tears are the panel's. 60 Hz tore less than 50 at the same hold, so the floor's period counts as well as the hold, but not enough. 65 Hz holds a frame 7.1 ms at most, between the clean 5.8 and the 8.1 that tore now and then, and was taken with the chance of an occasional tear, for the lower range shown once. 70 Hz also showed 35 fps three times, 70 fps once for 30 s, frames 4 and 40 ms late at 59.94 fps, and switches between 60, 61, 75 and 90 fps without a tear; 80 Hz the same plus 30 s of 3 s moving and 2 s still at 5% backlight, without a flicker.
Content at exactly the floor's rate judders on any floor: each frame races the refresh the panel starts on its own, and the loser waits a whole floor period. At 70 fps on 70 Hz, 20% of frames waited in one run, at 80 on 80, 33%. Nothing of ours runs there: Steam is held to 60 fps ("Steam" below) and no emulated system is near 65 Hz.
What a frame shown once needs, from the previous refresh's vblank
timestamp: to reach the plane (dpu_plane_set_scanout) about 0.3 to 0.4
ms before the floor runs out. Nearer than that a frame waits on a refresh
of the panel's own, or lands with it and starts one more refresh after
it, which the next frame waits on. Frames 0.29 ms from it still made it,
and ones 0.31 ms from it waited. Mesa's commit gets to the plane 0.07 ms
after it typically, 0.28 ms at p99, and the waits are mostly frames that
took longer. Present-probe shown once, 30 s each, on the floors measured
before the choice:
| Rate | Nominal time to the floor | Frames that waited on a refresh of the panel's own |
|---|---|---|
| 61 fps on 60 Hz | 0.27 ms | 86 of 1802 |
| 62 fps | 0.54 ms | 10 of 1829 |
| 63 fps | 0.79 ms | 2 of 1861 |
| 64, 65, 67 fps | 1.04 to 1.6 ms | 1 to 3, at the start or at a late frame |
| 65 fps on 61 Hz | 1.01 ms | 2 of 1787 |
| 61 to 90 fps on 50 Hz | 3.6 ms or more | none |
So on 65 Hz a frame shown once fits from about 68 fps, on 70 from about 73. The 1.6 to 1.8 ms of "On an 80 Hz floor" above count a late commit against another reference and are conservative for it.
For the emulators the counts are the same as on 80 Hz at every rate. The floor only matters to a late frame, which it gives room: after a doubled frame's repeat the panel waits a floor period, 15.4 ms, before refreshing on its own, so a 60 Hz game's frame up to about 6.5 ms late still starts its own refresh, against about 4 ms on 80 Hz. At 59.94 fps with every 60th frame 40 ms late, doubling was back within 4 to 6 frames on every floor, with no drift.
Repeats in the kernel (LFC)¶
Patch 1107 repeats a slow frame in the DPU's video encoder instead of
in Mesa (#586). It was compared with mesa-005 on the same image, below,
and replaced it: on by default, msm.dpu_lfc=0 turns it off and leaves
a slow frame to the panel's own refresh at the floor.
- Period. The kickoffs of a steady source fall on a grid, the
content's own clock, which stands in for mesa-005's targets. A kickoff
within an eighth of a period of its place pulls the grid and its
period a little towards itself; a late or dropped one moves neither, so
a late frame's repeats keep their places and the panel catches up on
them. Two distances alike are a new rate, and the grid starts again
from them, as from the first two, when both kickoffs missed the grid
or the distance is more than an eighth off the period: a doubled rate
puts every second frame back on the old grid, so misses alone never
noticed 30 to 60 Hz. A mode set and AVR going on or off start the
cadence afresh. A frame is shown
count = (P + R/50) / Rtimes, none below two refreshes or above 100 ms. Smoothing the distances between kickoffs instead, as a first build did, failed at 40 Hz: the frame after one 6 ms late came 18.8 ms after it, close enough to count, and three refreshes a frame became two for seven frames.dpu_enc_lfc_frametraces the grid at every kickoff. - Repeat. After each refresh of a frame, the vsync interrupt arms an
hrtimer for the next one at kickoff + shown x P/count, and the timer
writes
AVR_TRIGGER. A kickoff cancels what is left of the frame before, and a trigger finding one pending is skipped. A refresh the panel makes on its own, at the floor, counts like a repeat. No commit, no flip event, no atomic state. - Two races found on the device. The vsync that takes a frame can
come before the kickoff has put the frame on the grid; arming then
timed the first repeat from the frame before, already past, and its
trigger added a refresh (Tekken 3, 35 times in 10 minutes, each
starting a run a refresh behind). The kickoff arms it in that case.
And the encoder drops the vsync interrupt 58 ms after a frame when
nothing holds it (
ENTER_IDLE), so at 12 Hz the repeats stopped after 6 to 8 of 9.tools/display-tracehides that, sincevblank-rateholds the interrupt;--no-holdleaves it to the client. The encoder now holds it while AVR is on with LFC. - Clients. Any commit while
VRR_ENABLEDis set, timed or not. For the comparison, Mesa's repeats could be turned off (MESA_VK_WSI_DISPLAY_REPEAT=0, mesa-008, dropped with mesa-005); with both on, the kernel took Mesa's repeats for frames and added none of its own. Changedpu_lfcbetween clients: the vsync interrupt is held from when AVR goes on.
Two things about AVR in continuous mode had to be found on the Nova, with a build that had them as parameters (2026-10-09, present-probe, 30 s per run):
AVR_TRIGGERalone starts a refresh. Flushing the INTF as well, the other candidate, was not needed.- A trigger written while the INTF still scans the refresh before is kept for that refresh's end, as a commit's flush is. So the trigger goes at its place on the period, and where the panel is behind, the hardware starts the repeat at the shortest frame. Timing it from the vsync interrupt instead, never before the shortest frame after it, failed: the interrupt ran about 0.68 ms after the vsync (its trace time against the vblank timestamp), every repeat came that much late, 9.0 ms apart at 30 Hz instead of 8.33, and 152 of 882 frames lost a repeat.
| Run | Frames shown k times | Refresh against P/k, median / p99 | Scanout - target, median |
|---|---|---|---|
| 30 Hz, k = 4, timed from the vsync interrupt | 728 of 882 | 0.211 / 1.011 ms | 4.12 ms |
| 30 Hz, k = 4, trigger on the period | 878 of 885 | 0.015 / 0.275 ms | 0.50 ms |
| 60.099 Hz, k = 2, trigger on the period | 1772 of 1775 | 0.002 / 0.007 ms | 1.27 ms |
tools/display-check pairs each dpu_enc_lfc_repeat kick with the
vblank traced after it and counts those refreshes as kernel repeats, so
the same checks apply to both. It also reports how long a kick took to
start its refresh and how far it went from its place on the period.
Against mesa-005, a first look¶
2026-10-09, kernel-only build of patch 1107 over that day's image, one
Turnip build with mesa-008 for both sides (VK_DRIVER_FILES), only
msm.dpu_lfc toggled. present-probe, 60 s per run, every 600th frame
4 ms late, tools/display-check --rate.
| Rate | Repeats | Frames shown k times | Panel on its own | Refresh vs P/k, median / p95 / p99 ms | Scanout - target, median / p95 ms | Frames to recover from a late one | Checks failed |
|---|---|---|---|---|---|---|---|
| 60.099 Hz | Mesa | 3570 of 3574 | 2 | 0.002 / 0.006 / 0.007 | 14.09 / 14.18 | never | 0 |
| 60.099 Hz | kernel | 3574 of 3578 | 3 | 0.002 / 0.006 / 0.007 | 6.91 / 7.08 | never | 1 |
| 59.94 Hz | Mesa | 3561 of 3569 | 6 | 0.022 / 0.061 / 0.198 | 0.68 / 11.46 | 269 | 0 |
| 59.94 Hz | kernel | 3557 of 3570 | 7 | 0.022 / 0.075 / 0.250 | 0.48 / 3.36 | 82 | 0 |
| 59.826 Hz | Mesa | 3554 of 3562 | 6 | 0.038 / 0.089 / 0.289 | 0.37 / 10.38 | 156 | 1 |
| 59.826 Hz | kernel | 3544 of 3560 | 11 | 0.037 / 0.114 / 0.335 | 0.49 / 2.00 | 48 | 0 |
| 50 Hz | Mesa | 2969 of 2975 | 6 | 0.087 / 0.486 / 0.814 | 0.26 / 0.54 | 3 | 0 |
| 50 Hz | kernel | 2971 of 2977 | 8 | 0.072 / 0.330 / 0.664 | 0.28 / 0.88 | 3 | 0 |
| 40 Hz | Mesa | 2375 of 2381 | 5 | 0.014 / 0.021 / 0.218 | 0.85 / 11.63 | 287 | 0 |
| 40 Hz | kernel | 2372 of 2382 | 6 | 0.014 / 0.054 / 0.189 | 0.46 / 2.89 | 88 | 0 |
| 30 Hz | Mesa | 1781 of 1786 | 5 | 0.014 / 0.039 / 0.233 | 0.89 / 10.92 | 216 | 0 |
| 30 Hz | kernel | 1772 of 1786 | 6 | 0.014 / 0.052 / 0.228 | 0.54 / 2.17 | 66 | 0 |
- On time, the two are the same. Repeats land within microseconds of P/k either way, and a frame on time reaches the screen at the same phase: new frames go through the same commit path.
- After a late frame they differ by design. A 4 ms late frame misses the floor and is shown a refresh long on both. Mesa then commits the repeat of the next frame anyway, the frame after waits, and the panel runs a refresh behind until k x (P/k - R) a frame has made it up: 156 to 287 frames, 2.5 to 7 s, which is the p95 phase of 10 to 12 ms. The kernel skips that repeat because the next frame is already kicked off, so one frame is shown a refresh short and the panel is back on the content's grid; what is left takes 48 to 88 frames. Two frames off their count instead of one, and a fraction of the time behind.
- 60.099 Hz never catches up on either: half a frame is the fastest refresh, so only a skipped repeat moves it back. Both sat at a fixed offset after the first late frames, Mesa's twice the kernel's in this run. Which offset a run ends on depends on its history; a longer run is needed before reading the difference as a property of either.
- Failed checks: Mesa at 59.826 Hz, two jumps after a repeat committed 2.0 and 2.5 ms late, the userspace wakeup a kernel timer removes; the kernel at 60.099 Hz, one jump with no cause in the trace, of the kind seen before from the display side. Kernel triggers went a median 0.01 ms and a p99 0.11 ms after their place on the grid.
On the final kernel¶
2026-10-09, kernel-only build of PR #608 at 5661f21, the same Turnip with mesa-008 on both sides.
Games, 10 minutes each, flip traces through runemu (Mesa's runs on the kernel before, whose Mesa path is the same):
| Frames shown twice | Scanout - target, median / p95 | Presents late / committed after target | Checks failed | |
|---|---|---|---|---|
| Super Mario World, Mesa | 35986 of 35988 | 3.88 / 8.82 ms | 339 / 1325 | 0 |
| Super Mario World, kernel | 35963 of 35967 | 3.08 / 4.88 ms | 282 / 311 | 0 |
| Tekken 3, Mesa | 35803 of 35811 | 0.65 / 2.10 ms | 564 / 781 | 1 |
| Tekken 3, kernel | 35781 of 35793 | 0.66 / 1.87 ms | 540 / 587 | 2 |
Tekken 3's failures on the kernel: six phase jumps in the first 2.2 s, while the grid is still being found as the game boots (Mesa has targets from the first frame), and two later ones with the plane programmed 4.1 and 2.9 ms after the commit, the display-side kind Mesa's run had too. No trigger shared a refresh with another, and the run a refresh behind that the build before had for six minutes did not come back. Twice a frame came early off the grid, once while booting and once after a stall of two 18.6 ms frames, which the grid took for a new rate; its repeat went at once, held for the refresh's end, and added nothing.
Rate changes in one session, present-probe rate:A,B switch=5,
40 s: after every switch the kernel's count was right two frames on,
and every frame was shown k times from 0 to 5 frames on (Mesa, from its
targets: 0 to 2). The build before kept 30 Hz's count after every switch
to 60 Hz.
Below 17 Hz without vblank-rate (--no-hold): at 12 Hz 345 of
349 frames got all 9 repeats (6 to 8 on the build before), at 15 Hz 436
of 437 all 7.
Pause and menu, 100 cycles each through RetroArch's network commands, both paths: every run ended cleanly, with no DPU or DSI error. RetroArch keeps presenting while paused, about every 16 ms but 15 to 18 ms apart; the kernel's grid then settles just under two refreshes a frame and stops repeating, which shows nothing since the paused picture does not change, and after a resume takes a frame or two to repeat again, where Mesa, following the targets, does not.
Power and CPU, present-probe at 30 Hz (k = 4), no tracing, on battery, 60 s each, in the order Mesa, kernel, Mesa, kernel:
| Power, W (sd of 0.5 s samples) | Timer irq/s | All irq/s | Context switches/s | CPU busy, % of one core | present-probe CPU | |
|---|---|---|---|---|---|---|
| Mesa | 1.737 (0.093), 1.712 (0.099) | 4270, 4214 | 6393, 6340 | 5713, 5694 | 22.6, 24.2 | 2.13, 2.16 % |
| kernel | 1.620 (0.090), 1.628 (0.109) | 3879, 3962 | 5775, 5866 | 4948, 4953 | 19.9, 20.3 | 1.23, 1.21 % |
About 0.1 W less with the kernel's repeats, at 90 repeats a second.
Five minutes per rate¶
The comparison that decided it, run while the switch was being made:
the kernel at 5661f21 with mesa-005 still in the test Turnip, only
msm.dpu_lfc toggled. present-probe, 300 s per run, every 600th frame
4 ms late, tools/display-check --rate.
| Rate | Repeats | Frames shown k times | Panel on its own | Refresh vs P/k, median / p95 / p99 ms | Scanout - target, median / p95 ms | Checks failed |
|---|---|---|---|---|---|---|
| 60.099 Hz | Mesa | 17999 of 18003 | 2 | 0.002 / 0.006 / 0.007 | 12.33 / 14.59 | 0 |
| 60.099 Hz | kernel | 17997 of 18003 | 6 | 0.002 / 0.006 / 0.007 | 6.88 / 7.38 | 0 |
| 59.94 Hz | Mesa | 17923 of 17955 | 30 | 0.022 / 0.052 / 0.200 | 0.84 / 11.73 | 0 |
| 59.94 Hz | kernel | 17894 of 17955 | 31 | 0.022 / 0.071 / 0.226 | 0.49 / 3.36 | 1 |
| 59.826 Hz | Mesa | 17888 of 17920 | 30 | 0.038 / 0.102 / 0.305 | 0.48 / 10.64 | 1 |
| 59.826 Hz | kernel | 17859 of 17921 | 31 | 0.037 / 0.092 / 0.275 | 0.42 / 2.30 | 0 |
| 50 Hz | Mesa | 14949 of 14977 | 28 | 0.114 / 0.353 / 0.745 | 0.24 / 0.52 | 2 |
| 50 Hz | kernel | 14951 of 14977 | 27 | 0.071 / 0.311 / 0.624 | 0.27 / 0.79 | 0 |
| 40 Hz | Mesa | 11960 of 11982 | 21 | 0.014 / 0.021 / 0.190 | 0.99 / 11.76 | 0 |
| 40 Hz | kernel | 11939 of 11982 | 23 | 0.014 / 0.044 / 0.178 | 0.46 / 3.37 | 0 |
| 30 Hz | Mesa | 8970 of 8987 | 17 | 0.014 / 0.027 / 0.227 | 0.93 / 11.39 | 1 |
| 30 Hz | kernel | 8954 of 8986 | 17 | 0.013 / 0.036 / 0.199 | 0.44 / 2.99 | 0 |
- The first look holds over five minutes. Refreshes land as close to P/k on both. The kernel shows 16 to 29 more frames off their count, about one per late frame: the frame after a late one, shown a refresh short so the panel is back on the content's clock. In exchange the p95 phase is 2.3 to 3.4 ms against Mesa's 10.6 to 11.8, which spends the minutes after each late frame a refresh behind.
- 60.099 Hz levels off on both. Half a frame is the fastest refresh, so only a skipped repeat brings the panel back. The kernel held 6.6 to 7.3 ms behind the targets, 30 s medians, for the whole run; Mesa started at 14.6 and drifted to 10.4.
- Failed checks. Kernel, 59.94 Hz: two phase jumps of 2.2 ms with no cause in the trace. Mesa: at 30 Hz a repeat committed 15.9 ms after it was due; at 50 Hz two frames shown three times with the plane programmed 2.3 and 3.4 ms after the commit; at 59.826 Hz one jump with no cause in the trace.
The GPU clock: no fence deadline¶
At a commit, the atomic helper gives each plane's fence a deadline: the
start of the next vblank, from the mode's frame length. msm boosts the
GPU when such a fence is still running 3 ms before its deadline, raising
the clock's floor to twice the current clock for 50 ms
(msm_fence_set_deadline, msm_devfreq_boost). With AVR the panel waits
in its front porch for the commit, so a frame starts about 0.4 ms after
it and that vblank is always near or past. RetroArch presents right after
its final blit, about 80 us of GPU work, so a commit often went out with
the blit still running, and each such commit boosted. At fixed refresh
Mesa commits at a vblank event, the deadline is about 7 ms away, and the
blit has long finished.
Patch 1108 sets no deadline on a CRTC with VRR_ENABLED, so devfreq's
load decides the clock, as it does at fixed refresh. A late frame under
VRR is shown late, not dropped at a vblank.
NFSU on ARMSX2 with the test Turnip (main and mesa-009), on battery, 110 s from 35 s in. The race loads a savestate 25 s in; 3x is ARMSX2's internal resolution, 2x otherwise. Before is kernel 5661f21 with LFC on, after is the same kernel with 1108. Frames off their count are those not shown twice, the savestate load's pause left out.
| Run | Kernel | GPU MHz, mean | At 680 MHz | Mcycles per frame | Boosts | Battery W | Frames off their count | Phase p95, ms |
|---|---|---|---|---|---|---|---|---|
| Intro, VRR | before | 647 | 88% | 0.29 | 1755 | 2.67 | 1 | 0.91 |
| Intro, VRR | after | 401 | 0% | 0.27 | 0 | 2.70 | 3 | 0.83 |
| Intro, fixed | before | 404 | 1% | 0.27 | 255 | 2.76 | 73 | - |
| Intro, fixed | after | 449 | 17% | 0.27 | 761 | 2.74 | 107 | - |
| Race, VRR | before | 522 | 40% | 2.86 | 867 | 4.94 | 29 | 1.74 |
| Race, VRR | after | 455 | 19% | 2.77 | 0 | 4.89 | 43 | 1.44 |
| Race, fixed | before | 455 | 19% | 2.74 | 364 | 4.89 | 75 | - |
| Race, fixed | after | 441 | 14% | 2.74 | 335 | 4.89 | 56 | - |
| Race 3x, VRR | before | 610 | 43% | 4.38 | 375 | 5.70 | 41 | 4.07 |
| Race 3x, VRR | after | 606 | 40% | 4.36 | 1 | 5.77 | 75 | 2.65 |
| Race 3x, fixed | before | 608 | 42% | 4.38 | 163 | 5.60 | 226 | - |
| Race 3x, fixed | after | 616 | 44% | 4.41 | 129 | 5.61 | 155 | - |
- The boosts are gone, and VRR clocks as fixed refresh does. The work per frame is the same throughout, within 3%.
- No underclocking under load. Every run held 59.9 fps or more in every 10 s window, and display-check passed every run. The 3x race ran at 606 MHz against 610: above 50% load devfreq raises the clock without the boost.
- Frames. Frames off their count and late presents (1, 104 and 275 after, against 1, 112 and 280) are within the spread of the unchanged fixed-refresh runs between the two batches.
- Power hardly moves. The VRR runs are within 0.07 W of before; the same fixed-refresh runs repeat within 0.02 W. At light load the clock costs little: the intro keeps the GPU busy 3 to 4% of the time, so at 680 MHz it only idles longer.
- Fixed refresh still boosts, 129 to 761 times per run, when a commit lands within 3 ms of a vblank. 1108 leaves it as it was.
- Open: at 3x, VRR draws 0.10 to 0.16 W more than fixed refresh at the same clock, before and after. The deadline does not explain it.
The launch¶
Every RetroArch game runs with variable refresh, with no setting to get
right. runemu decides it per launch and exports
MESA_VK_WSI_DISPLAY_VRR, which Mesa and setsettings both read. It is 1
on KMS, on a panel that says vrr_capable and is the only output (a dock
brings a fixed-rate one), unless the game pins a display_mode.
Otherwise it is 0.
- Mesa (
mesa-006) setsVRR_ENABLEDin its first mode set, and clears it when it lets the CRTC go. 0 clears one left on by a run that crashed. Opt-in rather than Mesa'sadaptive_syncdefault, because mpv and SDL's KMS emulators present through the same backend without timing their frames. - setsettings uses the fastest mode for every game, Sync to Exact Content Framerate, vsync off (there is no vsync setting), and no frame delay. The table of a mode at twice each system's rate is for fixed refresh.
- RetroArch (
0019) keeps presents timed under Sync to Exact Content Framerate with vsync off. Vsync off would otherwise show each frame whenever it was ready. Vsync is never on (0021): content that does not fit twice into the display's rate is refused, "This game is not supported", and RetroArch quits.
vrr=0 in system.cfg, for everything (global.), a system or a game,
turns it off to compare. The fixed refresh path is then as before.
Measured 2026-10-08 through runemu, flip traces:
| Refreshes | Longest refresh | ||
|---|---|---|---|
| Super Mario World, 30 s | 3606, every frame twice | 8.42 ms | VRR_ENABLED 0 before, 1 while running, 0 after exit |
Tekken 3 with global.vrr=0 |
- | - | a VRR_ENABLED left on was turned off; 119.652 Hz mode |
The launcher¶
portarelauncher runs variable refresh too, on the fastest mode, and flips its one buffer 60 times a second whenever the panel is on and its own: each flip is a frame for the kernel, which shows it twice, and nothing new for the panel. Without the flips a variable refresh panel with nothing coming falls to its floor, and a static screen at 65 Hz is the one case the flicker tests above did not cover. Measured 2026-10-17 with a build from the launcher's tree: 119.9 Hz, refreshes 8.320 ms apart, 60 flips a second every one doubled, 1% of a core, and the mode set at start (111 ms) the only gap.
Steam¶
Steam's gamescope runs with --adaptive-sync --framerate-limit 60: it
sets VRR_ENABLED, flips each frame as the game delivers it, one flip in
flight, and holds the game to 60 fps. The kernel treats it as every other
client, so every frame is shown twice or more, and no game runs in the
band that fits neither way, 60.7 to 65 fps:
| The game's rate | Shown |
|---|---|
| 60 fps, the limit | twice: 120 Hz, the frame's late room about 5.5 ms |
| 40.07 to 60 fps | twice |
| 30.05 to 40.07 fps | three times |
| below | four times and more |
Steam's gamescope asks for -r 120 and its patch 0007 takes the fastest
mode that rounds to it, 120.198 Hz, the mode every game runs variable
refresh on. The exact rate matters to a 60.000 fps game shown twice,
measured with present-probe on both modes, 2026-10-17: two refreshes of
120.198 are 27 us short of its frame, so the panel waits for every
commit and starts the scan 0.46 ms after it (median; min 0.20), every
frame, without a hitch when the slack runs out. On 120.000 two refreshes
are the frame exactly: the panel locks to the game at whatever offset
the first frame set, 5.4 ms in one run, and an 8 ms late frame moves it
to wherever it lands, 1.8, 7.6, 1.5, 7.3 ms in turn, latency that
nothing drains. A late frame itself scans about 3 ms after its commit on
either mode, against 0.46 for one on time; the kernel's part in that is
not understood. The same R/50 slack takes a count
that needs more than the fastest refresh just above 40.07, 30.05, 24.04
and 20.03 fps, for every client: 40.2 fps is shown three times with a
refresh short 24 times a minute (#651).
The limit is gamescope's, the one of master (#650) whose variable refresh limiter keeps its schedule, and its patch 0008 makes the command line's limit a ceiling: Steam's own frame rate setting and the refresh cycle override lower it, never lift it, where before the first focus change replaced it with the override, 0 without one. Not measured yet: a real game on the final kernel and gamescope.
The panel's porches¶
Variable refresh runs on the fastest mode, 120.198 Hz. Until #623 that mode
had a 142-line back porch where every other mode has 27, and the panel does
not lock to it: it scans its own memory at about 120.14 Hz, and every game
showed a tear every few seconds, all the time at 50 fps. The DPU's CRC saw
every refresh whole, which is why nothing here caught it; a person watching
a moving bar did (tools/tear-tap). Patch 1110 gave it the 27 lines of the
other modes, and the beat went, but those were three lines more than the
panel's own 24: it then followed a stretch of only about 0.3 ms, and tore at
50 fps. Patch 1112 puts every mode on the panel's own 998-line timing, which
follows the stretch at 50 fps too. The measurements are in
REFRESH_RATES.md.
So a timing check here ends at the DPU. What the panel shows has to be watched, and the measurements above describe the link: on the wide mode the panel's own scan sat between them and the eye.
Do not¶
Do not write the DPU's timing registers from userspace. Writing
INTF_VSYNC_PERIOD_F0 on INTF 1 (0xae3600c) with devmem while the panel
ran rebooted the Nova, with no record of why. Use mode setting, or the
kernel patch.