Path: blob/main/sys/dev/acpi_support/acpi_system76.c
178428 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2026 Pouria Mousavizadeh Tehrani <[email protected]>4* All rights reserved.5*6* Redistribution and use in source and binary forms, with or without7* modification, are permitted provided that the following conditions8* are met:9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11* 2. Redistributions in binary form must reproduce the above copyright12* notice, this list of conditions and the following disclaimer in the13* documentation and/or other materials provided with the distribution.14*15* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND16* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE17* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE18* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE19* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL20* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS21* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)22* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT23* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY24* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF25* SUCH DAMAGE.26*/2728#include "opt_acpi.h"2930#include <sys/param.h>31#include <sys/bus.h>32#include <sys/kernel.h>33#include <sys/module.h>34#include <sys/sysctl.h>3536#include <contrib/dev/acpica/include/acpi.h>37#include <contrib/dev/acpica/include/accommon.h>3839#include <dev/acpica/acpivar.h>40#include <dev/backlight/backlight.h>4142#include "backlight_if.h"4344#define _COMPONENT ACPI_OEM45ACPI_MODULE_NAME("system76")4647static char *system76_ids[] = { "17761776", NULL };48ACPI_SERIAL_DECL(system76, "System76 ACPI management");4950struct acpi_ctrl {51int val;52bool exists;53};5455struct acpi_system76_softc {56device_t dev;57ACPI_HANDLE handle;5859struct acpi_ctrl kbb, /* S76_CTRL_KBB */60kbc, /* S76_CTRL_KBC */61bctl, /* S76_CTRL_BCTL */62bcth; /* S76_CTRL_BCTH */6364struct sysctl_ctx_list sysctl_ctx;65struct sysctl_oid *sysctl_tree;66struct cdev *kbb_bkl;67uint8_t backlight_level;68};6970static int acpi_system76_probe(device_t);71static int acpi_system76_attach(device_t);72static int acpi_system76_detach(device_t);73static int acpi_system76_suspend(device_t);74static int acpi_system76_resume(device_t);75static int acpi_system76_shutdown(device_t);76static void acpi_system76_init(struct acpi_system76_softc *);77static struct acpi_ctrl *78acpi_system76_ctrl_map(struct acpi_system76_softc *, int);79static int acpi_system76_update(struct acpi_system76_softc *, int, bool);80static int acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS);81static void acpi_system76_notify_handler(ACPI_HANDLE, uint32_t, void *);82static void acpi_system76_check(struct acpi_system76_softc *);83static int acpi_system76_backlight_update_status(device_t dev,84struct backlight_props *props);85static int acpi_system76_backlight_get_status(device_t dev,86struct backlight_props *props);87static int acpi_system76_backlight_get_info(device_t dev,88struct backlight_info *info);8990/* methods */91enum {92S76_CTRL_KBB = 1, /* Keyboard Brightness */93S76_CTRL_KBC = 2, /* Keyboard Color */94S76_CTRL_BCTL = 3, /* Battery Charging Start Thresholds */95S76_CTRL_BCTH = 4, /* Battery Charging End Thresholds */96};97#define S76_CTRL_MAX 59899struct s76_ctrl_table {100char *name;101char *get_method;102#define S76_CTRL_GKBB "\\_SB.S76D.GKBB"103#define S76_CTRL_GKBC "\\_SB.S76D.GKBC"104#define S76_CTRL_GBCT "\\_SB.PCI0.LPCB.EC0.GBCT"105106char *set_method;107#define S76_CTRL_SKBB "\\_SB.S76D.SKBB"108#define S76_CTRL_SKBC "\\_SB.S76D.SKBC"109#define S76_CTRL_SBCT "\\_SB.PCI0.LPCB.EC0.SBCT"110111char *desc;112};113114static const struct s76_ctrl_table s76_sysctl_table[] = {115[S76_CTRL_KBB] = {116.name = "keyboard_backlight",117.get_method = S76_CTRL_GKBB,118.set_method = S76_CTRL_SKBB,119.desc = "Keyboard Backlight",120},121[S76_CTRL_KBC] = {122.name = "keyboard_color",123.get_method = S76_CTRL_GKBC,124.set_method = S76_CTRL_SKBC,125.desc = "Keyboard Color",126},127[S76_CTRL_BCTL] = {128.name = "battery_charge_min",129.get_method = S76_CTRL_GBCT,130.set_method = S76_CTRL_SBCT,131.desc = "Start charging the battery when this threshold is reached (percentage)",132},133[S76_CTRL_BCTH] = {134.name = "battery_charge_max",135.get_method = S76_CTRL_GBCT,136.set_method = S76_CTRL_SBCT,137.desc = "Stop charging the battery when this threshold is reached (percentage)",138},139};140141static device_method_t acpi_system76_methods[] = {142/* Device interface */143DEVMETHOD(device_probe, acpi_system76_probe),144DEVMETHOD(device_attach, acpi_system76_attach),145DEVMETHOD(device_detach, acpi_system76_detach),146DEVMETHOD(device_suspend, acpi_system76_suspend),147DEVMETHOD(device_resume, acpi_system76_resume),148DEVMETHOD(device_shutdown, acpi_system76_shutdown),149150/* Backlight interface */151DEVMETHOD(backlight_update_status, acpi_system76_backlight_update_status),152DEVMETHOD(backlight_get_status, acpi_system76_backlight_get_status),153DEVMETHOD(backlight_get_info, acpi_system76_backlight_get_info),154155DEVMETHOD_END156};157158/* Notify event */159#define ACPI_NOTIFY_BACKLIGHT_CHANGED 0x80160#define ACPI_NOTIFY_COLOR_TOGGLE 0x81161#define ACPI_NOTIFY_COLOR_DOWN 0x82162#define ACPI_NOTIFY_COLOR_UP 0x83163#define ACPI_NOTIFY_COLOR_CHANGED 0x84164165static driver_t acpi_system76_driver = {166"acpi_system76",167acpi_system76_methods,168sizeof(struct acpi_system76_softc)169};170171static const uint32_t acpi_system76_backlight_levels[] = {1720, 6, 12, 18, 24, 30, 36, 42,17348, 54, 60, 66, 72, 78, 84, 100174};175176static inline uint32_t177devstate_to_backlight(uint32_t val)178{179return (acpi_system76_backlight_levels[val >> 4 & 0xf]);180}181182static inline uint32_t183backlight_to_devstate(uint32_t bkl)184{185int i;186uint32_t val;187188for (i = 0; i < nitems(acpi_system76_backlight_levels); i++) {189if (bkl < acpi_system76_backlight_levels[i])190break;191}192val = (i - 1) * 16;193if (val > 224)194val = 255;195return (val);196}197198/*199* Returns corresponding acpi_ctrl of softc from method200*/201static struct acpi_ctrl *202acpi_system76_ctrl_map(struct acpi_system76_softc *sc, int method)203{204205switch (method) {206case S76_CTRL_KBB:207return (&sc->kbb);208case S76_CTRL_KBC:209return (&sc->kbc);210case S76_CTRL_BCTL:211return (&sc->bctl);212case S76_CTRL_BCTH:213return (&sc->bcth);214default:215device_printf(sc->dev, "Driver received unknown method\n");216return (NULL);217}218}219220static int221acpi_system76_update(struct acpi_system76_softc *sc, int method, bool set)222{223struct acpi_ctrl *ctrl;224ACPI_STATUS status;225ACPI_BUFFER Buf;226ACPI_OBJECT Arg[2], Obj;227ACPI_OBJECT_LIST Args;228229ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);230ACPI_SERIAL_ASSERT(system76);231232if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)233return (EINVAL);234235switch (method) {236case S76_CTRL_BCTL:237case S76_CTRL_BCTH:238Arg[0].Type = ACPI_TYPE_INTEGER;239Arg[0].Integer.Value = method == S76_CTRL_BCTH ? 1 : 0;240Args.Count = set ? 2 : 1;241Args.Pointer = Arg;242Buf.Length = sizeof(Obj);243Buf.Pointer = &Obj;244245if (set) {246Arg[1].Type = ACPI_TYPE_INTEGER;247Arg[1].Integer.Value = ctrl->val;248249status = AcpiEvaluateObject(sc->handle,250s76_sysctl_table[method].set_method, &Args, &Buf);251} else {252status = AcpiEvaluateObject(sc->handle,253s76_sysctl_table[method].get_method, &Args, &Buf);254if (ACPI_SUCCESS(status) &&255Obj.Type == ACPI_TYPE_INTEGER)256ctrl->val = Obj.Integer.Value;257}258break;259case S76_CTRL_KBB:260case S76_CTRL_KBC:261if (set)262status = acpi_SetInteger(sc->handle, s76_sysctl_table[method].set_method,263ctrl->val);264else265status = acpi_GetInteger(sc->handle, s76_sysctl_table[method].get_method,266&ctrl->val);267break;268}269270if (ACPI_FAILURE(status)) {271device_printf(sc->dev, "Couldn't query method (%s)\n",272s76_sysctl_table[method].name);273return (status);274}275276return (0);277}278279static void280acpi_system76_notify_update(void *arg)281{282struct acpi_system76_softc *sc;283int method;284285ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);286287sc = (struct acpi_system76_softc *)device_get_softc(arg);288289ACPI_SERIAL_BEGIN(system76);290for (method = 1; method < S76_CTRL_MAX; method++) {291if (method == S76_CTRL_BCTL ||292method == S76_CTRL_BCTH)293continue;294acpi_system76_update(sc, method, false);295}296ACPI_SERIAL_END(system76);297298if (sc->kbb_bkl != NULL)299sc->backlight_level = devstate_to_backlight(sc->kbb.val);300}301302static void303acpi_system76_check(struct acpi_system76_softc *sc)304{305struct acpi_ctrl *ctrl;306int method;307308ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);309ACPI_SERIAL_ASSERT(system76);310311for (method = 1; method < S76_CTRL_MAX; method++) {312if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)313continue;314315/* available in all models */316if (method == S76_CTRL_BCTL ||317method == S76_CTRL_BCTH) {318ctrl->exists = true;319acpi_system76_update(sc, method, false);320continue;321}322323if (ACPI_FAILURE(acpi_GetInteger(sc->handle,324s76_sysctl_table[method].get_method, &ctrl->val))) {325ctrl->exists = false;326device_printf(sc->dev, "Driver can't control %s\n",327s76_sysctl_table[method].desc);328} else {329ctrl->exists = true;330acpi_system76_update(sc, method, false);331}332}333}334335static void336acpi_system76_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *ctx)337{338339ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify);340341switch (notify) {342case ACPI_NOTIFY_BACKLIGHT_CHANGED:343case ACPI_NOTIFY_COLOR_TOGGLE:344case ACPI_NOTIFY_COLOR_DOWN:345case ACPI_NOTIFY_COLOR_UP:346case ACPI_NOTIFY_COLOR_CHANGED:347AcpiOsExecute(OSL_NOTIFY_HANDLER,348acpi_system76_notify_update, ctx);349break;350default:351break;352}353}354355static int356acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS)357{358struct acpi_ctrl *ctrl, *ctrl_cmp;359struct acpi_system76_softc *sc;360int val, method, error;361bool update;362363ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);364365sc = (struct acpi_system76_softc *)oidp->oid_arg1;366method = oidp->oid_arg2;367if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)368return (EINVAL);369370val = ctrl->val;371error = sysctl_handle_int(oidp, &val, 0, req);372if (error != 0) {373device_printf(sc->dev, "Driver query failed\n");374return (error);375}376377if (req->newptr == NULL) {378/*379* ACPI will not notify us if battery thresholds changes380* outside this module. Therefore, always fetch those values.381*/382if (method != S76_CTRL_BCTL && method != S76_CTRL_BCTH)383return (error);384update = false;385} else {386/* Input validation */387switch (method) {388case S76_CTRL_KBB:389if (val > UINT8_MAX || val < 0)390return (EINVAL);391if (sc->kbb_bkl != NULL)392sc->backlight_level = devstate_to_backlight(val);393break;394case S76_CTRL_KBC:395if (val >= (1 << 24) || val < 0)396return (EINVAL);397break;398case S76_CTRL_BCTL:399if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTH)) == NULL)400return (EINVAL);401if (val > 100 || val < 0 || val >= ctrl_cmp->val)402return (EINVAL);403break;404case S76_CTRL_BCTH:405if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTL)) == NULL)406return (EINVAL);407if (val > 100 || val < 0 || val <= ctrl_cmp->val)408return (EINVAL);409break;410}411ctrl->val = val;412update = true;413}414415ACPI_SERIAL_BEGIN(system76);416error = acpi_system76_update(sc, method, update);417ACPI_SERIAL_END(system76);418return (error);419}420421static void422acpi_system76_init(struct acpi_system76_softc *sc)423{424struct acpi_softc *acpi_sc;425struct acpi_ctrl *ctrl;426uint32_t method;427428ACPI_SERIAL_ASSERT(system76);429430acpi_system76_check(sc);431acpi_sc = acpi_device_get_parent_softc(sc->dev);432sysctl_ctx_init(&sc->sysctl_ctx);433sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx,434SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "s76",435CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "system76 control");436437for (method = 1; method < S76_CTRL_MAX; method++) {438if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL)439continue;440441if (!ctrl->exists)442continue;443444if (method == S76_CTRL_KBB) {445sc->kbb_bkl = backlight_register("system76_keyboard", sc->dev);446if (sc->kbb_bkl == NULL)447device_printf(sc->dev, "Can not register backlight\n");448else449sc->backlight_level = devstate_to_backlight(sc->kbb.val);450}451452SYSCTL_ADD_PROC(&sc->sysctl_ctx,453SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, s76_sysctl_table[method].name,454CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MPSAFE,455sc, method, acpi_system76_sysctl_handler, "IU", s76_sysctl_table[method].desc);456}457}458459static int460acpi_system76_backlight_update_status(device_t dev, struct backlight_props461*props)462{463struct acpi_system76_softc *sc;464465sc = device_get_softc(dev);466if (sc->kbb.val != backlight_to_devstate(props->brightness)) {467sc->kbb.val = backlight_to_devstate(props->brightness);468acpi_system76_update(sc, S76_CTRL_KBB, true);469}470sc->backlight_level = props->brightness;471472return (0);473}474475static int476acpi_system76_backlight_get_status(device_t dev, struct backlight_props *props)477{478struct acpi_system76_softc *sc;479480sc = device_get_softc(dev);481props->brightness = sc->backlight_level;482props->nlevels = nitems(acpi_system76_backlight_levels);483memcpy(props->levels, acpi_system76_backlight_levels,484sizeof(acpi_system76_backlight_levels));485486return (0);487}488489static int490acpi_system76_backlight_get_info(device_t dev, struct backlight_info *info)491{492info->type = BACKLIGHT_TYPE_KEYBOARD;493strlcpy(info->name, "System76 Keyboard", BACKLIGHTMAXNAMELENGTH);494495return (0);496}497498static int499acpi_system76_attach(device_t dev)500{501struct acpi_system76_softc *sc;502503ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__);504505sc = device_get_softc(dev);506sc->dev = dev;507sc->handle = acpi_get_handle(dev);508509AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY,510acpi_system76_notify_handler, dev);511512ACPI_SERIAL_BEGIN(system76);513acpi_system76_init(sc);514ACPI_SERIAL_END(system76);515516return (0);517}518519static int520acpi_system76_detach(device_t dev)521{522struct acpi_system76_softc *sc;523524sc = device_get_softc(dev);525if (sysctl_ctx_free(&sc->sysctl_ctx) != 0)526return (EBUSY);527528AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY,529acpi_system76_notify_handler);530531if (sc->kbb_bkl != NULL)532backlight_destroy(sc->kbb_bkl);533534return (0);535}536537static int538acpi_system76_suspend(device_t dev)539{540struct acpi_system76_softc *sc;541struct acpi_ctrl *ctrl;542543sc = device_get_softc(dev);544if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) {545ctrl->val = 0;546acpi_system76_update(sc, S76_CTRL_KBB, true);547}548549return (0);550}551552static int553acpi_system76_resume(device_t dev)554{555struct acpi_system76_softc *sc;556struct acpi_ctrl *ctrl;557558sc = device_get_softc(dev);559if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) {560ctrl->val = backlight_to_devstate(sc->backlight_level);561acpi_system76_update(sc, S76_CTRL_KBB, true);562}563564return (0);565}566567static int568acpi_system76_shutdown(device_t dev)569{570return (acpi_system76_detach(dev));571}572573static int574acpi_system76_probe(device_t dev)575{576int rv;577578if (acpi_disabled("system76") || device_get_unit(dev) > 1)579return (ENXIO);580rv = ACPI_ID_PROBE(device_get_parent(dev), dev, system76_ids, NULL);581if (rv > 0) {582return (rv);583}584585return (BUS_PROBE_VENDOR);586}587588DRIVER_MODULE(acpi_system76, acpi, acpi_system76_driver, 0, 0);589MODULE_VERSION(acpi_system76, 1);590MODULE_DEPEND(acpi_system76, acpi, 1, 1, 1);591MODULE_DEPEND(acpi_system76, backlight, 1, 1, 1);592593594