git: cdad55809ef5 - main - acpi_system76: Support for acpi-controlled buttons on System76

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Sat, 07 Mar 2026 15:27:52 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=cdad55809ef59239c3bbdc841ed307db68bb3971

commit cdad55809ef59239c3bbdc841ed307db68bb3971
Author:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
AuthorDate: 2026-03-06 17:15:49 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-03-07 15:27:08 +0000

    acpi_system76: Support for acpi-controlled buttons on System76
    
    Add acpi_system76 for handling acpi-controlled buttons
    on System76 Laptops.
    
    Reviewed by: imp
    Differential Revision: https://reviews.freebsd.org/D55694
---
 sys/conf/files                          |   1 +
 sys/dev/acpi_support/acpi_system76.c    | 359 ++++++++++++++++++++++++++++++++
 sys/modules/acpi/Makefile               |   1 +
 sys/modules/acpi/acpi_system76/Makefile |   7 +
 sys/x86/conf/NOTES                      |   3 +
 5 files changed, 371 insertions(+)

diff --git a/sys/conf/files b/sys/conf/files
index 632fddef2cb5..ceff5c9d6c16 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -744,6 +744,7 @@ dev/acpi_support/acpi_ibm.c	optional acpi_ibm acpi
 dev/acpi_support/acpi_panasonic.c optional acpi_panasonic acpi
 dev/acpi_support/acpi_sbl_wmi.c	optional acpi_sbl_wmi acpi
 dev/acpi_support/acpi_sony.c	optional acpi_sony acpi
+dev/acpi_support/acpi_system76.c	optional acpi_system76 acpi
 dev/acpi_support/acpi_toshiba.c	optional acpi_toshiba acpi
 dev/acpi_support/atk0110.c	optional aibs acpi
 dev/acpica/Osd/OsdDebug.c	optional acpi
diff --git a/sys/dev/acpi_support/acpi_system76.c b/sys/dev/acpi_support/acpi_system76.c
new file mode 100644
index 000000000000..916a9a61f471
--- /dev/null
+++ b/sys/dev/acpi_support/acpi_system76.c
@@ -0,0 +1,359 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+
+#include <dev/acpica/acpivar.h>
+#include <sys/sysctl.h>
+
+#define _COMPONENT ACPI_OEM
+ACPI_MODULE_NAME("system76")
+
+static char	*system76_ids[] = { "17761776", NULL };
+ACPI_SERIAL_DECL(system76, "System76 ACPI management");
+
+struct acpi_ctrl {
+	int	val;
+	bool	exists;
+};
+
+struct acpi_system76_softc {
+	device_t	dev;
+	ACPI_HANDLE	handle;
+
+	struct acpi_ctrl	kbb,	/* S76_CTRL_KBB */
+				kbc;	/* S76_CTRL_KBC */
+
+	struct sysctl_ctx_list	sysctl_ctx;
+	struct sysctl_oid	*sysctl_tree;
+};
+
+static int	acpi_system76_probe(device_t);
+static int	acpi_system76_attach(device_t);
+static int	acpi_system76_detach(device_t);
+static void	acpi_system76_init(struct acpi_system76_softc *);
+static struct acpi_ctrl *
+		acpi_system76_ctrl_map(struct acpi_system76_softc *, int);
+static int	acpi_system76_update(struct acpi_system76_softc *, int, bool);
+static int	acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS);
+static void	acpi_system76_notify_handler(ACPI_HANDLE, uint32_t, void *);
+static void	acpi_system76_check(struct acpi_system76_softc *);
+
+/* methods */
+#define	S76_CTRL_KBB	1	/* Keyboard Brightness */
+#define	S76_CTRL_KBC	2	/* Keyboard Color */
+#define	S76_CTRL_MAX	3
+
+struct s76_ctrl_table {
+	char	*name;
+	char	*get_method;
+#define S76_CTRL_GKBB	"\\_SB.S76D.GKBB"
+#define S76_CTRL_GKBC	"\\_SB.S76D.GKBC"
+
+	char	*set_method;
+#define S76_CTRL_SKBB	"\\_SB.S76D.SKBB"
+#define S76_CTRL_SKBC	"\\_SB.S76D.SKBC"
+
+	char	*desc;
+};
+
+static const struct s76_ctrl_table s76_sysctl_table[] = {
+	[S76_CTRL_KBB] = {
+		.name = "keyboard_backlight",
+		.get_method = S76_CTRL_GKBB,
+		.set_method = S76_CTRL_SKBB,
+		.desc = "Keyboard Backlight",
+	},
+	[S76_CTRL_KBC] = {
+		.name = "keyboard_color",
+		.get_method = S76_CTRL_GKBC,
+		.set_method = S76_CTRL_SKBC,
+		.desc = "Keyboard Color",
+	},
+};
+
+static device_method_t acpi_system76_methods[] = {
+	DEVMETHOD(device_probe, acpi_system76_probe),
+	DEVMETHOD(device_attach, acpi_system76_attach),
+	DEVMETHOD(device_detach, acpi_system76_detach),
+
+	DEVMETHOD_END
+};
+
+/* Notify event */
+#define	ACPI_NOTIFY_BACKLIGHT_CHANGED	0x80
+#define	ACPI_NOTIFY_COLOR_TOGGLE	0x81
+#define	ACPI_NOTIFY_COLOR_DOWN		0x82
+#define	ACPI_NOTIFY_COLOR_UP		0x83
+#define	ACPI_NOTIFY_COLOR_CHANGED	0x84
+
+static driver_t acpi_system76_driver = {
+	"acpi_system76",
+	acpi_system76_methods,
+	sizeof(struct acpi_system76_softc)
+};
+
+/*
+ * Returns corresponding acpi_ctrl of softc from method
+ */
+static struct acpi_ctrl *
+acpi_system76_ctrl_map(struct acpi_system76_softc *sc, int method)
+{
+
+	switch (method) {
+	case S76_CTRL_KBB:
+		return (&sc->kbb);
+		break;
+	case S76_CTRL_KBC:
+		return (&sc->kbc);
+		break;
+	default:
+		device_printf(sc->dev, "Driver received unknown method\n");
+		return (NULL);
+	}
+}
+
+static int
+acpi_system76_update(struct acpi_system76_softc *sc, int method, bool set)
+{
+	struct acpi_ctrl *ctrl;
+	ACPI_STATUS status;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	ACPI_SERIAL_ASSERT(system76);
+
+	if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
+		return (EINVAL);
+
+	if (set)
+		status = acpi_SetInteger(sc->handle, s76_sysctl_table[method].set_method,
+		    ctrl->val);
+	else
+		status = acpi_GetInteger(sc->handle, s76_sysctl_table[method].get_method,
+		    &ctrl->val);
+	if (ACPI_FAILURE(status)) {
+		device_printf(sc->dev, "Couldn't query method (%s)\n",
+		    s76_sysctl_table[method].name);
+		return (status);
+	}
+
+	return (0);
+}
+
+static void
+acpi_system76_notify_update(void *arg)
+{
+	struct acpi_system76_softc *sc;
+	int method;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = (struct acpi_system76_softc *)device_get_softc(arg);
+
+	ACPI_SERIAL_BEGIN(system76);
+	for (method = 1; method < S76_CTRL_MAX; method++)
+		acpi_system76_update(sc, method, false);
+	ACPI_SERIAL_END(system76);
+}
+
+static void
+acpi_system76_check(struct acpi_system76_softc *sc)
+{
+	struct acpi_ctrl *ctrl;
+	int method;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+	ACPI_SERIAL_ASSERT(system76);
+
+	for (method = 1; method < S76_CTRL_MAX; method++) {
+		if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
+			continue;
+
+		if (ACPI_FAILURE(acpi_GetInteger(sc->handle,
+		    s76_sysctl_table[method].get_method, &ctrl->val))) {
+			ctrl->exists = false;
+			device_printf(sc->dev, "Driver can't control %s\n",
+			    s76_sysctl_table[method].desc);
+		} else {
+			ctrl->exists = true;
+			acpi_system76_update(sc, method, false);
+		}
+	}
+}
+
+static void
+acpi_system76_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *ctx)
+{
+
+	ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);
+
+	switch (notify) {
+	case ACPI_NOTIFY_BACKLIGHT_CHANGED:
+	case ACPI_NOTIFY_COLOR_TOGGLE:
+	case ACPI_NOTIFY_COLOR_DOWN:
+	case ACPI_NOTIFY_COLOR_UP:
+	case ACPI_NOTIFY_COLOR_CHANGED:
+		AcpiOsExecute(OSL_NOTIFY_HANDLER,
+		    acpi_system76_notify_update, ctx);
+		break;
+	default:
+		break;
+	}
+}
+
+static int
+acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS)
+{
+	struct acpi_ctrl *ctrl;
+	struct acpi_system76_softc *sc;
+	int val, method, error;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = (struct acpi_system76_softc *)oidp->oid_arg1;
+	method = oidp->oid_arg2;
+	if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
+		return (EINVAL);
+
+	val = ctrl->val;
+	error = sysctl_handle_int(oidp, &val, 0, req);
+	if (error != 0) {
+		device_printf(sc->dev, "Driver query failed\n");
+		return (error);
+	}
+	if (req->newptr == NULL)
+		return (error);
+
+	/* Input validation */
+	switch (method) {
+	case S76_CTRL_KBB:
+		if (val > UINT8_MAX || val < 0)
+			return (EINVAL);
+		break;
+	case S76_CTRL_KBC:
+		if (val >= (1 << 24) || val < 0)
+			return (EINVAL);
+		break;
+	default:
+		break;
+	}
+
+	ctrl->val = val;
+
+	ACPI_SERIAL_BEGIN(system76);
+	error = acpi_system76_update(sc, method, true);
+	ACPI_SERIAL_END(system76);
+	return (error);
+}
+
+static void
+acpi_system76_init(struct acpi_system76_softc *sc)
+{
+	struct acpi_softc *acpi_sc;
+	struct acpi_ctrl *ctrl;
+	uint32_t method;
+
+	ACPI_SERIAL_ASSERT(system76);
+
+	acpi_system76_check(sc);
+	acpi_sc = acpi_device_get_parent_softc(sc->dev);
+	sysctl_ctx_init(&sc->sysctl_ctx);
+	sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,
+	    SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "s76",
+	    CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "system76 control");
+
+	for (method = 1; method < S76_CTRL_MAX; method++) {
+		if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)
+			continue;
+
+		if (!ctrl->exists)
+			continue;
+
+		SYSCTL_ADD_PROC(&sc->sysctl_ctx,
+		    SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, s76_sysctl_table[method].name,
+		    CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MPSAFE,
+		    sc, method, acpi_system76_sysctl_handler, "IU", s76_sysctl_table[method].desc);
+	}
+}
+
+static int
+acpi_system76_attach(device_t dev)
+{
+	struct acpi_system76_softc *sc;
+
+	ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);
+
+	sc = device_get_softc(dev);
+	sc->dev = dev;
+	sc->handle = acpi_get_handle(dev);
+
+	AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,
+	    acpi_system76_notify_handler, dev);
+
+	ACPI_SERIAL_BEGIN(system76);
+	acpi_system76_init(sc);
+	ACPI_SERIAL_END(system76);
+
+	return (0);
+}
+
+static int
+acpi_system76_detach(device_t dev)
+{
+	struct acpi_system76_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (sysctl_ctx_free(&sc->sysctl_ctx) != 0)
+		return (EBUSY);
+
+	return (0);
+}
+
+static int
+acpi_system76_probe(device_t dev)
+{
+	int rv;
+
+	if (acpi_disabled("system76") || device_get_unit(dev) > 1)
+		return (ENXIO);
+	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, system76_ids, NULL);
+	if (rv > 0) {
+		return (rv);
+	}
+
+	return (BUS_PROBE_VENDOR);
+}
+
+DRIVER_MODULE(acpi_system76, acpi, acpi_system76_driver, 0, 0);
+MODULE_VERSION(acpi_system76, 1);
+MODULE_DEPEND(acpi_system76, acpi, 1, 1, 1);
diff --git a/sys/modules/acpi/Makefile b/sys/modules/acpi/Makefile
index 5040187e906f..265e6bd6cdcb 100644
--- a/sys/modules/acpi/Makefile
+++ b/sys/modules/acpi/Makefile
@@ -9,6 +9,7 @@ SUBDIR=	\
 	acpi_panasonic \
 	acpi_sbl_wmi \
 	acpi_sony \
+	acpi_system76 \
 	acpi_toshiba \
 	acpi_video \
 	acpi_wmi \
diff --git a/sys/modules/acpi/acpi_system76/Makefile b/sys/modules/acpi/acpi_system76/Makefile
new file mode 100644
index 000000000000..86d2c91e712d
--- /dev/null
+++ b/sys/modules/acpi/acpi_system76/Makefile
@@ -0,0 +1,7 @@
+.PATH:	${SRCTOP}/sys/dev/acpi_support
+
+KMOD=	acpi_system76
+CFLAGS+=-I${SRCTOP}/sys/dev/acpi_support
+SRCS=	acpi_system76.c opt_acpi.h acpi_if.h device_if.h bus_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/x86/conf/NOTES b/sys/x86/conf/NOTES
index 501d4159b129..877cbb3beb7f 100644
--- a/sys/x86/conf/NOTES
+++ b/sys/x86/conf/NOTES
@@ -178,6 +178,9 @@ device		acpi_sbl_wmi
 # ACPI Sony extra (LCD brightness)
 device		acpi_sony
 
+# ACPI System76 extra (Keyboard brightness, Keyboard color)
+device		acpi_system76
+
 # ACPI Toshiba Extras (LCD backlight/brightness, video output, etc.)
 device		acpi_toshiba