I bought a laptop only to find it has PWM flicker below a certain brightness level. The PWM can easily be triggered accidentally, for example by using the brightness keys on the keyboard, brightness sliders in various applications, or due to some other unforeseen scenario. If you are sensitive to the extent that very short exposure to PWM triggers symptoms immediately, this must be avoided. The problem with Linux is that there is no simple way to enforce a specific minimum brightness at all times. I came up with the following solution:
You can put .rules files in /etc/udev/rules.d
that do something when something happens. In our case, we want to set the backlight to our known-safe minimum brightness if PWM ever activates. I wrote a small C program that accomplishes that task.
The .rules file (I named it 99-disable-pwm
):
ACTION=="change", SUBSYSTEM=="backlight", KERNEL=="intel_backlight", RUN+="/usr/local/bin/brightness-check"
The program (replace the defined values with the ones from your backlight):
#include <fcntl.h>
#include <unistd.h>
#define MIN_BRIGHTNESS "334"
#define MAX_BRIGHTNESS "852"
#define FILENAME "/sys/class/backlight/intel_backlight/brightness"
int main(void)
{
int fd = open(FILENAME, O_RDWR);
if (fd == -1)
return 1;
char buf[sizeof(MAX_BRIGHTNESS)];
ssize_t ret = read(fd, buf, sizeof(buf));
if (ret == 0 || ret == -1)
return 1;
if (ret < sizeof(MIN_BRIGHTNESS))
goto set_min_brightness;
if (ret > sizeof(MIN_BRIGHTNESS))
return 0;
for (int i = 0; i < ret - 1; i++) {
if (buf[i] < MIN_BRIGHTNESS[i])
goto set_min_brightness;
if (buf[i] > MIN_BRIGHTNESS[i])
break;
}
return 0;
set_min_brightness:
if (lseek(fd, 0, SEEK_SET) == -1)
return 1;
#define NEW_BRIGHTNESS MIN_BRIGHTNESS "\n"
write(fd, NEW_BRIGHTNESS, sizeof(NEW_BRIGHTNESS) - 1);
fsync(fd);
close(fd);
}
You can save it in a .c file and compile and copy it to a different directory it with the following command:
gcc FILENAME -O3 -s
sudo cp a.out /usr/local/bin/brightness-check
The file name and directory don't really matter but they must be the same as specified in the .rules file.
The changes should take effect immediately and setting the brightness to a lower minimum value should no longer be possible. There is however a very small time window in which the PWM will activate. When attempting to lower the brightness below the treshold you will see the screen flicker for like 1 millisecond (not measured, just guessed). I tried to keep that time as small as possible by using a compiled C program instead of a (much slower) script. I hope it is small enough on your machine not to cause symptoms. Which may also depend on the PWM frequency. For example, with 20 kHz PWM and 1 ms time window you would look at 20 flicker periods.
I hope this helps others, too. Let me know if you have further ideas.
Update
For devices that use the Intel driver, we now have a perfect solution (thanks @ JTL and WhisperingWind), but it requires modifying and compiling the driver source code. So the following quick info is for advanced users who feel up to it and know how to safely integrate the modified driver in their system.
In the Linux source tree at drivers/gpu/drm/i915/display/intel_backlight.c
, have a look at the functions intel_backlight_set_acpi
and intel_panel_set_backlight
. In both functions each, before the line that starts with mutex_lock
, insert
if (user_level < xxx)
return;
With "xxx" being the minimum brightness value you know is safe for your device.
There is one issue left: attempts to set the brightness below that value are now blocked, but the file /sys/class/backlight/intel_backlight/brightness
may still change and contain a value different from the actual brightness. If this is bothering you (for example, brightness programs may display a wrong brightness value), you can work around it by using both the changed driver and the original udev rules solution until we find a better way.