The brightness of LED backlight in TFT displays is sometimes controlled using a technique called PWM or pulse width modulation. If implemented incorrectly, particularly if PWM frequency is too low, it can introduce distracting visual effects and induce eye strain, headaches, and even dizziness in some people[1].
Many older laptops set PWM frequency for their backlight too low. Luckily, in laptops equipped with Intel i915 GPU the PWM frequency is often controlled by the GPU and thus can be adjusted by a software.
According to the manual[2], in i915 (or
at least, in Sandy Bridge) PWM frequency is controlled by a value of the 4 upper bytes of the register 0xC8254
; the
value of these 4 bytes
represents the period of the PWM stream in PCH display raw clocks multiplied by 128
PCH display raw clocks is held in another register, 0xC6204
, and it is PCH frequency in MHz. So if the value is
0x7D
it means, that PCH frequency is 125 MHz.
Resulting PWM frequency in Hz can be calculated by the formula:
Important:
fpwm = ( reg0xC6204
× 1,000,000 ) / 128 / reg0xC8254[7..4]
This means, that to increase PWM frequency of LED backlight we have to decrease the value stored in 4 upper bytes of
the register 0xC8254
.
An application called intel_reg
from the
intel-gpu-tools[3] package lets reading and writing
i915 registers, and thus the formula can be implemented as a simple shell script (if we ignore some defensive
boilerplate):
Caution:
Although precautions have been taken, executing this script may cause damage to your system or even hardware
#!/usr/bin/env bash
function usage() {
cat << EOF
intelpwm FREQ [PCH_RAWCLK_FREQ_REG [BLC_PWM_PCH_CTL2_REG]]
FREQ desired PWM frequency in Hz
PCH_RAWCLK_FREQ_REG PCH raw clock register name or offset
BLC_PWM_PCH_CTL2_REG backlight control register name or offset
EOF
}
function reg_check() {
# If the register is a mnemonic name and not an offset, make sure that it is
# known to intel_reg utility. Unknown mnemonic likely means, that we are on a
# different generation of hardware that may have different registers layout
printf "%d" "$1" 1>/dev/null 2>&1 || \
intel_reg list | grep "$1" 1>/dev/null 2>&1 || \
(>&2 echo "Register $1 is not defined on this hardware" && false)
}
function reg_read() {
# this assumes fixed position of the value in intel_reg output
# so far this has been the case though
intel_reg read "$1" | cut -c51-60
}
FREQ=$1
PCH_RAWCLK_FREQ_REG=${2:-PCH_RAWCLK_FREQ}
BLC_PWM_PCH_CTL2_REG=${3:-BLC_PWM_PCH_CTL2}
if [ -z "$FREQ" ]; then
usage
exit 1
fi
reg_check "${PCH_RAWCLK_FREQ_REG}" || exit 1
reg_check "${BLC_PWM_PCH_CTL2_REG}" || exit 1
PCH_FREQ="$(reg_read ${PCH_RAWCLK_FREQ_REG})" 1
BLC_CTL2="$(reg_read ${BLC_PWM_PCH_CTL2_REG})"
CYCLE="${BLC_CTL2:6:4}"
HEX=$(printf "0x%08x" $((1000000*PCH_FREQ/128/FREQ)))
PERIOD="${HEX:6:9}"
>&2 echo "Writing 0x${PERIOD}${CYCLE} to register ${BLC_PWM_PCH_CTL2_REG}"
intel_reg write "${BLC_PWM_PCH_CTL2_REG}" "0x${PERIOD}${CYCLE}"
- boilerplate ends here, and actual formula implementation starts
And then, to set PWM frequency to some reasonable 800 Hz we would do
# intelpwm 800
Since the value we wrote to the register is not permanent, we have to write it again every time the power is cycled for the GPU or the backlight (screen is turned off, laptop suspended, etc.). This is easy to do with udev:
KERNEL!="intel_backlight", SUBSYSTEM!="backlight", ACTION!="change", GOTO="backlight_pwm_rules_end" RUN="intelpwm 800" LABEL="backlight_pwm_rules_end"
The script, slightly modified to receive values from a config file, and the corresponding udev rule are published on my github as intelpwm-udev[4].
- https://www.tftcentral.co.uk/articles/pulse_width_modulation.htm ↩
- Intel HD Graphics programmer’s reference manual - https://01.org/linuxgraphics/sites/default/files/documentation/snb_ihd_os_vol3_part3.pdf ↩
- http://cgit.freedesktop.org/xorg/app/intel-gpu-tools/ ↩
- https://github.com/edio/intelpwm-udev ↩