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