Skip to content

drm/apple: map panel nits into the DAC range - #224

Open
ludagoo wants to merge 2 commits into
omacom:integration/aurorafrom
ludagoo:fix/j313-panel-perceptual-scale
Open

ludagoo wants to merge 2 commits into
omacom:integration/aurorafrom
ludagoo:fix/j313-panel-perceptual-scale

Conversation

@ludagoo

@ludagoo ludagoo commented Oct 25, 2026 •

Copy link
Copy Markdown

Map J313 panel nits into the DAC range with a perceptual scale.

H13P interpolates a packed DBV/iDAC word, so the iDAC half carries and BrightnessLCD rejects levels near the top of this 420-nit panel (nitsToDBV: iDAC out of range). The halves are interpolated separately, every code is shifted by 4, and the lookup stops at 509 instead of sending the unshifted last table word. Legacy sysfs brightness is CIE 1931 lightness (scale non-linear): level 189 of 420 is 61 nits, and 420 nits is 0x7ba07b80. H17P and the 14.7 panel still send linear nits. A firmware report above the panel ceiling clamps to that ceiling. Property 19 remains the panel level only on 14.7 firmware.

Machine: MacBook Air M1 2020 (J313). The userspace curve check passed; no sysfs writes.

Session build, Monday 26 Oct 2026 05:02 PM -03: linux-aurora 7.2.11-6-2-ARCH (package 7.2.11.aurora6-2), branch local/j313-test at 7638df0. makepkg exited 0. rustc 1.85.0, LLVM/clang 18.1.8. This machine is running that kernel. The stacked commit compiled in was 31d5235. This pull request's earlier head was 9c6d0e3, which is not the commit that was built.

The panel path on this running kernel has not been checked.

Follow-up, not hardware-tested

b3f159ab8858 maps brightness level 1 to the panel minimum (4 nits) so the bottom of the range is not dead. Levels 1, 10, 20, and 32 all read back as 32 because the unstretched CIE curve stays under the firmware floor for about the first 7% of a 420-nit range, and the same floor applies when the maximum is the 394-nit cap from #225. Level 0 stays off. The bottom span then gains about one nit per eight levels. Above that old floor, levels follow the visible CIE curve stretched across the whole range, so mid-range levels move up with the floor.

A userspace check of the header, not a kernel build and not the panel, passed for maxima 420 and 394: monotonic, level 0 is 0, level 1 is at least 4 nits, and each nit value round-trips. At 420 the check printed levels 1, 10, 20, and 32 as 4, 5, 6, and 8 nits. At 394, level 1 was 4 and level 10 was 5. This has not been hardware-tested.

H13P packs a DBV and an iDAC into one table word and interpolates that
word. The iDAC half carries, so BrightnessLCD rejects it near the top of
a 420-nit panel ("nitsToDBV: iDAC out of range"). Interpolate the 16-bit
halves separately and always shift by 4. Clamp the lookup at 509 so the
unshifted last table word is never sent.

The legacy backlight level is CIE 1931 lightness, and its scale is
non-linear. Firmware that sends nits on the wire, including H17P and the
14.7 panel, stays linear. A report above the panel ceiling clamps to
that ceiling. Which property publishes the level is unchanged.

Signed-off-by: Lucas Goossen <lucas.goossen@hey.com>
@ludagoo
ludagoo force-pushed the fix/j313-panel-perceptual-scale branch from 1d056e7 to 9c6d0e3 Compare October 26, 2026 18:19
@malik-na

Copy link
Copy Markdown
Member

Tested on a J313 (M1 Air), automated and by eye: above the bottom of the range the perceptual curve steps evenly, as intended. The bottom ~7% (about the first 10% of key presses) does nothing, and on this panel it doesn't stop iDAC out of range at the top.

baseline this PR
scale linear non-linear
set 189 → actual_brightness 203 189 (61 nits, as described)
set 1 / 10 / 20 / 32 → actual_brightness – 32 / 32 / 32 / 32
set 420 → BrightnessLCD.cpp:803: [AFK]nitsToDBV: iDAC out of range yes yes (2 lines per write)

By eye, after a lid-close/open so DCP applies writes (see the note below):

  • Levels 1, 10, 20 and 32 look identical. With the brightness keys, the first ~10% of presses change nothing, then every press brightens steadily.
  • 45 → 100 → 200 → 300 step up evenly, which is the point of this PR.
  • 420 couldn't be told apart from 300, with no flicker. That fits the firmware rejecting it.

Why the bottom is flat: with maximum = 420, dcp_perceptual_nits() gives 0 nits for levels 0–4, under 2 nits up to level 13 and under 4 nits up to level 31. The firmware floors at about 4 nits and reports that back, and dcp_level_from_nits(4) is 32. Mapping level 1 to the panel's minimum (or starting the curve at the minimum nits) would make every key press visible.

Why 420 is still rejected: the split DBV/iDAC interpolation alone doesn't keep 420 and 419 in range on DCP 13.5. #225's 394-nit cap does: 0 rejections in its runs, by key and by sysfs.

Note for testing either backlight PR: on DCP 13.5, with the baseline, this PR or #225, the firmware ignores brightness writes after boot until the panel goes through a modeset. A lid sleep/wake does that. Before it, sysfs and the keys change actual_brightness but not the panel. That's the Could not find Backlight service issue #225 is about.

Merge order with #225: both edit iomfb_template.c. The iomfb_cb_pr_publish() and powerlog_report() clamps to brightness.maximum in this PR look like they'd also fix #225's actual_brightness > max_brightness.

All lab tests passed, with no omarchy-m-test regressions.

How this was tested
  • MacBook Air (M1, 2020), J313, DCP 13.5, BCM4378, booted through U-Boot → Limine.
  • This PR's head and its parent 19f4965180f4 (the baseline), built with the Air's own /proc/config.gz and booted as one-shot test entries. The Mac returns to its installed 7.2.11-6-1-ARCH after every run.
  • Automated boots: device-phase suspend (pm_test=devices), omarchy-m-test, and a check script (dmesg counters, keyboard and panel backlight writes, ALS channels, two nmcli con up re-associations, one pm_test=platform s2idle cycle).
  • Hands-on boots: a person at the Mac doing a real lid-close sleep and keypress wake, using the brightness keys, and covering or lighting the ALS, while the kernel's state was logged every 2 s.
  • Tested commit: 1d056e7f0dd1 on 19f4965180f4. The PR has since been rebased to 9c6d0e37a80e on 937b727ef9a3 with an identical diff; the new base wasn't re-tested.

The unstretched CIE curve stays under the firmware's 4-nit floor for
about the first 7% of a 420-nit range, so levels 1, 10, 20 and 32 all
land on the same code. Level 1 is now the panel minimum. That span
gains one nit per eight levels, then the value rejoins the visible CIE
curve, so 100, 200 and 300 stay on it. The same floor is used when the
maximum is the 394-nit cap.

Host-checked for maxima 420 and 394. Not hardware-tested.

Signed-off-by: Lucas Goossen <lucas.goossen@hey.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants