1 /* $NetBSD: asus_acpi.c,v 1.26 2015/04/23 23:23:00 pgoyette Exp $ */
2
3 /*-
4 * Copyright (c) 2007, 2008, 2009 Jared D. McNeill <jmcneill@invisible.ca>
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: asus_acpi.c,v 1.26 2015/04/23 23:23:00 pgoyette Exp $");
31
32 #include <sys/param.h>
33 #include <sys/device.h>
34 #include <sys/module.h>
35 #include <sys/sysctl.h>
36 #include <sys/systm.h>
37
38 #include <dev/acpi/acpireg.h>
39 #include <dev/acpi/acpivar.h>
40
41 #define _COMPONENT ACPI_RESOURCE_COMPONENT
42 ACPI_MODULE_NAME ("asus_acpi")
43
44 struct asus_softc {
45 device_t sc_dev;
46 struct acpi_devnode *sc_node;
47
48 #define ASUS_PSW_DISPLAY_CYCLE 0
49 #define ASUS_PSW_LAST 1
50 struct sysmon_pswitch sc_smpsw[ASUS_PSW_LAST];
51 bool sc_smpsw_valid;
52
53 struct sysmon_envsys *sc_sme;
54 #define ASUS_SENSOR_FAN 0
55 #define ASUS_SENSOR_LAST 1
56 envsys_data_t sc_sensor[ASUS_SENSOR_LAST];
57
58 int32_t sc_brightness;
59 ACPI_INTEGER sc_cfvnum;
60
61 struct sysctllog *sc_log;
62 int sc_cfv_mib;
63 int sc_cfvnum_mib;
64 };
65
66 #define ASUS_NOTIFY_WirelessSwitch 0x10
67 #define ASUS_NOTIFY_BrightnessLow 0x20
68 #define ASUS_NOTIFY_BrightnessHigh 0x2f
69 #define ASUS_NOTIFY_DisplayCycle 0x30
70 #define ASUS_NOTIFY_WindowSwitch 0x12 /* XXXJDM ?? */
71 #define ASUS_NOTIFY_VolumeMute 0x13
72 #define ASUS_NOTIFY_VolumeDown 0x14
73 #define ASUS_NOTIFY_VolumeUp 0x15
74
75 #define ASUS_METHOD_SDSP "SDSP"
76 #define ASUS_SDSP_LCD 0x01
77 #define ASUS_SDSP_CRT 0x02
78 #define ASUS_SDSP_TV 0x04
79 #define ASUS_SDSP_DVI 0x08
80 #define ASUS_SDSP_ALL \
81 (ASUS_SDSP_LCD | ASUS_SDSP_CRT | ASUS_SDSP_TV | ASUS_SDSP_DVI)
82 #define ASUS_METHOD_PBLG "PBLG"
83 #define ASUS_METHOD_PBLS "PBLS"
84 #define ASUS_METHOD_CFVS "CFVS"
85 #define ASUS_METHOD_CFVG "CFVG"
86
87 #define ASUS_EC_METHOD_FAN_RPMH "\\_SB.PCI0.SBRG.EC0.SC05"
88 #define ASUS_EC_METHOD_FAN_RPML "\\_SB.PCI0.SBRG.EC0.SC06"
89
90 static int asus_match(device_t, cfdata_t, void *);
91 static void asus_attach(device_t, device_t, void *);
92 static int asus_detach(device_t, int);
93
94 static void asus_notify_handler(ACPI_HANDLE, uint32_t, void *);
95
96 static void asus_init(device_t);
97 static bool asus_suspend(device_t, const pmf_qual_t *);
98 static bool asus_resume(device_t, const pmf_qual_t *);
99
100 static void asus_sysctl_setup(struct asus_softc *);
101
102 static void asus_sensors_refresh(struct sysmon_envsys *, envsys_data_t *);
103 static bool asus_get_fan_speed(struct asus_softc *, uint32_t *);
104
105 CFATTACH_DECL_NEW(asus, sizeof(struct asus_softc),
106 asus_match, asus_attach, asus_detach, NULL);
107
108 static const char * const asus_ids[] = {
109 "ASUS010",
110 NULL
111 };
112
113 static int
asus_match(device_t parent,cfdata_t match,void * opaque)114 asus_match(device_t parent, cfdata_t match, void *opaque)
115 {
116 struct acpi_attach_args *aa = opaque;
117
118 if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE)
119 return 0;
120
121 return acpi_match_hid(aa->aa_node->ad_devinfo, asus_ids);
122 }
123
124 static void
asus_attach(device_t parent,device_t self,void * opaque)125 asus_attach(device_t parent, device_t self, void *opaque)
126 {
127 struct asus_softc *sc = device_private(self);
128 struct acpi_attach_args *aa = opaque;
129
130 sc->sc_dev = self;
131 sc->sc_node = aa->aa_node;
132
133 aprint_naive("\n");
134 aprint_normal("\n");
135
136 asus_init(self);
137 asus_sysctl_setup(sc);
138
139 sc->sc_smpsw_valid = true;
140 sc->sc_smpsw[ASUS_PSW_DISPLAY_CYCLE].smpsw_name =
141 PSWITCH_HK_DISPLAY_CYCLE;
142 sc->sc_smpsw[ASUS_PSW_DISPLAY_CYCLE].smpsw_type =
143 PSWITCH_TYPE_HOTKEY;
144 if (sysmon_pswitch_register(&sc->sc_smpsw[ASUS_PSW_DISPLAY_CYCLE])) {
145 aprint_error_dev(self, "couldn't register with sysmon\n");
146 sc->sc_smpsw_valid = false;
147 }
148
149 if (asus_get_fan_speed(sc, NULL) == false)
150 goto out;
151
152 sc->sc_sme = sysmon_envsys_create();
153
154 strcpy(sc->sc_sensor[ASUS_SENSOR_FAN].desc, "fan");
155 sc->sc_sensor[ASUS_SENSOR_FAN].units = ENVSYS_SFANRPM;
156 sc->sc_sensor[ASUS_SENSOR_FAN].state = ENVSYS_SINVALID;
157 sysmon_envsys_sensor_attach(sc->sc_sme,
158 &sc->sc_sensor[ASUS_SENSOR_FAN]);
159
160 sc->sc_sme->sme_name = device_xname(self);
161 sc->sc_sme->sme_cookie = sc;
162 sc->sc_sme->sme_refresh = asus_sensors_refresh;
163 sc->sc_sme->sme_flags = SME_POLL_ONLY;
164
165 if (sysmon_envsys_register(sc->sc_sme)) {
166 aprint_error_dev(self, "couldn't register with envsys\n");
167 sysmon_envsys_destroy(sc->sc_sme);
168 sc->sc_sme = NULL;
169 }
170
171 out:
172 (void)pmf_device_register(self, asus_suspend, asus_resume);
173 (void)acpi_register_notify(sc->sc_node, asus_notify_handler);
174 }
175
176 static int
asus_detach(device_t self,int flags)177 asus_detach(device_t self, int flags)
178 {
179 struct asus_softc *sc = device_private(self);
180 int i;
181
182 acpi_deregister_notify(sc->sc_node);
183
184 if (sc->sc_smpsw_valid != false) {
185
186 for (i = 0; i < ASUS_PSW_LAST; i++)
187 sysmon_pswitch_unregister(&sc->sc_smpsw[i]);
188 }
189
190 if (sc->sc_sme != NULL)
191 sysmon_envsys_unregister(sc->sc_sme);
192
193 if (sc->sc_log != NULL)
194 sysctl_teardown(&sc->sc_log);
195
196 pmf_device_deregister(self);
197
198 return 0;
199 }
200
201 static void
asus_notify_handler(ACPI_HANDLE hdl,uint32_t notify,void * opaque)202 asus_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
203 {
204 struct asus_softc *sc;
205 device_t self = opaque;
206
207 sc = device_private(self);
208
209 if (notify >= ASUS_NOTIFY_BrightnessLow &&
210 notify <= ASUS_NOTIFY_BrightnessHigh) {
211 aprint_debug_dev(sc->sc_dev, "brightness %d percent\n",
212 (notify & 0xf) * 100 / 0xf);
213 return;
214 }
215
216 switch (notify) {
217 case ASUS_NOTIFY_WirelessSwitch: /* handled by AML */
218 case ASUS_NOTIFY_WindowSwitch: /* XXXJDM what is this? */
219 break;
220 case ASUS_NOTIFY_DisplayCycle:
221 if (sc->sc_smpsw_valid == false)
222 break;
223 sysmon_pswitch_event(&sc->sc_smpsw[ASUS_PSW_DISPLAY_CYCLE],
224 PSWITCH_EVENT_PRESSED);
225 break;
226 case ASUS_NOTIFY_VolumeMute:
227 pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_TOGGLE);
228 break;
229 case ASUS_NOTIFY_VolumeDown:
230 pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_DOWN);
231 break;
232 case ASUS_NOTIFY_VolumeUp:
233 pmf_event_inject(NULL, PMFE_AUDIO_VOLUME_UP);
234 break;
235 default:
236 aprint_debug_dev(sc->sc_dev, "unknown event 0x%02x\n", notify);
237 break;
238 }
239 }
240
241 static void
asus_init(device_t self)242 asus_init(device_t self)
243 {
244 struct asus_softc *sc = device_private(self);
245 ACPI_INTEGER cfv;
246 ACPI_STATUS rv;
247
248 /* Disable ASL display switching. */
249 rv = acpi_eval_set_integer(sc->sc_node->ad_handle, "INIT", 0x40);
250
251 if (ACPI_FAILURE(rv))
252 aprint_error_dev(self, "couldn't evaluate INIT: %s\n",
253 AcpiFormatException(rv));
254
255 rv = acpi_eval_integer(sc->sc_node->ad_handle, ASUS_METHOD_CFVG, &cfv);
256
257 if (ACPI_FAILURE(rv))
258 return;
259
260 sc->sc_cfvnum = (cfv >> 8) & 0xff;
261 }
262
263 static bool
asus_suspend(device_t self,const pmf_qual_t * qual)264 asus_suspend(device_t self, const pmf_qual_t *qual)
265 {
266 struct asus_softc *sc = device_private(self);
267 ACPI_INTEGER val = 0;
268 ACPI_STATUS rv;
269
270 /* Capture display brightness. */
271 rv = acpi_eval_integer(sc->sc_node->ad_handle, ASUS_METHOD_PBLG, &val);
272
273 if (ACPI_FAILURE(rv) || val > INT32_MAX)
274 sc->sc_brightness = -1;
275 else
276 sc->sc_brightness = val;
277
278 return true;
279 }
280
281 static bool
asus_resume(device_t self,const pmf_qual_t * qual)282 asus_resume(device_t self, const pmf_qual_t *qual)
283 {
284 struct asus_softc *sc = device_private(self);
285 ACPI_STATUS rv;
286
287 asus_init(self);
288
289 if (sc->sc_brightness < 0)
290 return true;
291
292 /* Restore previous display brightness. */
293 rv = acpi_eval_set_integer(sc->sc_node->ad_handle, ASUS_METHOD_PBLS,
294 sc->sc_brightness);
295
296 if (ACPI_FAILURE(rv))
297 aprint_error_dev(self, "couldn't evaluate PBLS: %s\n",
298 AcpiFormatException(rv));
299
300 return true;
301 }
302
303 static int
asus_sysctl_verify(SYSCTLFN_ARGS)304 asus_sysctl_verify(SYSCTLFN_ARGS)
305 {
306 struct sysctlnode node;
307 struct asus_softc *sc;
308 ACPI_INTEGER cfv;
309 ACPI_STATUS rv;
310 int err, tmp;
311
312 node = *rnode;
313 sc = rnode->sysctl_data;
314 if (node.sysctl_num == sc->sc_cfv_mib) {
315 rv = acpi_eval_integer(sc->sc_node->ad_handle,
316 ASUS_METHOD_CFVG, &cfv);
317 if (ACPI_FAILURE(rv))
318 return ENXIO;
319 tmp = cfv & 0xff;
320 node.sysctl_data = &tmp;
321 err = sysctl_lookup(SYSCTLFN_CALL(&node));
322 if (err || newp == NULL)
323 return err;
324
325 if (tmp < 0 || (uint64_t)tmp >= sc->sc_cfvnum)
326 return EINVAL;
327
328 rv = acpi_eval_set_integer(sc->sc_node->ad_handle,
329 ASUS_METHOD_CFVS, tmp);
330
331 if (ACPI_FAILURE(rv))
332 return ENXIO;
333 }
334
335 return 0;
336 }
337
338 static void
asus_sysctl_setup(struct asus_softc * sc)339 asus_sysctl_setup(struct asus_softc *sc)
340 {
341 const struct sysctlnode *node, *node_cfv, *node_ncfv;
342 int err, node_mib;
343
344 if (sc->sc_cfvnum == 0)
345 return;
346
347 err = sysctl_createv(&sc->sc_log, 0, NULL, &node, 0,
348 CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, NULL, 0,
349 NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);
350 if (err)
351 goto sysctl_err;
352 node_mib = node->sysctl_num;
353 err = sysctl_createv(&sc->sc_log, 0, NULL, &node_ncfv,
354 CTLFLAG_READONLY, CTLTYPE_QUAD, "ncfv",
355 SYSCTL_DESCR("Number of CPU frequency/voltage modes"),
356 NULL, 0, &sc->sc_cfvnum, 0,
357 CTL_HW, node_mib, CTL_CREATE, CTL_EOL);
358 if (err)
359 goto sysctl_err;
360 sc->sc_cfvnum_mib = node_ncfv->sysctl_num;
361 err = sysctl_createv(&sc->sc_log, 0, NULL, &node_cfv,
362 CTLFLAG_READWRITE, CTLTYPE_INT, "cfv",
363 SYSCTL_DESCR("Current CPU frequency/voltage mode"),
364 asus_sysctl_verify, 0, (void *)sc, 0,
365 CTL_HW, node_mib, CTL_CREATE, CTL_EOL);
366 if (err)
367 goto sysctl_err;
368 sc->sc_cfv_mib = node_cfv->sysctl_num;
369
370 return;
371 sysctl_err:
372 aprint_error_dev(sc->sc_dev, "failed to add sysctl nodes. (%d)\n", err);
373 }
374
375 static void
asus_sensors_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)376 asus_sensors_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
377 {
378 struct asus_softc *sc = sme->sme_cookie;
379 uint32_t rpm;
380
381 switch (edata->sensor) {
382 case ASUS_SENSOR_FAN:
383 if (asus_get_fan_speed(sc, &rpm)) {
384 edata->value_cur = rpm;
385 edata->state = ENVSYS_SVALID;
386 } else
387 edata->state = ENVSYS_SINVALID;
388 break;
389 }
390 }
391
392 static bool
asus_get_fan_speed(struct asus_softc * sc,uint32_t * speed)393 asus_get_fan_speed(struct asus_softc *sc, uint32_t *speed)
394 {
395 ACPI_INTEGER rpmh, rpml;
396 ACPI_STATUS rv;
397
398 rv = acpi_eval_integer(sc->sc_node->ad_handle,
399 ASUS_EC_METHOD_FAN_RPMH, &rpmh);
400 if (ACPI_FAILURE(rv))
401 return false;
402 rv = acpi_eval_integer(sc->sc_node->ad_handle,
403 ASUS_EC_METHOD_FAN_RPML, &rpml);
404 if (ACPI_FAILURE(rv))
405 return false;
406
407 if (speed)
408 *speed = (rpmh << 8) | rpml;
409 return true;
410 }
411
412 MODULE(MODULE_CLASS_DRIVER, asus, "sysmon_envsys,sysmon_power");
413
414 #ifdef _MODULE
415 #include "ioconf.c"
416 #endif
417
418 static int
asus_modcmd(modcmd_t cmd,void * aux)419 asus_modcmd(modcmd_t cmd, void *aux)
420 {
421 int rv = 0;
422
423 switch (cmd) {
424
425 case MODULE_CMD_INIT:
426
427 #ifdef _MODULE
428 rv = config_init_component(cfdriver_ioconf_asus,
429 cfattach_ioconf_asus, cfdata_ioconf_asus);
430 #endif
431 break;
432
433 case MODULE_CMD_FINI:
434
435 #ifdef _MODULE
436 rv = config_fini_component(cfdriver_ioconf_asus,
437 cfattach_ioconf_asus, cfdata_ioconf_asus);
438 #endif
439 break;
440
441 default:
442 rv = ENOTTY;
443 }
444
445 return rv;
446 }
447