Adjusting the backlight PWM frequency on an Ivy Bridge laptop
After chip-level-flashing a Ivy Bridge Dell laptop (Inspiron 15R SE 7520) with a corrupted BIOS chip (the BIOS spontaneously corrupted in 2014 then I fried the EC trying to reflash the BIOS, but that's a story for another time), I was disappointed to find that the backlight was dimmed using PWM at 200 Hz (a period of 5ms). This meant that setting the brightness to 50% makes the light turn on for 2.5ms before turning off, then repeat every 5ms. This causes noticeable flicker when I move my hand in front of the screen.
🔗 Increasing the backlight PWM frequency
Reading through the i915 driver and Sandy/Ivy Bridge GPU datasheets, I found that the backlight was controlled by two GPU registers at 0x48250 and 0x48254 (CPU-written?), with mirrors-ish at 0xC8250 and 0xC8254 (PCH-written?). There's actually a script to decrease the backlight period register at https://github.com/edio/intelpwm-udev, but the problem is that Linux still scales the pulse width register based on the original period (5ms) at time of bootup and doesn't see the userspace tool writing a shorter period (higher frequency). If you halve the backlight period to 2.5ms, 50% brightness suddenly configures the GPU to turn the screen on for 2.5 of 2.5 ms, setting the screen to full brightness instead. (All brightness values below 50% are doubled, and all above 50% write illegal register combinations according to the datasheets, haha). As an added gotcha, if you write to the wrong register mirror (0xCxxxx vs 0x4xxxx), you get seizure-inducing screen backlight flicker comparable to the issue report at https://gitlab.freedesktop.org/drm/intel/-/issues/7013 (though with a different cause).
I tried recompiling the i915 driver to increase the PWM frequency at boot time, but was unable to compile and replace that driver alone due to symbol version mismatches (and I didn't want to rebuild the whole kernel on an obsolete dual-core Ivy Bridge). So I tried changing the backlight frequency in the BIOS itself, which was OS-agnostic and didn't require editing Linux drivers.
I unpacked the firmware update download using uninsyde, extracted files from the 6MB UEFI archive (split between a 4MB and 2MB SPI flash chip) using UEFITool, and began decompiling the UEFI firmware in Ghidra and tracing control flow across multiple undocumented labyrinthine EFI executables. After days of puzzling over the code and cursing out Ghidra's lack of working offset pointers, I found that the PWM register's value was parsed out of a Video BIOS Table (VBT)'s backlight PWM frequency field, then converted from Hz to register values. With the help of intel_vbt_decode to decode the file, I hex-edited the VBT to change the refresh rate of its 16 display configurations (I didn't know which was used) to a less noticeable 800 Hz, fixed the file checksum, reinserted the file into a modified BIOS image, then soldered bodge wires onto my BIOS chip and reflashed the BIOS.
Sadly, this only affected pure UEFI boot and not CSM mode, and I had to run with CSM enabled since disabling it causes Windows 7 (the OS I used when the laptop was new, and which I keep this laptop just to run) to show a black screen. And I got burnt out from reversing EFI and have no motivation to figure out where CSM gets its backlight frequency. Even if I did manage to fix CSM mode, I won't be flashing my changes into the BIOS chips anytime soon, since I broke a flash chip leg and two motherboard pads while flashing my VBT-modded BIOS, and barely managed to patch the flash chip circuit back together with bodge wire soldered to the chip's stub, and painstakingly inspecting motherboard traces to find a nearby pad connected to the intended destination.
🔗 Running Windows 7 on pure UEFI
Thankfully, I found another solution in the form of UefiSeven, an EFI module that allows Windows 7 to display images properly in pure UEFI mode. To install it on an existing Windows 7 installation running in CSM mode, you only need to follow the README's steps 6 and 7:
- Boot into CSM-mode Windows 7.
- Open Explorer to your EFI System Partition, and go to
EFI\Microsoft\Boot\. Then rename
bootmgfw.original.efi, and copy UefiSeven
- Reboot into BIOS options and disable CSM.
When you next reboot, Windows 7 will display an image and run the backlight at your edited refresh rate! I've used my system in this configuration for a few weeks, and the display works perfectly, and changing screen resolution works properly as well. The only problem I've found so far is that startup-time chkdsk operations display garbled pixels instead of text, but seem to complete normally. (Some screensavers imported from Windows 98 only show a black screen which persists for a few seconds after you move your mouse, but that happens on my other computers too, and such old programs malfunctioning is to be expected.) Additionally I found that starting a Windows Virtual PC VM twice crashes the system and corrupts the screen, though I don't know if this is caused by UefiSeven.
🔗 Flicker-free backlights
Some displays like my M1 MacBook Air and my desktop displays, as well as the DF6113-based LED backlight drivers sold as replacements for CCFL bulbs on eBay/AliExpress/Amazon, dim LED backlights using current regulation (the DF6113 uses a switched-mode power MOSFET at hundreds of KHz to avoid resistive losses, hooked up to a LC filter for smoothing), rather than pulse-width modulation (rapidly switching the LEDs between fully-on and fully-off). This produces better visual results, but I did not try setting this up on my Inspiron. In fact I assume the laptop or panel's LED driver circuitry isn't even capable of current-based backlight dimming (at least without hardware modifications), though I did not tear down the hardware to confirm.
🔗 Playing music on the Ivy Bridge backlight (trying to)
While playing with the
intel_reg tool used by
intelpwm-udev, I actually managed to write a program that would dynamically rewrite the period and pulse width registers to play music off the screen backlight, which I'd listen to using a solar panel hooked up to a DC-blocking capacitor and 3.5mm audio jack. For audio, I converted a Monkey Island tune from the code at https://www.thanassis.space/monkeyisland.html. It almost worked... except that whenever I decreased the period (raised the pitch), there was a random chance the new period would be less than the current phase counter, in which case the GPU would fail to reset the phase to 0 and begin a new period (as expected), but instead the display would hang at full or zero brightness nonstop (producing a gap in the sound) until the 16-bit phase counter running at 125/128 ≈ 0.98 MHz rolled over. I've recorded my "best effort" attempt at music, at https://www.youtube.com/watch?v=7qt0ssFk410 (every recording has different gaps when pitch increases).