xref: /freebsd/sys/dev/acpi_support/acpi_system76.c (revision bc531a96c9b28b1cabcd5deb0c9f8f6d815cfebc)
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