From: "Piotr K. Isajew" Subject: [patch] Backlight control for Lenovo Legion laptop To: tech@openbsd.org Date: Tue, 21 Oct 2025 18:30:25 +0200 The laptop has a NVIDIA/AMDGPU dual graphics. Display backlight is controlled via ACPI WMI. I have discussed this patch with Ted Unangst few months ago, but apparently he was busy with other things, so I'm posting it here for those, who might need it. Index: dev/acpi/acpiwmi.c =================================================================== RCS file: /home/cvs/src/sys/dev/acpi/acpiwmi.c,v diff -u -p -r1.6 acpiwmi.c --- dev/acpi/acpiwmi.c 14 Jul 2025 23:49:08 -0000 1.6 +++ dev/acpi/acpiwmi.c 21 Oct 2025 08:26:41 -0000 @@ -36,6 +36,9 @@ #define DPRINTF(x) #endif +#include +#include + struct acpiwmi_softc { struct device sc_dev; bus_space_tag_t sc_iot; @@ -137,6 +140,9 @@ guid_string(const uint8_t *guid, char *b static int wmi_asus_init(struct acpiwmi_softc *, struct guidinfo *); static int wmi_asus_event(struct wmihandler *, int); +static int wmi_nvbl_init(struct acpiwmi_softc *, struct guidinfo *); +static int wmi_nvbl_event(struct wmihandler *, int); + struct wmi_target { uint8_t guid[GUID_SIZE]; int (*init)(struct acpiwmi_softc *, struct guidinfo *); @@ -147,8 +153,14 @@ static struct wmi_target targets[] = { /* "97845ED0-4E6D-11DE-8A39-0800200C9A66" */ { 0xd0, 0x5e, 0x84, 0x97, 0x6d, 0x4e, 0xde, 0x11, 0x8a, 0x39, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 }, - wmi_asus_init, + wmi_asus_init }, + { + /* "603E9613-EF25-4338-A3D0-C46177516DB7" */ + { 0x13, 0x96, 0x3e, 0x60, 0x25, 0xef, 0x38, 0x43, + 0xa3, 0xd0, 0xc4, 0x61, 0x77, 0x51, 0x6d, 0xb7 }, + wmi_nvbl_init + } }; void @@ -177,7 +189,7 @@ acpiwmi_attach(struct device *parent, st char buf[64]; DPRINTF(("method: %s\n", guid_string(guids[i].guid, buf))); #endif - for (int j = 0; j < 1; j++) { + for (int j = 0; j < 2; j++) { if (memcmp(guids[i].guid, targets[j].guid, GUID_SIZE) == 0) targets[j].init(sc, &guids[i]); } @@ -380,5 +392,115 @@ wmi_asus_event(struct wmihandler *wmi, i DPRINTF(("asus button %d\n", code)); break; } + return 0; +} + + +/* Nvidia/amdgpu backlight control, as found on Lenovo Legion + laptop. */ +struct wminvbl { + struct wmihandler w_wmi; + int curr_brightness; + int max_brightness; +}; + +#define NVIDIA_BRIGHTNESS_METHOD_LEVEL 1 + +#define NVIDIA_BRIGHTNESS_OP_GET 0 +#define NVIDIA_BRIGHTNESS_OP_SET 1 +#define NVIDIA_BRIGHTNESS_OP_GET_MAX 2 + + +static int +wmi_nvbl_set_brightness(struct wminvbl *wh, int val) +{ + return wmi_eval_method(&wh->w_wmi, 1, + NVIDIA_BRIGHTNESS_METHOD_LEVEL, + NVIDIA_BRIGHTNESS_OP_SET, + val); +} + +static int +wmi_nvbl_get_brightness(struct wminvbl *wh) +{ + int rc = wmi_eval_method(&wh->w_wmi, 1, + NVIDIA_BRIGHTNESS_METHOD_LEVEL, + NVIDIA_BRIGHTNESS_OP_GET, 0); + return rc; +} + +static struct wminvbl * +wmi_nvbl_get(struct acpiwmi_softc *sc) +{ + struct wmihandler *wh = sc->sc_handlers.slh_first; + while (wh && wh->w_event != wmi_nvbl_event) + wh = wh->w_next.sle_next; + return (struct wminvbl*)wh; +} + +static int +wmi_nvbl_get_param(struct wsdisplay_param *dp) +{ + struct acpiwmi_softc *sc = acpiwmi_cd.cd_devs[0]; + struct wminvbl *wh = wmi_nvbl_get(sc); + if (!wh) return -1; + + switch(dp->param) { + case WSDISPLAYIO_PARAM_BRIGHTNESS: + dp->min = 0; + dp->max = wh->max_brightness>0?wh->max_brightness:0xffff; + dp->curval = wh->curr_brightness; + return 0; + default: + return -1; + } +} + +static int +wmi_nvbl_set_param(struct wsdisplay_param *dp) +{ + struct acpiwmi_softc *sc = acpiwmi_cd.cd_devs[0]; + struct wminvbl *wh = wmi_nvbl_get(sc); + if (!wh) return -1; + + switch(dp->param) { + case WSDISPLAYIO_PARAM_BRIGHTNESS: + wh->curr_brightness = dp->curval; + wmi_nvbl_set_brightness(wh, dp->curval); + return 0; + default: + return -1; + } +} + +static int +wmi_nvbl_init(struct acpiwmi_softc *sc, struct guidinfo *ginfo) +{ + struct wminvbl *wh; + + wh = malloc(sizeof(*wh), M_DEVBUF, M_NOWAIT | M_ZERO); + if(!wh) + return -1; + wh->w_wmi.w_sc = sc; + wh->w_wmi.w_event = wmi_nvbl_event; + snprintf(wh->w_wmi.w_method, sizeof(wh->w_wmi.w_method), + "WM%c%c", ginfo->oid[0], ginfo->oid[1]); + + ws_get_param = wmi_nvbl_get_param; + ws_set_param = wmi_nvbl_set_param; + + SLIST_INSERT_HEAD(&sc->sc_handlers, &wh->w_wmi, w_next); + + wh->max_brightness = wmi_eval_method(&wh->w_wmi, 1, + NVIDIA_BRIGHTNESS_METHOD_LEVEL, + NVIDIA_BRIGHTNESS_OP_GET_MAX, 0); + wh->curr_brightness = wmi_nvbl_get_brightness(wh); + return 0; +} + +static int +wmi_nvbl_event(struct wmihandler *wmi, int code) +{ + printf("unhandled event: %d\n", code); return 0; } Index: dev/pci/drm/amd/amdgpu/amdgpu_drv.c =================================================================== RCS file: /home/cvs/src/sys/dev/pci/drm/amd/amdgpu/amdgpu_drv.c,v diff -u -p -r1.64 amdgpu_drv.c --- dev/pci/drm/amd/amdgpu/amdgpu_drv.c 26 Aug 2025 03:33:49 -0000 1.64 +++ dev/pci/drm/amd/amdgpu/amdgpu_drv.c 21 Oct 2025 08:26:41 -0000 @@ -3560,10 +3560,14 @@ amdgpu_wsioctl(void *v, u_long cmd, cadd switch (dp->param) { case WSDISPLAYIO_PARAM_BRIGHTNESS: - dp->min = 0; - dp->max = bd->props.max_brightness; - dp->curval = bd->props.brightness; - return (dp->max > dp->min) ? 0 : -1; + if(ws_get_param == NULL) { + dp->min = 0; + dp->max = bd->props.max_brightness; + dp->curval = bd->props.brightness; + return (dp->max > dp->min) ? 0 : -1; + } else { + return ws_get_param(dp); + } } break; case WSDISPLAYIO_SETPARAM: @@ -3572,10 +3576,14 @@ amdgpu_wsioctl(void *v, u_long cmd, cadd switch (dp->param) { case WSDISPLAYIO_PARAM_BRIGHTNESS: - bd->props.brightness = dp->curval; - backlight_update_status(bd); - knote_locked(&adev->ddev.note, NOTE_CHANGE); - return 0; + if(ws_set_param == NULL) { + bd->props.brightness = dp->curval; + backlight_update_status(bd); + knote_locked(&adev->ddev.note, NOTE_CHANGE); + return 0; + } else { + return ws_set_param(dp); + } } break; case WSDISPLAYIO_SVIDEO: