Linux kernel backlight driver for the ThinkPad x220 FHD mod control board —
a USB HID device at 10c4:83ce. It registers /sys/class/backlight/x220-bl so
desktop environments (KDE/KWin, GNOME, etc.) can control the panel backlight
natively through their usual brightness controls.
- Forwards brightness to the control board over USB HID
SET_REPORT/GET_REPORT(output report ID0x06, feature report ID0x04; 16 levels, 0–15). - Parents the backlight device under the correct DRM connector so Wayland/KWin
discovers it automatically. It matches in priority order:
- A connected eDP connector (patched VBT →
eDP-1) - A connected connector named DP-3 (stock VBT →
DP-3) ...and skips phantom connectors likeLVDS-1that may also report as connected on this hardware.
- A connected eDP connector (patched VBT →
- Auto-reclaims the USB interface after suspend/resume and hotplug, so the
driver survives re-enumeration instead of losing the device to
usbhid. See How auto-reclaim works.
- Linux kernel headers for the running kernel (
linux-headerson Arch). - DKMS — optional, but recommended so the module rebuilds on kernel updates.
- Rust toolchain — only needed to build the optional VBT patcher.
makepkg -siThis builds and installs the x220-bl-dkms package, which:
- Installs the module source under
/usr/src/x220-bl-dkms-1.0/. - Registers it with DKMS and builds/installs it for the running kernel.
- Installs the modprobe quirk to
/etc/modprobe.d/x220-bl.conf(see below).
After a kernel update, DKMS auto-rebuilds the module.
sudo cp -r . /usr/src/x220-bl-1.0
sudo dkms add x220-bl/1.0
sudo dkms build x220-bl/1.0
sudo dkms install x220-bl/1.0Then install the modprobe quirk manually (see below).
make
sudo insmod x220_bl.koYou will also want the modprobe quirk below, or usbhid will grab the device.
The FHD control board is not HID-compliant, so usbhid / hid-generic
match it with higher priority than this driver and own interface 0 at boot.
The quirk tells usbhid to ignore the device entirely so x220-bl owns it:
# Prevent usbhid from binding the (non-HID-compliant) AGAN FHD control board
# so the x220-bl driver owns it. HID_QUIRK_IGNORE = BIT(2) = 0x4.
options usbhid quirks=0x10c4:0x83ce:0x4
On Arch, makepkg -si installs this to /etc/modprobe.d/x220-bl.conf
automatically (it is backed up by the package). On other distros, install it
manually:
sudo cp x220-bl.conf /etc/modprobe.d/
sudo modprobe -r usbhid && sudo modprobe usbhid # or just rebootThis is the first layer of auto-reclaim — see the next section.
# Set brightness (0-15)
echo 15 > /sys/class/backlight/x220-bl/brightness
# Read current brightness directly from the device
cat /sys/class/backlight/x220-bl/actual_brightness
# Max brightness
cat /sys/class/backlight/x220-bl/max_brightnessNote: the backlight core caches
brightness, socat brightnessdoes not round-trip to the device. Readactual_brightnessto verify a real device read.
To unload the module:
sudo rmmod x220_blReclaiming the interface after suspend/resume (and hotplug) is done in two layers. Both are recommended; together they make the driver robust.
Layer 1 — modprobe quirk (x220-bl.conf, described above): prevents
usbhid from binding the board at all, so x220-bl wins interface 0 cleanly
on every (re-)enumeration.
Layer 2 — in-module notifier: the module registers on the USB notifier
chain via usb_register_notify(). On USB_DEVICE_ADD for 10c4:83ce it
defers into a dedicated ordered workqueue (claim/probe sleep, but the
notifier runs in atomic context), resolves interface 0 via usb_ifnum_to_if(),
and calls the reclaim helper which unbinds any current owner, claims the
interface, and probes it. This covers resume re-enumeration and hotplug even
when Layer 1 is absent.
At boot, the notifier only sees devices added after registration, so the
module also does a synchronous boot-time claim via usb_for_each_dev() to pick
up devices that were already present.
With Layer 1 active, the device is already ours by the time USB_DEVICE_ADD
fires, so the reclaim helper is a no-op. Without Layer 1, the worker steals it
back from usbhid. Either way, the driver ends up owning the interface.
ls /sys/class/backlight/ # should list x220-bl
readlink -f /sys/class/backlight/x220-bl # should resolve under drm/card*/card*-eDP-1
# (or card*-DP-3 with the stock VBT)
cat /sys/class/drm/card*/status # LVDS-1 should be gone or disconnected
dmesg | grep x220-bl # connector match + claim log linesIf the backlight shows up but reads look stale, remember to check
actual_brightness, not brightness (see the note in Usage).
This is an optional, separate tool under vbt/ (Rust). It is not required
to use the driver — with the stock VBT the panel appears as DP-3 and the
driver matches that just fine. Patching the VBT only changes the connector name
to eDP-1 so i915 stops creating a phantom LVDS-1 / intel_backlight.
The x220 FHD mod replaces the LVDS panel with an eDP panel wired through DP-D
(DRM connector DP-3), but the stock VBT still describes an LVDS internal
panel. This causes i915 to create both LVDS-1 + intel_backlight and DP-3,
and some desktop environments prefer the phantom intel_backlight. The vbt/
tool patches the VBT so i915 treats DP-D as the internal panel:
- Child device 0 (LVDS/LFP1) is replaced with the EFP3/DP-D child.
- The replacement gets handle
0x0008(LFP1) and type0x78c6(internal + eDP). - The old EFP3 slot is zeroed out.
- Boot display type is set to eDP in
BDB_DRIVER_FEATURES. - The VBT header checksum is recalculated.
cd vbt
cargo build# Extract from the running kernel
cp /sys/kernel/debug/dri/*/i915_vbt /tmp/x220.vbt
# Patch it (writes x220-edp.vbt next to the input)
cargo run -- /tmp/x220.vbt
# Install the patched VBT
sudo cp x220-edp.vbt /lib/firmware/If your root filesystem is encrypted, embed the VBT in the initramfs so i915
can load it before root is mounted. Edit /etc/mkinitcpio.conf:
FILES=(/lib/firmware/x220-edp.vbt)
Add to /etc/kernel/cmdline:
i915.vbt_firmware=x220-edp.vbt
For rEFInd, also update /boot/refind_linux.conf to match.
sudo mkinitcpio -P
sudo rebootls /sys/class/backlight/ # should show x220-bl
readlink /sys/class/backlight/x220-bl # should be under drm/card*/card*-eDP-1/
cat /sys/class/drm/card*/status # LVDS-1 should be gone or disconnectedGPL-2.0-only. See LICENSE.