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
devstate_to_backlight(uint32_t val)178 devstate_to_backlight(uint32_t val)
179 {
180 return (acpi_system76_backlight_levels[val >> 4 & 0xf]);
181 }
182
183 static inline uint32_t
backlight_to_devstate(uint32_t bkl)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 *
acpi_system76_ctrl_map(struct acpi_system76_softc * sc,int method)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
acpi_system76_update(struct acpi_system76_softc * sc,int method,bool set)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
acpi_system76_notify_update(void * arg)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
acpi_system76_check(struct acpi_system76_softc * sc)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
acpi_system76_notify_handler(ACPI_HANDLE handle,uint32_t notify,void * ctx)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
acpi_system76_sysctl_handler(SYSCTL_HANDLER_ARGS)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
acpi_system76_init(struct acpi_system76_softc * sc)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
acpi_system76_backlight_update_status(device_t dev,struct backlight_props * props)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
acpi_system76_backlight_get_status(device_t dev,struct backlight_props * props)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
acpi_system76_backlight_get_info(device_t dev,struct backlight_info * info)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
acpi_system76_attach(device_t dev)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
acpi_system76_detach(device_t dev)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
acpi_system76_suspend(device_t dev)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
acpi_system76_resume(device_t dev)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
acpi_system76_shutdown(device_t dev)569 acpi_system76_shutdown(device_t dev)
570 {
571 return (acpi_system76_detach(dev));
572 }
573
574 static int
acpi_system76_probe(device_t dev)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