1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2026 Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include "opt_acpi.h" 30 31 #include <sys/param.h> 32 #include <sys/bus.h> 33 #include <sys/kernel.h> 34 #include <sys/module.h> 35 #include <sys/sysctl.h> 36 37 #include <contrib/dev/acpica/include/acpi.h> 38 #include <contrib/dev/acpica/include/accommon.h> 39 40 #include <dev/acpica/acpivar.h> 41 #include <dev/backlight/backlight.h> 42 43 #include "backlight_if.h" 44 45 #define _COMPONENT ACPI_OEM 46 ACPI_MODULE_NAME("system76") 47 48 static char *system76_ids[] = { "17761776", NULL }; 49 ACPI_SERIAL_DECL(system76, "System76 ACPI management"); 50 51 struct acpi_ctrl { 52 int val; 53 bool exists; 54 }; 55 56 struct acpi_system76_softc { 57 device_t dev; 58 ACPI_HANDLE handle; 59 60 struct acpi_ctrl kbb, /* S76_CTRL_KBB */ 61 kbc, /* S76_CTRL_KBC */ 62 bctl, /* S76_CTRL_BCTL */ 63 bcth; /* S76_CTRL_BCTH */ 64 65 struct sysctl_ctx_list sysctl_ctx; 66 struct sysctl_oid *sysctl_tree; 67 struct cdev *kbb_bkl; 68 uint8_t backlight_level; 69 }; 70 71 static int acpi_system76_probe(device_t); 72 static int acpi_system76_attach(device_t); 73 static int acpi_system76_detach(device_t); 74 static int acpi_system76_suspend(device_t); 75 static int acpi_system76_resume(device_t); 76 static int acpi_system76_shutdown(device_t); 77 static void acpi_system76_init(struct acpi_system76_softc *); 78 static struct acpi_ctrl * 79 acpi_system76_ctrl_map(struct acpi_system76_softc *, int); 80 static int acpi_system76_update(struct acpi_system76_softc *, int, bool); 81 static int acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS); 82 static void acpi_system76_notify_handler(ACPI_HANDLE, uint32_t, void *); 83 static void acpi_system76_check(struct acpi_system76_softc *); 84 static int acpi_system76_backlight_update_status(device_t dev, 85 struct backlight_props *props); 86 static int acpi_system76_backlight_get_status(device_t dev, 87 struct backlight_props *props); 88 static int acpi_system76_backlight_get_info(device_t dev, 89 struct backlight_info *info); 90 91 /* methods */ 92 enum { 93 S76_CTRL_KBB = 1, /* Keyboard Brightness */ 94 S76_CTRL_KBC = 2, /* Keyboard Color */ 95 S76_CTRL_BCTL = 3, /* Battery Charging Start Thresholds */ 96 S76_CTRL_BCTH = 4, /* Battery Charging End Thresholds */ 97 }; 98 #define S76_CTRL_MAX 5 99 100 struct s76_ctrl_table { 101 char *name; 102 char *get_method; 103 #define S76_CTRL_GKBB "\\_SB.S76D.GKBB" 104 #define S76_CTRL_GKBC "\\_SB.S76D.GKBC" 105 #define S76_CTRL_GBCT "\\_SB.PCI0.LPCB.EC0.GBCT" 106 107 char *set_method; 108 #define S76_CTRL_SKBB "\\_SB.S76D.SKBB" 109 #define S76_CTRL_SKBC "\\_SB.S76D.SKBC" 110 #define S76_CTRL_SBCT "\\_SB.PCI0.LPCB.EC0.SBCT" 111 112 char *desc; 113 }; 114 115 static const struct s76_ctrl_table s76_sysctl_table[] = { 116 [S76_CTRL_KBB] = { 117 .name = "keyboard_backlight", 118 .get_method = S76_CTRL_GKBB, 119 .set_method = S76_CTRL_SKBB, 120 .desc = "Keyboard Backlight", 121 }, 122 [S76_CTRL_KBC] = { 123 .name = "keyboard_color", 124 .get_method = S76_CTRL_GKBC, 125 .set_method = S76_CTRL_SKBC, 126 .desc = "Keyboard Color", 127 }, 128 [S76_CTRL_BCTL] = { 129 .name = "battery_charge_min", 130 .get_method = S76_CTRL_GBCT, 131 .set_method = S76_CTRL_SBCT, 132 .desc = "Start charging the battery when this threshold is reached (percentage)", 133 }, 134 [S76_CTRL_BCTH] = { 135 .name = "battery_charge_max", 136 .get_method = S76_CTRL_GBCT, 137 .set_method = S76_CTRL_SBCT, 138 .desc = "Stop charging the battery when this threshold is reached (percentage)", 139 }, 140 }; 141 142 static device_method_t acpi_system76_methods[] = { 143 /* Device interface */ 144 DEVMETHOD(device_probe, acpi_system76_probe), 145 DEVMETHOD(device_attach, acpi_system76_attach), 146 DEVMETHOD(device_detach, acpi_system76_detach), 147 DEVMETHOD(device_suspend, acpi_system76_suspend), 148 DEVMETHOD(device_resume, acpi_system76_resume), 149 DEVMETHOD(device_shutdown, acpi_system76_shutdown), 150 151 /* Backlight interface */ 152 DEVMETHOD(backlight_update_status, acpi_system76_backlight_update_status), 153 DEVMETHOD(backlight_get_status, acpi_system76_backlight_get_status), 154 DEVMETHOD(backlight_get_info, acpi_system76_backlight_get_info), 155 156 DEVMETHOD_END 157 }; 158 159 /* Notify event */ 160 #define ACPI_NOTIFY_BACKLIGHT_CHANGED 0x80 161 #define ACPI_NOTIFY_COLOR_TOGGLE 0x81 162 #define ACPI_NOTIFY_COLOR_DOWN 0x82 163 #define ACPI_NOTIFY_COLOR_UP 0x83 164 #define ACPI_NOTIFY_COLOR_CHANGED 0x84 165 166 static driver_t acpi_system76_driver = { 167 "acpi_system76", 168 acpi_system76_methods, 169 sizeof(struct acpi_system76_softc) 170 }; 171 172 static const uint32_t acpi_system76_backlight_levels[] = { 173 0, 6, 12, 18, 24, 30, 36, 42, 174 48, 54, 60, 66, 72, 78, 84, 100 175 }; 176 177 static inline uint32_t 178 devstate_to_backlight(uint32_t val) 179 { 180 return (acpi_system76_backlight_levels[val >> 4 & 0xf]); 181 } 182 183 static inline uint32_t 184 backlight_to_devstate(uint32_t bkl) 185 { 186 int i; 187 uint32_t val; 188 189 for (i = 0; i < nitems(acpi_system76_backlight_levels); i++) { 190 if (bkl < acpi_system76_backlight_levels[i]) 191 break; 192 } 193 val = (i - 1) * 16; 194 if (val > 224) 195 val = 255; 196 return (val); 197 } 198 199 /* 200 * Returns corresponding acpi_ctrl of softc from method 201 */ 202 static struct acpi_ctrl * 203 acpi_system76_ctrl_map(struct acpi_system76_softc *sc, int method) 204 { 205 206 switch (method) { 207 case S76_CTRL_KBB: 208 return (&sc->kbb); 209 case S76_CTRL_KBC: 210 return (&sc->kbc); 211 case S76_CTRL_BCTL: 212 return (&sc->bctl); 213 case S76_CTRL_BCTH: 214 return (&sc->bcth); 215 default: 216 device_printf(sc->dev, "Driver received unknown method\n"); 217 return (NULL); 218 } 219 } 220 221 static int 222 acpi_system76_update(struct acpi_system76_softc *sc, int method, bool set) 223 { 224 struct acpi_ctrl *ctrl; 225 ACPI_STATUS status; 226 ACPI_BUFFER Buf; 227 ACPI_OBJECT Arg[2], Obj; 228 ACPI_OBJECT_LIST Args; 229 230 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 231 ACPI_SERIAL_ASSERT(system76); 232 233 if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL) 234 return (EINVAL); 235 236 switch (method) { 237 case S76_CTRL_BCTL: 238 case S76_CTRL_BCTH: 239 Arg[0].Type = ACPI_TYPE_INTEGER; 240 Arg[0].Integer.Value = method == S76_CTRL_BCTH ? 1 : 0; 241 Args.Count = set ? 2 : 1; 242 Args.Pointer = Arg; 243 Buf.Length = sizeof(Obj); 244 Buf.Pointer = &Obj; 245 246 if (set) { 247 Arg[1].Type = ACPI_TYPE_INTEGER; 248 Arg[1].Integer.Value = ctrl->val; 249 250 status = AcpiEvaluateObject(sc->handle, 251 s76_sysctl_table[method].set_method, &Args, &Buf); 252 } else { 253 status = AcpiEvaluateObject(sc->handle, 254 s76_sysctl_table[method].get_method, &Args, &Buf); 255 if (ACPI_SUCCESS(status) && 256 Obj.Type == ACPI_TYPE_INTEGER) 257 ctrl->val = Obj.Integer.Value; 258 } 259 break; 260 case S76_CTRL_KBB: 261 case S76_CTRL_KBC: 262 if (set) 263 status = acpi_SetInteger(sc->handle, s76_sysctl_table[method].set_method, 264 ctrl->val); 265 else 266 status = acpi_GetInteger(sc->handle, s76_sysctl_table[method].get_method, 267 &ctrl->val); 268 break; 269 } 270 271 if (ACPI_FAILURE(status)) { 272 device_printf(sc->dev, "Couldn't query method (%s)\n", 273 s76_sysctl_table[method].name); 274 return (status); 275 } 276 277 return (0); 278 } 279 280 static void 281 acpi_system76_notify_update(void *arg) 282 { 283 struct acpi_system76_softc *sc; 284 int method; 285 286 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 287 288 sc = (struct acpi_system76_softc *)device_get_softc(arg); 289 290 ACPI_SERIAL_BEGIN(system76); 291 for (method = 1; method < S76_CTRL_MAX; method++) { 292 if (method == S76_CTRL_BCTL || 293 method == S76_CTRL_BCTH) 294 continue; 295 acpi_system76_update(sc, method, false); 296 } 297 ACPI_SERIAL_END(system76); 298 299 if (sc->kbb_bkl != NULL) 300 sc->backlight_level = devstate_to_backlight(sc->kbb.val); 301 } 302 303 static void 304 acpi_system76_check(struct acpi_system76_softc *sc) 305 { 306 struct acpi_ctrl *ctrl; 307 int method; 308 309 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 310 ACPI_SERIAL_ASSERT(system76); 311 312 for (method = 1; method < S76_CTRL_MAX; method++) { 313 if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL) 314 continue; 315 316 /* available in all models */ 317 if (method == S76_CTRL_BCTL || 318 method == S76_CTRL_BCTH) { 319 ctrl->exists = true; 320 acpi_system76_update(sc, method, false); 321 continue; 322 } 323 324 if (ACPI_FAILURE(acpi_GetInteger(sc->handle, 325 s76_sysctl_table[method].get_method, &ctrl->val))) { 326 ctrl->exists = false; 327 device_printf(sc->dev, "Driver can't control %s\n", 328 s76_sysctl_table[method].desc); 329 } else { 330 ctrl->exists = true; 331 acpi_system76_update(sc, method, false); 332 } 333 } 334 } 335 336 static void 337 acpi_system76_notify_handler(ACPI_HANDLE handle, uint32_t notify, void *ctx) 338 { 339 340 ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); 341 342 switch (notify) { 343 case ACPI_NOTIFY_BACKLIGHT_CHANGED: 344 case ACPI_NOTIFY_COLOR_TOGGLE: 345 case ACPI_NOTIFY_COLOR_DOWN: 346 case ACPI_NOTIFY_COLOR_UP: 347 case ACPI_NOTIFY_COLOR_CHANGED: 348 AcpiOsExecute(OSL_NOTIFY_HANDLER, 349 acpi_system76_notify_update, ctx); 350 break; 351 default: 352 break; 353 } 354 } 355 356 static int 357 acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS) 358 { 359 struct acpi_ctrl *ctrl, *ctrl_cmp; 360 struct acpi_system76_softc *sc; 361 int val, method, error; 362 bool update; 363 364 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 365 366 sc = (struct acpi_system76_softc *)oidp->oid_arg1; 367 method = oidp->oid_arg2; 368 if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL) 369 return (EINVAL); 370 371 val = ctrl->val; 372 error = sysctl_handle_int(oidp, &val, 0, req); 373 if (error != 0) { 374 device_printf(sc->dev, "Driver query failed\n"); 375 return (error); 376 } 377 378 if (req->newptr == NULL) { 379 /* 380 * ACPI will not notify us if battery thresholds changes 381 * outside this module. Therefore, always fetch those values. 382 */ 383 if (method != S76_CTRL_BCTL && method != S76_CTRL_BCTH) 384 return (error); 385 update = false; 386 } else { 387 /* Input validation */ 388 switch (method) { 389 case S76_CTRL_KBB: 390 if (val > UINT8_MAX || val < 0) 391 return (EINVAL); 392 if (sc->kbb_bkl != NULL) 393 sc->backlight_level = devstate_to_backlight(val); 394 break; 395 case S76_CTRL_KBC: 396 if (val >= (1 << 24) || val < 0) 397 return (EINVAL); 398 break; 399 case S76_CTRL_BCTL: 400 if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTH)) == NULL) 401 return (EINVAL); 402 if (val > 100 || val < 0 || val >= ctrl_cmp->val) 403 return (EINVAL); 404 break; 405 case S76_CTRL_BCTH: 406 if ((ctrl_cmp = acpi_system76_ctrl_map(sc, S76_CTRL_BCTL)) == NULL) 407 return (EINVAL); 408 if (val > 100 || val < 0 || val <= ctrl_cmp->val) 409 return (EINVAL); 410 break; 411 } 412 ctrl->val = val; 413 update = true; 414 } 415 416 ACPI_SERIAL_BEGIN(system76); 417 error = acpi_system76_update(sc, method, update); 418 ACPI_SERIAL_END(system76); 419 return (error); 420 } 421 422 static void 423 acpi_system76_init(struct acpi_system76_softc *sc) 424 { 425 struct acpi_softc *acpi_sc; 426 struct acpi_ctrl *ctrl; 427 uint32_t method; 428 429 ACPI_SERIAL_ASSERT(system76); 430 431 acpi_system76_check(sc); 432 acpi_sc = acpi_device_get_parent_softc(sc->dev); 433 sysctl_ctx_init(&sc->sysctl_ctx); 434 sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, 435 SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "s76", 436 CTLFLAG_RD | CTLFLAG_MPSAFE, 0, "system76 control"); 437 438 for (method = 1; method < S76_CTRL_MAX; method++) { 439 if ((ctrl = acpi_system76_ctrl_map(sc, method)) == NULL) 440 continue; 441 442 if (!ctrl->exists) 443 continue; 444 445 if (method == S76_CTRL_KBB) { 446 sc->kbb_bkl = backlight_register("system76_keyboard", sc->dev); 447 if (sc->kbb_bkl == NULL) 448 device_printf(sc->dev, "Can not register backlight\n"); 449 else 450 sc->backlight_level = devstate_to_backlight(sc->kbb.val); 451 } 452 453 SYSCTL_ADD_PROC(&sc->sysctl_ctx, 454 SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, s76_sysctl_table[method].name, 455 CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_ANYBODY | CTLFLAG_MPSAFE, 456 sc, method, acpi_system76_sysctl_handler, "IU", s76_sysctl_table[method].desc); 457 } 458 } 459 460 static int 461 acpi_system76_backlight_update_status(device_t dev, struct backlight_props 462 *props) 463 { 464 struct acpi_system76_softc *sc; 465 466 sc = device_get_softc(dev); 467 if (sc->kbb.val != backlight_to_devstate(props->brightness)) { 468 sc->kbb.val = backlight_to_devstate(props->brightness); 469 acpi_system76_update(sc, S76_CTRL_KBB, true); 470 } 471 sc->backlight_level = props->brightness; 472 473 return (0); 474 } 475 476 static int 477 acpi_system76_backlight_get_status(device_t dev, struct backlight_props *props) 478 { 479 struct acpi_system76_softc *sc; 480 481 sc = device_get_softc(dev); 482 props->brightness = sc->backlight_level; 483 props->nlevels = nitems(acpi_system76_backlight_levels); 484 memcpy(props->levels, acpi_system76_backlight_levels, 485 sizeof(acpi_system76_backlight_levels)); 486 487 return (0); 488 } 489 490 static int 491 acpi_system76_backlight_get_info(device_t dev, struct backlight_info *info) 492 { 493 info->type = BACKLIGHT_TYPE_KEYBOARD; 494 strlcpy(info->name, "System76 Keyboard", BACKLIGHTMAXNAMELENGTH); 495 496 return (0); 497 } 498 499 static int 500 acpi_system76_attach(device_t dev) 501 { 502 struct acpi_system76_softc *sc; 503 504 ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); 505 506 sc = device_get_softc(dev); 507 sc->dev = dev; 508 sc->handle = acpi_get_handle(dev); 509 510 AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, 511 acpi_system76_notify_handler, dev); 512 513 ACPI_SERIAL_BEGIN(system76); 514 acpi_system76_init(sc); 515 ACPI_SERIAL_END(system76); 516 517 return (0); 518 } 519 520 static int 521 acpi_system76_detach(device_t dev) 522 { 523 struct acpi_system76_softc *sc; 524 525 sc = device_get_softc(dev); 526 if (sysctl_ctx_free(&sc->sysctl_ctx) != 0) 527 return (EBUSY); 528 529 AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, 530 acpi_system76_notify_handler); 531 532 if (sc->kbb_bkl != NULL) 533 backlight_destroy(sc->kbb_bkl); 534 535 return (0); 536 } 537 538 static int 539 acpi_system76_suspend(device_t dev) 540 { 541 struct acpi_system76_softc *sc; 542 struct acpi_ctrl *ctrl; 543 544 sc = device_get_softc(dev); 545 if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) { 546 ctrl->val = 0; 547 acpi_system76_update(sc, S76_CTRL_KBB, true); 548 } 549 550 return (0); 551 } 552 553 static int 554 acpi_system76_resume(device_t dev) 555 { 556 struct acpi_system76_softc *sc; 557 struct acpi_ctrl *ctrl; 558 559 sc = device_get_softc(dev); 560 if ((ctrl = acpi_system76_ctrl_map(sc, S76_CTRL_KBB)) != NULL) { 561 ctrl->val = backlight_to_devstate(sc->backlight_level); 562 acpi_system76_update(sc, S76_CTRL_KBB, true); 563 } 564 565 return (0); 566 } 567 568 static int 569 acpi_system76_shutdown(device_t dev) 570 { 571 return (acpi_system76_detach(dev)); 572 } 573 574 static int 575 acpi_system76_probe(device_t dev) 576 { 577 int rv; 578 579 if (acpi_disabled("system76") || device_get_unit(dev) > 1) 580 return (ENXIO); 581 rv = ACPI_ID_PROBE(device_get_parent(dev), dev, system76_ids, NULL); 582 if (rv > 0) { 583 return (rv); 584 } 585 586 return (BUS_PROBE_VENDOR); 587 } 588 589 DRIVER_MODULE(acpi_system76, acpi, acpi_system76_driver, 0, 0); 590 MODULE_VERSION(acpi_system76, 1); 591 MODULE_DEPEND(acpi_system76, acpi, 1, 1, 1); 592 MODULE_DEPEND(acpi_system76, backlight, 1, 1, 1); 593