Index | Thread | Search

From:
"Piotr K. Isajew" <pki@ex.com.pl>
Subject:
[patch] Backlight control for Lenovo Legion laptop
To:
tech@openbsd.org
Date:
Tue, 21 Oct 2025 18:30:25 +0200

Download raw body.

Thread
  • Piotr K. Isajew:

    [patch] Backlight control for Lenovo Legion laptop

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 <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsdisplayvar.h>
+
 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: