xref: /openbsd-src/sys/dev/acpi/acpivout.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /*	$OpenBSD: acpivout.c,v 1.19 2020/02/08 19:08:17 patrick Exp $	*/
2 /*
3  * Copyright (c) 2009 Paul Irofti <pirofti@openbsd.org>
4  *
5  * Permission to use, copy, modify, and/or distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/device.h>
21 #include <sys/malloc.h>
22 
23 #include <machine/bus.h>
24 
25 #include <dev/acpi/acpivar.h>
26 #include <dev/acpi/acpidev.h>
27 #include <dev/acpi/amltypes.h>
28 #include <dev/acpi/dsdt.h>
29 
30 #include <dev/wscons/wsconsio.h>
31 #include <dev/wscons/wsdisplayvar.h>
32 
33 int	acpivout_match(struct device *, void *, void *);
34 void	acpivout_attach(struct device *, struct device *, void *);
35 int	acpivout_notify(struct aml_node *, int, void *);
36 
37 #ifdef ACPIVIDEO_DEBUG
38 #define DPRINTF(x)	printf x
39 #else
40 #define DPRINTF(x)
41 #endif
42 
43 /* Notifications for Output Devices */
44 #define NOTIFY_BRIGHTNESS_CYCLE		0x85
45 #define NOTIFY_BRIGHTNESS_UP		0x86
46 #define NOTIFY_BRIGHTNESS_DOWN		0x87
47 #define NOTIFY_BRIGHTNESS_ZERO		0x88
48 #define NOTIFY_DISPLAY_OFF		0x89
49 
50 #define BRIGHTNESS_STEP			5
51 
52 struct acpivout_softc {
53 	struct device		sc_dev;
54 
55 	bus_space_tag_t		sc_iot;
56 	bus_space_handle_t	sc_ioh;
57 
58 	struct acpi_softc	*sc_acpi;
59 	struct aml_node		*sc_devnode;
60 
61 	int	*sc_bcl;
62 	size_t	sc_bcl_len;
63 
64 	int	sc_brightness;
65 };
66 
67 void	acpivout_brightness_cycle(struct acpivout_softc *);
68 void	acpivout_brightness_step(struct acpivout_softc *, int);
69 void	acpivout_brightness_zero(struct acpivout_softc *);
70 int	acpivout_get_brightness(struct acpivout_softc *);
71 int	acpivout_select_brightness(struct acpivout_softc *, int);
72 int	acpivout_find_brightness(struct acpivout_softc *, int);
73 void	acpivout_set_brightness(void *, int);
74 void	acpivout_get_bcl(struct acpivout_softc *);
75 
76 /* wconsole hook functions */
77 int	acpivout_get_param(struct wsdisplay_param *);
78 int	acpivout_set_param(struct wsdisplay_param *);
79 
80 struct cfattach acpivout_ca = {
81 	sizeof(struct acpivout_softc), acpivout_match, acpivout_attach
82 };
83 
84 struct cfdriver acpivout_cd = {
85 	NULL, "acpivout", DV_DULL
86 };
87 
88 int
89 acpivout_match(struct device *parent, void *match, void *aux)
90 {
91 	struct acpi_attach_args	*aaa = aux;
92 	struct cfdata		*cf = match;
93 
94 	if (aaa->aaa_name == NULL ||
95 	    strcmp(aaa->aaa_name, cf->cf_driver->cd_name) != 0 ||
96 	    aaa->aaa_table != NULL)
97 		return (0);
98 
99 	return (1);
100 }
101 
102 void
103 acpivout_attach(struct device *parent, struct device *self, void *aux)
104 {
105 	struct acpivout_softc	*sc = (struct acpivout_softc *)self;
106 	struct acpi_attach_args	*aaa = aux;
107 
108 	sc->sc_acpi = ((struct acpivideo_softc *)parent)->sc_acpi;
109 	sc->sc_devnode = aaa->aaa_node;
110 
111 	printf(": %s\n", sc->sc_devnode->name);
112 
113 	aml_register_notify(sc->sc_devnode, aaa->aaa_dev,
114 	    acpivout_notify, sc, ACPIDEV_NOPOLL);
115 
116 	if (!aml_searchname(sc->sc_devnode, "_BQC") ||
117 	    ws_get_param || ws_set_param)
118 		return;
119 
120 	ws_get_param = acpivout_get_param;
121 	ws_set_param = acpivout_set_param;
122 
123 	acpivout_get_bcl(sc);
124 
125 	sc->sc_brightness = acpivout_get_brightness(sc);
126 }
127 
128 int
129 acpivout_notify(struct aml_node *node, int notify, void *arg)
130 {
131 	struct acpivout_softc *sc = arg;
132 
133 	if (ws_get_param == NULL || ws_set_param == NULL)
134 		return (0);
135 
136 	switch (notify) {
137 	case NOTIFY_BRIGHTNESS_CYCLE:
138 		acpivout_brightness_cycle(sc);
139 		break;
140 	case NOTIFY_BRIGHTNESS_UP:
141 		acpivout_brightness_step(sc, 1);
142 		break;
143 	case NOTIFY_BRIGHTNESS_DOWN:
144 		acpivout_brightness_step(sc, -1);
145 		break;
146 	case NOTIFY_BRIGHTNESS_ZERO:
147 		acpivout_brightness_zero(sc);
148 		break;
149 	case NOTIFY_DISPLAY_OFF:
150 		/* TODO: D3 state change */
151 		break;
152 	default:
153 		printf("%s: unknown event 0x%02x\n", DEVNAME(sc), notify);
154 		break;
155 	}
156 
157 	return (0);
158 }
159 
160 void
161 acpivout_brightness_cycle(struct acpivout_softc *sc)
162 {
163 	struct wsdisplay_param dp;
164 
165 	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
166 	if (ws_get_param(&dp))
167 		return;
168 
169 	if (dp.curval == dp.max)
170 		acpivout_brightness_zero(sc);
171 	else
172 		acpivout_brightness_step(sc, 1);
173 }
174 
175 void
176 acpivout_brightness_step(struct acpivout_softc *sc, int dir)
177 {
178 	struct wsdisplay_param dp;
179 	int delta, new;
180 
181 	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
182 	if (ws_get_param(&dp))
183 		return;
184 
185 	new = dp.curval;
186 	delta = ((dp.max - dp.min) * BRIGHTNESS_STEP) / 100;
187 	if (dir > 0) {
188 		if (delta > dp.max - dp.curval)
189 			new = dp.max;
190 		else
191 			new += delta;
192 	} else if (dir < 0) {
193 		if (delta > dp.curval - dp.min)
194 			new = dp.min;
195 		else
196 			new -= delta;
197 	}
198 
199 	if (dp.curval == new)
200 		return;
201 
202 	dp.curval = new;
203 	ws_set_param(&dp);
204 }
205 
206 void
207 acpivout_brightness_zero(struct acpivout_softc *sc)
208 {
209 	struct wsdisplay_param dp;
210 
211 	dp.param = WSDISPLAYIO_PARAM_BRIGHTNESS;
212 	if (ws_get_param(&dp))
213 		return;
214 
215 	dp.curval = dp.min;
216 	ws_set_param(&dp);
217 }
218 
219 int
220 acpivout_get_brightness(struct acpivout_softc *sc)
221 {
222 	struct aml_value res;
223 	int level;
224 
225 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BQC", 0, NULL, &res);
226 	level = aml_val2int(&res);
227 	aml_freevalue(&res);
228 	DPRINTF(("%s: BQC = %d\n", DEVNAME(sc), level));
229 
230 	if (level < sc->sc_bcl[0] || level > sc->sc_bcl[sc->sc_bcl_len -1])
231 		level = -1;
232 
233 	return (level);
234 }
235 
236 int
237 acpivout_select_brightness(struct acpivout_softc *sc, int nlevel)
238 {
239 	int nindex, level;
240 
241 	level = sc->sc_brightness;
242 	if (level == -1)
243 		return level;
244 
245 	nindex = acpivout_find_brightness(sc, nlevel);
246 	if (sc->sc_bcl[nindex] == level) {
247 		if (nlevel > level && (nindex + 1 < sc->sc_bcl_len))
248 			nindex++;
249 		else if (nlevel < level && (nindex - 1 >= 0))
250 			nindex--;
251 	}
252 
253 	return nindex;
254 }
255 
256 int
257 acpivout_find_brightness(struct acpivout_softc *sc, int level)
258 {
259 	int i, mid;
260 
261 	for (i = 0; i < sc->sc_bcl_len - 1; i++) {
262 		mid = sc->sc_bcl[i] + (sc->sc_bcl[i + 1] - sc->sc_bcl[i]) / 2;
263 		if (sc->sc_bcl[i] <= level && level <=  mid)
264 			return i;
265 		if  (mid < level && level <= sc->sc_bcl[i + 1])
266 			return i + 1;
267 	}
268 	if (level < sc->sc_bcl[0])
269 		return 0;
270 	else
271 		return i;
272 }
273 
274 void
275 acpivout_set_brightness(void *arg0, int arg1)
276 {
277 	struct acpivout_softc *sc = arg0;
278 	struct aml_value args, res;
279 
280 	memset(&args, 0, sizeof(args));
281 	args.v_integer = sc->sc_brightness;
282 	args.type = AML_OBJTYPE_INTEGER;
283 
284 	DPRINTF(("%s: BCM = %d\n", DEVNAME(sc), sc->sc_brightness));
285 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCM", 1, &args, &res);
286 
287 	aml_freevalue(&res);
288 }
289 
290 void
291 acpivout_get_bcl(struct acpivout_softc *sc)
292 {
293 	int	i, j, value;
294 	struct aml_value res;
295 
296 	DPRINTF(("Getting _BCL!"));
297 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCL", 0, NULL, &res);
298 	if (res.type != AML_OBJTYPE_PACKAGE) {
299 		sc->sc_bcl_len = 0;
300 		goto err;
301 	}
302 	/*
303 	 * Per the ACPI spec section B.6.2 the _BCL method returns a package.
304 	 * The first integer in the package is the brightness level
305 	 * when the computer has full power, and the second is the
306 	 * brightness level when the computer is on batteries.
307 	 * All other levels may be used by OSPM.
308 	 * So we skip the first two integers in the package.
309 	 */
310 	if (res.length <= 2) {
311 		sc->sc_bcl_len = 0;
312 		goto err;
313 	}
314 	sc->sc_bcl_len = res.length - 2;
315 
316 	sc->sc_bcl = mallocarray(sc->sc_bcl_len, sizeof(int), M_DEVBUF,
317 	    M_WAITOK | M_ZERO);
318 
319 	for (i = 0; i < sc->sc_bcl_len; i++) {
320 		/* Sort darkest to brightest */
321 		value = aml_val2int(res.v_package[i + 2]);
322 		for (j = i; j > 0 && sc->sc_bcl[j - 1] > value; j--)
323 			sc->sc_bcl[j] = sc->sc_bcl[j - 1];
324 		 sc->sc_bcl[j] = value;
325 	}
326 
327 err:
328 	aml_freevalue(&res);
329 }
330 
331 
332 int
333 acpivout_get_param(struct wsdisplay_param *dp)
334 {
335 	struct acpivout_softc	*sc = NULL;
336 	int i;
337 
338 	switch (dp->param) {
339 	case WSDISPLAYIO_PARAM_BRIGHTNESS:
340 		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
341 			if (acpivout_cd.cd_devs[i] == NULL)
342 				continue;
343 			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
344 			/* Ignore device if not connected. */
345 			if (sc->sc_bcl_len != 0)
346 				break;
347 		}
348 		if (sc != NULL && sc->sc_bcl_len != 0) {
349 			dp->min = 0;
350 			dp->max = sc->sc_bcl[sc->sc_bcl_len - 1];
351 			dp->curval = sc->sc_brightness;
352 			return 0;
353 		}
354 		return -1;
355 	default:
356 		return -1;
357 	}
358 }
359 
360 int
361 acpivout_set_param(struct wsdisplay_param *dp)
362 {
363 	struct acpivout_softc	*sc = NULL;
364 	int i, nindex;
365 
366 	switch (dp->param) {
367 	case WSDISPLAYIO_PARAM_BRIGHTNESS:
368 		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
369 			if (acpivout_cd.cd_devs[i] == NULL)
370 				continue;
371 			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
372 			/* Ignore device if not connected. */
373 			if (sc->sc_bcl_len != 0)
374 				break;
375 		}
376 		if (sc != NULL && sc->sc_bcl_len != 0) {
377 			nindex = acpivout_select_brightness(sc, dp->curval);
378 			if (nindex != -1) {
379 				sc->sc_brightness = sc->sc_bcl[nindex];
380 				acpi_addtask(sc->sc_acpi,
381 				    acpivout_set_brightness, sc, 0);
382 				acpi_wakeup(sc->sc_acpi);
383 				return 0;
384 			}
385 		}
386 		return -1;
387 	default:
388 		return -1;
389 	}
390 }
391