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