1 /* $OpenBSD: acpivout.c,v 1.26 2024/04/13 23:44:11 jsg 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 const 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
acpivout_match(struct device * parent,void * match,void * aux)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
acpivout_attach(struct device * parent,struct device * self,void * aux)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 acpi_max_osi >= OSI_WIN_8)
116 return;
117
118 acpivout_get_bcl(sc);
119 if (sc->sc_bcl_len == 0)
120 return;
121
122 sc->sc_brightness = acpivout_get_brightness(sc);
123
124 ws_get_param = acpivout_get_param;
125 ws_set_param = acpivout_set_param;
126 }
127
128 int
acpivout_notify(struct aml_node * node,int notify,void * arg)129 acpivout_notify(struct aml_node *node, int notify, void *arg)
130 {
131 struct acpivout_softc *sc = arg;
132
133 switch (notify) {
134 case NOTIFY_BRIGHTNESS_CYCLE:
135 wsdisplay_brightness_cycle(NULL);
136 break;
137 case NOTIFY_BRIGHTNESS_UP:
138 wsdisplay_brightness_step(NULL, 1);
139 break;
140 case NOTIFY_BRIGHTNESS_DOWN:
141 wsdisplay_brightness_step(NULL, -1);
142 break;
143 case NOTIFY_BRIGHTNESS_ZERO:
144 wsdisplay_brightness_zero(NULL);
145 break;
146 case NOTIFY_DISPLAY_OFF:
147 /* TODO: D3 state change */
148 break;
149 default:
150 printf("%s: unknown event 0x%02x\n", DEVNAME(sc), notify);
151 break;
152 }
153
154 return (0);
155 }
156
157
158 int
acpivout_get_brightness(struct acpivout_softc * sc)159 acpivout_get_brightness(struct acpivout_softc *sc)
160 {
161 struct aml_value res;
162 int level;
163
164 aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BQC", 0, NULL, &res);
165 level = aml_val2int(&res);
166 aml_freevalue(&res);
167 DPRINTF(("%s: BQC = %d\n", DEVNAME(sc), level));
168
169 if (level < sc->sc_bcl[0])
170 level = sc->sc_bcl[0];
171 else if (level > sc->sc_bcl[sc->sc_bcl_len - 1])
172 level = sc->sc_bcl[sc->sc_bcl_len - 1];
173
174 return (level);
175 }
176
177 int
acpivout_select_brightness(struct acpivout_softc * sc,int nlevel)178 acpivout_select_brightness(struct acpivout_softc *sc, int nlevel)
179 {
180 int nindex, level;
181
182 level = sc->sc_brightness;
183 nindex = acpivout_find_brightness(sc, nlevel);
184 if (sc->sc_bcl[nindex] == level) {
185 if (nlevel > level && (nindex + 1 < sc->sc_bcl_len))
186 nindex++;
187 else if (nlevel < level && (nindex - 1 >= 0))
188 nindex--;
189 }
190
191 return nindex;
192 }
193
194 int
acpivout_find_brightness(struct acpivout_softc * sc,int level)195 acpivout_find_brightness(struct acpivout_softc *sc, int level)
196 {
197 int i, mid;
198
199 for (i = 0; i < sc->sc_bcl_len - 1; i++) {
200 mid = sc->sc_bcl[i] + (sc->sc_bcl[i + 1] - sc->sc_bcl[i]) / 2;
201 if (sc->sc_bcl[i] <= level && level <= mid)
202 return i;
203 if (mid < level && level <= sc->sc_bcl[i + 1])
204 return i + 1;
205 }
206 if (level < sc->sc_bcl[0])
207 return 0;
208 else
209 return i;
210 }
211
212 void
acpivout_set_brightness(void * arg0,int arg1)213 acpivout_set_brightness(void *arg0, int arg1)
214 {
215 struct acpivout_softc *sc = arg0;
216 struct aml_value args, res;
217
218 memset(&args, 0, sizeof(args));
219 args.v_integer = sc->sc_brightness;
220 args.type = AML_OBJTYPE_INTEGER;
221
222 DPRINTF(("%s: BCM = %d\n", DEVNAME(sc), sc->sc_brightness));
223 aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCM", 1, &args, &res);
224
225 aml_freevalue(&res);
226 }
227
228 void
acpivout_get_bcl(struct acpivout_softc * sc)229 acpivout_get_bcl(struct acpivout_softc *sc)
230 {
231 int i, j, value;
232 struct aml_value res;
233
234 DPRINTF(("Getting _BCL!"));
235 aml_evalname(sc->sc_acpi, sc->sc_devnode, "_BCL", 0, NULL, &res);
236 if (res.type != AML_OBJTYPE_PACKAGE) {
237 sc->sc_bcl_len = 0;
238 goto err;
239 }
240 /*
241 * Per the ACPI spec section B.6.2 the _BCL method returns a package.
242 * The first integer in the package is the brightness level
243 * when the computer has full power, and the second is the
244 * brightness level when the computer is on batteries.
245 * All other levels may be used by OSPM.
246 * So we skip the first two integers in the package.
247 */
248 if (res.length <= 2) {
249 sc->sc_bcl_len = 0;
250 goto err;
251 }
252 sc->sc_bcl_len = res.length - 2;
253
254 sc->sc_bcl = mallocarray(sc->sc_bcl_len, sizeof(int), M_DEVBUF,
255 M_WAITOK | M_ZERO);
256
257 for (i = 0; i < sc->sc_bcl_len; i++) {
258 /* Sort darkest to brightest */
259 value = aml_val2int(res.v_package[i + 2]);
260 for (j = i; j > 0 && sc->sc_bcl[j - 1] > value; j--)
261 sc->sc_bcl[j] = sc->sc_bcl[j - 1];
262 sc->sc_bcl[j] = value;
263 }
264
265 err:
266 aml_freevalue(&res);
267 }
268
269
270 int
acpivout_get_param(struct wsdisplay_param * dp)271 acpivout_get_param(struct wsdisplay_param *dp)
272 {
273 struct acpivout_softc *sc = NULL;
274 int i;
275
276 switch (dp->param) {
277 case WSDISPLAYIO_PARAM_BRIGHTNESS:
278 for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
279 if (acpivout_cd.cd_devs[i] == NULL)
280 continue;
281 sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
282 /* Ignore device if not connected. */
283 if (sc->sc_bcl_len != 0)
284 break;
285 }
286 if (sc != NULL && sc->sc_bcl_len != 0) {
287 dp->min = 0;
288 dp->max = sc->sc_bcl[sc->sc_bcl_len - 1];
289 dp->curval = sc->sc_brightness;
290 return 0;
291 }
292 return -1;
293 default:
294 return -1;
295 }
296 }
297
298 int
acpivout_set_param(struct wsdisplay_param * dp)299 acpivout_set_param(struct wsdisplay_param *dp)
300 {
301 struct acpivout_softc *sc = NULL;
302 int i, nindex;
303
304 switch (dp->param) {
305 case WSDISPLAYIO_PARAM_BRIGHTNESS:
306 for (i = 0; i < acpivout_cd.cd_ndevs; i++) {
307 if (acpivout_cd.cd_devs[i] == NULL)
308 continue;
309 sc = (struct acpivout_softc *)acpivout_cd.cd_devs[i];
310 /* Ignore device if not connected. */
311 if (sc->sc_bcl_len != 0)
312 break;
313 }
314 if (sc != NULL && sc->sc_bcl_len != 0) {
315 nindex = acpivout_select_brightness(sc, dp->curval);
316 sc->sc_brightness = sc->sc_bcl[nindex];
317 acpi_addtask(sc->sc_acpi,
318 acpivout_set_brightness, sc, 0);
319 acpi_wakeup(sc->sc_acpi);
320 return 0;
321 }
322 return -1;
323 default:
324 return -1;
325 }
326 }
327