xref: /openbsd/sys/dev/acpi/acpivout.c (revision 09467b48)
1 /*	$OpenBSD: acpivout.c,v 1.23 2020/05/19 14:26:59 kettenis Exp $	*/
2 /*
3  * Copyright (c) 2009 Paul Irofti <paul@irofti.net>
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 int	acpivout_get_brightness(struct acpivout_softc *);
68 int	acpivout_select_brightness(struct acpivout_softc *, int);
69 int	acpivout_find_brightness(struct acpivout_softc *, int);
70 void	acpivout_set_brightness(void *, int);
71 void	acpivout_get_bcl(struct acpivout_softc *);
72 
73 /* wconsole hook functions */
74 int	acpivout_get_param(struct wsdisplay_param *);
75 int	acpivout_set_param(struct wsdisplay_param *);
76 
77 struct cfattach acpivout_ca = {
78 	sizeof(struct acpivout_softc), acpivout_match, acpivout_attach
79 };
80 
81 struct cfdriver acpivout_cd = {
82 	NULL, "acpivout", DV_DULL
83 };
84 
85 int
86 acpivout_match(struct device *parent, void *match, void *aux)
87 {
88 	struct acpi_attach_args	*aaa = aux;
89 	struct cfdata		*cf = match;
90 
91 	if (aaa->aaa_name == NULL ||
92 	    strcmp(aaa->aaa_name, cf->cf_driver->cd_name) != 0 ||
93 	    aaa->aaa_table != NULL)
94 		return (0);
95 
96 	return (1);
97 }
98 
99 void
100 acpivout_attach(struct device *parent, struct device *self, void *aux)
101 {
102 	struct acpivout_softc	*sc = (struct acpivout_softc *)self;
103 	struct acpi_attach_args	*aaa = aux;
104 
105 	sc->sc_acpi = ((struct acpivideo_softc *)parent)->sc_acpi;
106 	sc->sc_devnode = aaa->aaa_node;
107 
108 	printf(": %s\n", sc->sc_devnode->name);
109 
110 	aml_register_notify(sc->sc_devnode, aaa->aaa_dev,
111 	    acpivout_notify, sc, ACPIDEV_NOPOLL);
112 
113 	if (!aml_searchname(sc->sc_devnode, "_BQC") ||
114 	    ws_get_param || ws_set_param)
115 		return;
116 
117 	acpivout_get_bcl(sc);
118 	if (sc->sc_bcl_len == 0)
119 		return;
120 
121 	sc->sc_brightness = acpivout_get_brightness(sc);
122 
123 	ws_get_param = acpivout_get_param;
124 	ws_set_param = acpivout_set_param;
125 }
126 
127 int
128 acpivout_notify(struct aml_node *node, int notify, void *arg)
129 {
130 	struct acpivout_softc *sc = arg;
131 
132 	switch (notify) {
133 	case NOTIFY_BRIGHTNESS_CYCLE:
134 		wsdisplay_brightness_cycle(NULL);
135 		break;
136 	case NOTIFY_BRIGHTNESS_UP:
137 		wsdisplay_brightness_step(NULL, 1);
138 		break;
139 	case NOTIFY_BRIGHTNESS_DOWN:
140 		wsdisplay_brightness_step(NULL, -1);
141 		break;
142 	case NOTIFY_BRIGHTNESS_ZERO:
143 		wsdisplay_brightness_zero(NULL);
144 		break;
145 	case NOTIFY_DISPLAY_OFF:
146 		/* TODO: D3 state change */
147 		break;
148 	default:
149 		printf("%s: unknown event 0x%02x\n", DEVNAME(sc), notify);
150 		break;
151 	}
152 
153 	return (0);
154 }
155 
156 
157 int
158 acpivout_get_brightness(struct acpivout_softc *sc)
159 {
160 	struct aml_value res;
161 	int level;
162 
163 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BQC", 0, NULL, &res);
164 	level = aml_val2int(&res);
165 	aml_freevalue(&res);
166 	DPRINTF(("%s: BQC = %d\n", DEVNAME(sc), level));
167 
168 	if (level < sc->sc_bcl[0])
169 		level = sc->sc_bcl[0];
170 	else if (level > sc->sc_bcl[sc->sc_bcl_len - 1])
171 		level = sc->sc_bcl[sc->sc_bcl_len - 1];
172 
173 	return (level);
174 }
175 
176 int
177 acpivout_select_brightness(struct acpivout_softc *sc, int nlevel)
178 {
179 	int nindex, level;
180 
181 	level = sc->sc_brightness;
182 	nindex = acpivout_find_brightness(sc, nlevel);
183 	if (sc->sc_bcl[nindex] == level) {
184 		if (nlevel > level && (nindex + 1 < sc->sc_bcl_len))
185 			nindex++;
186 		else if (nlevel < level && (nindex - 1 >= 0))
187 			nindex--;
188 	}
189 
190 	return nindex;
191 }
192 
193 int
194 acpivout_find_brightness(struct acpivout_softc *sc, int level)
195 {
196 	int i, mid;
197 
198 	for (i = 0; i < sc->sc_bcl_len - 1; i++) {
199 		mid = sc->sc_bcl[i] + (sc->sc_bcl[i + 1] - sc->sc_bcl[i]) / 2;
200 		if (sc->sc_bcl[i] <= level && level <=  mid)
201 			return i;
202 		if  (mid < level && level <= sc->sc_bcl[i + 1])
203 			return i + 1;
204 	}
205 	if (level < sc->sc_bcl[0])
206 		return 0;
207 	else
208 		return i;
209 }
210 
211 void
212 acpivout_set_brightness(void *arg0, int arg1)
213 {
214 	struct acpivout_softc *sc = arg0;
215 	struct aml_value args, res;
216 
217 	memset(&args, 0, sizeof(args));
218 	args.v_integer = sc->sc_brightness;
219 	args.type = AML_OBJTYPE_INTEGER;
220 
221 	DPRINTF(("%s: BCM = %d\n", DEVNAME(sc), sc->sc_brightness));
222 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCM", 1, &args, &res);
223 
224 	aml_freevalue(&res);
225 }
226 
227 void
228 acpivout_get_bcl(struct acpivout_softc *sc)
229 {
230 	int	i, j, value;
231 	struct aml_value res;
232 
233 	DPRINTF(("Getting _BCL!"));
234 	aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCL", 0, NULL, &res);
235 	if (res.type != AML_OBJTYPE_PACKAGE) {
236 		sc->sc_bcl_len = 0;
237 		goto err;
238 	}
239 	/*
240 	 * Per the ACPI spec section B.6.2 the _BCL method returns a package.
241 	 * The first integer in the package is the brightness level
242 	 * when the computer has full power, and the second is the
243 	 * brightness level when the computer is on batteries.
244 	 * All other levels may be used by OSPM.
245 	 * So we skip the first two integers in the package.
246 	 */
247 	if (res.length <= 2) {
248 		sc->sc_bcl_len = 0;
249 		goto err;
250 	}
251 	sc->sc_bcl_len = res.length - 2;
252 
253 	sc->sc_bcl = mallocarray(sc->sc_bcl_len, sizeof(int), M_DEVBUF,
254 	    M_WAITOK | M_ZERO);
255 
256 	for (i = 0; i < sc->sc_bcl_len; i++) {
257 		/* Sort darkest to brightest */
258 		value = aml_val2int(res.v_package[i + 2]);
259 		for (j = i; j > 0 && sc->sc_bcl[j - 1] > value; j--)
260 			sc->sc_bcl[j] = sc->sc_bcl[j - 1];
261 		 sc->sc_bcl[j] = value;
262 	}
263 
264 err:
265 	aml_freevalue(&res);
266 }
267 
268 
269 int
270 acpivout_get_param(struct wsdisplay_param *dp)
271 {
272 	struct acpivout_softc	*sc = NULL;
273 	int i;
274 
275 	switch (dp->param) {
276 	case WSDISPLAYIO_PARAM_BRIGHTNESS:
277 		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
278 			if (acpivout_cd.cd_devs[i] == NULL)
279 				continue;
280 			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
281 			/* Ignore device if not connected. */
282 			if (sc->sc_bcl_len != 0)
283 				break;
284 		}
285 		if (sc != NULL && sc->sc_bcl_len != 0) {
286 			dp->min = 0;
287 			dp->max = sc->sc_bcl[sc->sc_bcl_len - 1];
288 			dp->curval = sc->sc_brightness;
289 			return 0;
290 		}
291 		return -1;
292 	default:
293 		return -1;
294 	}
295 }
296 
297 int
298 acpivout_set_param(struct wsdisplay_param *dp)
299 {
300 	struct acpivout_softc	*sc = NULL;
301 	int i, nindex;
302 
303 	switch (dp->param) {
304 	case WSDISPLAYIO_PARAM_BRIGHTNESS:
305 		for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
306 			if (acpivout_cd.cd_devs[i] == NULL)
307 				continue;
308 			sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
309 			/* Ignore device if not connected. */
310 			if (sc->sc_bcl_len != 0)
311 				break;
312 		}
313 		if (sc != NULL && sc->sc_bcl_len != 0) {
314 			nindex = acpivout_select_brightness(sc, dp->curval);
315 			sc->sc_brightness = sc->sc_bcl[nindex];
316 			acpi_addtask(sc->sc_acpi,
317 			    acpivout_set_brightness, sc, 0);
318 			acpi_wakeup(sc->sc_acpi);
319 			return 0;
320 		}
321 		return -1;
322 	default:
323 		return -1;
324 	}
325 }
326