1 | /* $NetBSD: sony_acpi.c,v 1.23 2016/07/07 06:55:41 msaitoh Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2005 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Christos Zoulas. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
20 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
21 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
22 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
23 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
24 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
26 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
27 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
28 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
29 | * POSSIBILITY OF SUCH DAMAGE. |
30 | */ |
31 | #include <sys/cdefs.h> |
32 | __KERNEL_RCSID(0, "$NetBSD: sony_acpi.c,v 1.23 2016/07/07 06:55:41 msaitoh Exp $" ); |
33 | |
34 | #include <sys/param.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 ("sony_acpi" ) |
43 | |
44 | #define SONY_NOTIFY_FnKeyEvent 0x92 |
45 | #define SONY_NOTIFY_BrightnessDownPressed 0x85 |
46 | #define SONY_NOTIFY_BrightnessDownReleased 0x05 |
47 | #define SONY_NOTIFY_BrightnessUpPressed 0x86 |
48 | #define SONY_NOTIFY_BrightnessUpReleased 0x06 |
49 | #define SONY_NOTIFY_DisplaySwitchPressed 0x87 |
50 | #define SONY_NOTIFY_DisplaySwitchReleased 0x07 |
51 | #define SONY_NOTIFY_ZoomPressed 0x8a |
52 | #define SONY_NOTIFY_ZoomReleased 0x0a |
53 | #define SONY_NOTIFY_SuspendPressed 0x8c |
54 | #define SONY_NOTIFY_SuspendReleased 0x0c |
55 | |
56 | struct sony_acpi_softc { |
57 | device_t sc_dev; |
58 | struct sysctllog *sc_log; |
59 | struct acpi_devnode *sc_node; |
60 | |
61 | #define SONY_PSW_SLEEP 0 |
62 | #define SONY_PSW_DISPLAY_CYCLE 1 |
63 | #define SONY_PSW_ZOOM 2 |
64 | #define SONY_PSW_LAST 3 |
65 | struct sysmon_pswitch sc_smpsw[SONY_PSW_LAST]; |
66 | int sc_smpsw_valid; |
67 | |
68 | #define SONY_ACPI_QUIRK_FNINIT 0x01 |
69 | int sc_quirks; |
70 | bool sc_has_pic; |
71 | |
72 | struct sony_acpi_pmstate { |
73 | ACPI_INTEGER brt; |
74 | } sc_pmstate; |
75 | }; |
76 | |
77 | static const char * const sony_acpi_ids[] = { |
78 | "SNY5001" , |
79 | NULL |
80 | }; |
81 | |
82 | static int sony_acpi_match(device_t, cfdata_t, void *); |
83 | static void sony_acpi_attach(device_t, device_t, void *); |
84 | static ACPI_STATUS sony_acpi_eval_set_integer(ACPI_HANDLE, const char *, |
85 | ACPI_INTEGER, ACPI_INTEGER *); |
86 | static void sony_acpi_quirk_setup(struct sony_acpi_softc *); |
87 | static void sony_acpi_notify_handler(ACPI_HANDLE, uint32_t, void *); |
88 | static bool sony_acpi_suspend(device_t, const pmf_qual_t *); |
89 | static bool sony_acpi_resume(device_t, const pmf_qual_t *); |
90 | static void sony_acpi_brightness_down(device_t); |
91 | static void sony_acpi_brightness_up(device_t); |
92 | static ACPI_STATUS sony_acpi_find_pic(ACPI_HANDLE, uint32_t, void *, void **); |
93 | |
94 | CFATTACH_DECL_NEW(sony_acpi, sizeof(struct sony_acpi_softc), |
95 | sony_acpi_match, sony_acpi_attach, NULL, NULL); |
96 | |
97 | static int |
98 | sony_acpi_match(device_t parent, cfdata_t match, void *aux) |
99 | { |
100 | struct acpi_attach_args *aa = aux; |
101 | |
102 | if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) |
103 | return 0; |
104 | |
105 | return acpi_match_hid(aa->aa_node->ad_devinfo, sony_acpi_ids); |
106 | } |
107 | |
108 | static int |
109 | sony_sysctl_helper(SYSCTLFN_ARGS) |
110 | { |
111 | struct sysctlnode node; |
112 | ACPI_INTEGER acpi_val; |
113 | ACPI_STATUS rv; |
114 | int val, old_val, error; |
115 | char buf[SYSCTL_NAMELEN + 1], *ptr; |
116 | struct sony_acpi_softc *sc = rnode->sysctl_data; |
117 | |
118 | (void)snprintf(buf, sizeof(buf), "G%s" , rnode->sysctl_name); |
119 | for (ptr = buf; *ptr; ptr++) |
120 | *ptr = toupper((unsigned char)*ptr); |
121 | |
122 | rv = acpi_eval_integer(sc->sc_node->ad_handle, buf, &acpi_val); |
123 | if (ACPI_FAILURE(rv)) { |
124 | #ifdef DIAGNOSTIC |
125 | printf("%s: couldn't get `%s'\n" , device_xname(sc->sc_dev), |
126 | buf); |
127 | #endif |
128 | return EIO; |
129 | } |
130 | val = old_val = acpi_val; |
131 | |
132 | node = *rnode; |
133 | node.sysctl_data = &val; |
134 | |
135 | error = sysctl_lookup(SYSCTLFN_CALL(&node)); |
136 | if (error || newp == NULL) |
137 | return error; |
138 | |
139 | buf[0] = 'S'; |
140 | acpi_val = val; |
141 | rv = sony_acpi_eval_set_integer(sc->sc_node->ad_handle, buf, |
142 | acpi_val, NULL); |
143 | if (ACPI_FAILURE(rv)) { |
144 | #ifdef DIAGNOSTIC |
145 | printf("%s: couldn't set `%s' to %d\n" , |
146 | device_xname(sc->sc_dev), buf, val); |
147 | #endif |
148 | return EIO; |
149 | } |
150 | return 0; |
151 | } |
152 | |
153 | static ACPI_STATUS |
154 | sony_walk_cb(ACPI_HANDLE hnd, uint32_t v, void *context, void **status) |
155 | { |
156 | struct sony_acpi_softc *sc = (void *)context; |
157 | const struct sysctlnode *node, *snode; |
158 | const char *name = acpi_name(hnd); |
159 | ACPI_INTEGER acpi_val; |
160 | char buf[SYSCTL_NAMELEN + 1], *ptr; |
161 | int rv; |
162 | |
163 | if ((name = strrchr(name, '.')) == NULL) |
164 | return AE_OK; |
165 | |
166 | name++; |
167 | if ((*name != 'G') && (*name != 'S')) |
168 | return AE_OK; |
169 | |
170 | (void)strlcpy(buf, name, sizeof(buf)); |
171 | *buf = 'G'; |
172 | |
173 | /* |
174 | * We assume that if the 'get' of the name as an integer is |
175 | * successful it is ok. |
176 | */ |
177 | if (acpi_eval_integer(sc->sc_node->ad_handle, buf, &acpi_val)) |
178 | return AE_OK; |
179 | |
180 | for (ptr = buf; *ptr; ptr++) |
181 | *ptr = tolower(*ptr); |
182 | |
183 | if ((rv = sysctl_createv(&sc->sc_log, 0, NULL, &snode, 0, |
184 | CTLTYPE_NODE, device_xname(sc->sc_dev), |
185 | SYSCTL_DESCR("sony controls" ), |
186 | NULL, 0, NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL)) != 0) |
187 | goto out; |
188 | |
189 | if ((rv = sysctl_createv(&sc->sc_log, 0, &snode, &node, |
190 | CTLFLAG_READWRITE, CTLTYPE_INT, buf + 1, NULL, |
191 | sony_sysctl_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL)) != 0) |
192 | goto out; |
193 | |
194 | out: |
195 | #ifdef DIAGNOSTIC |
196 | if (rv) |
197 | printf("%s: sysctl_createv failed (rv = %d)\n" , |
198 | device_xname(sc->sc_dev), rv); |
199 | #endif |
200 | return AE_OK; |
201 | } |
202 | |
203 | ACPI_STATUS |
204 | sony_acpi_eval_set_integer(ACPI_HANDLE handle, const char *path, |
205 | ACPI_INTEGER val, ACPI_INTEGER *valp) |
206 | { |
207 | ACPI_STATUS rv; |
208 | ACPI_BUFFER buf; |
209 | ACPI_OBJECT param, ret_val; |
210 | ACPI_OBJECT_LIST params; |
211 | |
212 | if (handle == NULL) |
213 | handle = ACPI_ROOT_OBJECT; |
214 | |
215 | params.Count = 1; |
216 | params.Pointer = ¶m; |
217 | |
218 | param.Type = ACPI_TYPE_INTEGER; |
219 | param.Integer.Value = val; |
220 | |
221 | buf.Pointer = &ret_val; |
222 | buf.Length = sizeof(ret_val); |
223 | |
224 | rv = AcpiEvaluateObjectTyped(handle, path, ¶ms, &buf, |
225 | ACPI_TYPE_INTEGER); |
226 | |
227 | if (ACPI_SUCCESS(rv) && valp) |
228 | *valp = ret_val.Integer.Value; |
229 | |
230 | return rv; |
231 | } |
232 | |
233 | static void |
234 | sony_acpi_attach(device_t parent, device_t self, void *aux) |
235 | { |
236 | struct sony_acpi_softc *sc = device_private(self); |
237 | struct acpi_attach_args *aa = aux; |
238 | ACPI_STATUS rv; |
239 | int i; |
240 | |
241 | aprint_naive(": Sony Miscellaneous Controller\n" ); |
242 | aprint_normal(": Sony Miscellaneous Controller\n" ); |
243 | |
244 | sc->sc_node = aa->aa_node; |
245 | sc->sc_dev = self; |
246 | |
247 | rv = AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 100, |
248 | sony_acpi_find_pic, NULL, sc, NULL); |
249 | if (ACPI_FAILURE(rv)) |
250 | aprint_error_dev(self, "couldn't walk namespace: %s\n" , |
251 | AcpiFormatException(rv)); |
252 | |
253 | /* |
254 | * If we don't find an SNY6001 device, assume that we need the |
255 | * Fn key initialization sequence. |
256 | */ |
257 | if (sc->sc_has_pic == false) |
258 | sc->sc_quirks |= SONY_ACPI_QUIRK_FNINIT; |
259 | |
260 | sony_acpi_quirk_setup(sc); |
261 | |
262 | /* Configure suspend button and hotkeys */ |
263 | sc->sc_smpsw[SONY_PSW_SLEEP].smpsw_name = device_xname(self); |
264 | sc->sc_smpsw[SONY_PSW_SLEEP].smpsw_type = PSWITCH_TYPE_SLEEP; |
265 | sc->sc_smpsw[SONY_PSW_DISPLAY_CYCLE].smpsw_name = |
266 | PSWITCH_HK_DISPLAY_CYCLE; |
267 | sc->sc_smpsw[SONY_PSW_DISPLAY_CYCLE].smpsw_type = PSWITCH_TYPE_HOTKEY; |
268 | sc->sc_smpsw[SONY_PSW_ZOOM].smpsw_name = PSWITCH_HK_ZOOM_BUTTON; |
269 | sc->sc_smpsw[SONY_PSW_ZOOM].smpsw_type = PSWITCH_TYPE_HOTKEY; |
270 | sc->sc_smpsw_valid = 1; |
271 | |
272 | for (i = 0; i < SONY_PSW_LAST; i++) |
273 | if (sysmon_pswitch_register(&sc->sc_smpsw[i]) != 0) { |
274 | aprint_error_dev(self, |
275 | "couldn't register %s with sysmon\n" , |
276 | sc->sc_smpsw[i].smpsw_name); |
277 | sc->sc_smpsw_valid = 0; |
278 | } |
279 | |
280 | (void)acpi_register_notify(sc->sc_node, sony_acpi_notify_handler); |
281 | |
282 | /* Install sysctl handler */ |
283 | rv = AcpiWalkNamespace(ACPI_TYPE_METHOD, |
284 | sc->sc_node->ad_handle, 1, sony_walk_cb, NULL, sc, NULL); |
285 | |
286 | #ifdef DIAGNOSTIC |
287 | if (ACPI_FAILURE(rv)) |
288 | aprint_error_dev(self, "Cannot walk ACPI namespace (%u)\n" , |
289 | rv); |
290 | #endif |
291 | |
292 | if (!pmf_device_register(self, sony_acpi_suspend, sony_acpi_resume)) |
293 | aprint_error_dev(self, "couldn't establish power handler\n" ); |
294 | |
295 | if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_UP, |
296 | sony_acpi_brightness_up, true)) |
297 | aprint_error_dev(self, |
298 | "couldn't register BRIGHTNESS UP handler\n" ); |
299 | |
300 | if (!pmf_event_register(self, PMFE_DISPLAY_BRIGHTNESS_DOWN, |
301 | sony_acpi_brightness_down, true)) |
302 | aprint_error_dev(self, |
303 | "couldn't register BRIGHTNESS DOWN handler\n" ); |
304 | } |
305 | |
306 | static void |
307 | sony_acpi_quirk_setup(struct sony_acpi_softc *sc) |
308 | { |
309 | ACPI_HANDLE hdl = sc->sc_node->ad_handle; |
310 | |
311 | if (sc->sc_quirks & SONY_ACPI_QUIRK_FNINIT) { |
312 | /* Initialize extra Fn keys */ |
313 | sony_acpi_eval_set_integer(hdl, "SN02" , 0x04, NULL); |
314 | sony_acpi_eval_set_integer(hdl, "SN07" , 0x02, NULL); |
315 | sony_acpi_eval_set_integer(hdl, "SN02" , 0x10, NULL); |
316 | sony_acpi_eval_set_integer(hdl, "SN07" , 0x00, NULL); |
317 | sony_acpi_eval_set_integer(hdl, "SN03" , 0x02, NULL); |
318 | sony_acpi_eval_set_integer(hdl, "SN07" , 0x101, NULL); |
319 | } |
320 | } |
321 | |
322 | static void |
323 | sony_acpi_notify_handler(ACPI_HANDLE hdl, uint32_t notify, void *opaque) |
324 | { |
325 | device_t dv = opaque; |
326 | struct sony_acpi_softc *sc = device_private(dv); |
327 | ACPI_STATUS rv; |
328 | ACPI_INTEGER arg; |
329 | |
330 | if (notify == SONY_NOTIFY_FnKeyEvent) { |
331 | rv = sony_acpi_eval_set_integer(hdl, "SN07" , 0x202, &arg); |
332 | if (ACPI_FAILURE(rv)) |
333 | return; |
334 | |
335 | notify = arg & 0xff; |
336 | } |
337 | |
338 | switch (notify) { |
339 | case SONY_NOTIFY_BrightnessDownPressed: |
340 | sony_acpi_brightness_down(dv); |
341 | break; |
342 | case SONY_NOTIFY_BrightnessUpPressed: |
343 | sony_acpi_brightness_up(dv); |
344 | break; |
345 | case SONY_NOTIFY_BrightnessDownReleased: |
346 | case SONY_NOTIFY_BrightnessUpReleased: |
347 | break; |
348 | case SONY_NOTIFY_SuspendPressed: |
349 | if (!sc->sc_smpsw_valid) |
350 | break; |
351 | sysmon_pswitch_event(&sc->sc_smpsw[SONY_PSW_SLEEP], |
352 | PSWITCH_EVENT_PRESSED); |
353 | break; |
354 | case SONY_NOTIFY_SuspendReleased: |
355 | break; |
356 | case SONY_NOTIFY_DisplaySwitchPressed: |
357 | if (!sc->sc_smpsw_valid) |
358 | break; |
359 | sysmon_pswitch_event(&sc->sc_smpsw[SONY_PSW_DISPLAY_CYCLE], |
360 | PSWITCH_EVENT_PRESSED); |
361 | break; |
362 | case SONY_NOTIFY_DisplaySwitchReleased: |
363 | break; |
364 | case SONY_NOTIFY_ZoomPressed: |
365 | if (!sc->sc_smpsw_valid) |
366 | break; |
367 | sysmon_pswitch_event(&sc->sc_smpsw[SONY_PSW_ZOOM], |
368 | PSWITCH_EVENT_PRESSED); |
369 | break; |
370 | case SONY_NOTIFY_ZoomReleased: |
371 | break; |
372 | default: |
373 | aprint_debug_dev(dv, "unknown notify event 0x%x\n" , notify); |
374 | break; |
375 | } |
376 | } |
377 | |
378 | static bool |
379 | sony_acpi_suspend(device_t dv, const pmf_qual_t *qual) |
380 | { |
381 | struct sony_acpi_softc *sc = device_private(dv); |
382 | |
383 | acpi_eval_integer(sc->sc_node->ad_handle, "GBRT" , &sc->sc_pmstate.brt); |
384 | |
385 | return true; |
386 | } |
387 | |
388 | static bool |
389 | sony_acpi_resume(device_t dv, const pmf_qual_t *qual) |
390 | { |
391 | struct sony_acpi_softc *sc = device_private(dv); |
392 | |
393 | sony_acpi_eval_set_integer(sc->sc_node->ad_handle, "SBRT" , |
394 | sc->sc_pmstate.brt, NULL); |
395 | sony_acpi_quirk_setup(sc); |
396 | |
397 | return true; |
398 | } |
399 | |
400 | static void |
401 | sony_acpi_brightness_up(device_t dv) |
402 | { |
403 | struct sony_acpi_softc *sc = device_private(dv); |
404 | ACPI_INTEGER arg; |
405 | ACPI_STATUS rv; |
406 | |
407 | rv = acpi_eval_integer(sc->sc_node->ad_handle, "GBRT" , &arg); |
408 | if (ACPI_FAILURE(rv)) |
409 | return; |
410 | if (arg >= 8) |
411 | arg = 8; |
412 | else |
413 | arg++; |
414 | sony_acpi_eval_set_integer(sc->sc_node->ad_handle, "SBRT" , arg, NULL); |
415 | } |
416 | |
417 | static void |
418 | sony_acpi_brightness_down(device_t dv) |
419 | { |
420 | struct sony_acpi_softc *sc = device_private(dv); |
421 | ACPI_INTEGER arg; |
422 | ACPI_STATUS rv; |
423 | |
424 | rv = acpi_eval_integer(sc->sc_node->ad_handle, "GBRT" , &arg); |
425 | if (ACPI_FAILURE(rv)) |
426 | return; |
427 | if (arg <= 0) |
428 | arg = 0; |
429 | else |
430 | arg--; |
431 | sony_acpi_eval_set_integer(sc->sc_node->ad_handle, "SBRT" , arg, NULL); |
432 | } |
433 | |
434 | static ACPI_STATUS |
435 | sony_acpi_find_pic(ACPI_HANDLE hdl, uint32_t level, |
436 | void *opaque, void **status) |
437 | { |
438 | struct sony_acpi_softc *sc = opaque; |
439 | ACPI_STATUS rv; |
440 | ACPI_DEVICE_INFO *devinfo; |
441 | |
442 | rv = AcpiGetObjectInfo(hdl, &devinfo); |
443 | if (ACPI_FAILURE(rv) || devinfo == NULL) |
444 | return AE_OK; /* we don't want to stop searching */ |
445 | |
446 | if ((devinfo->Valid & ACPI_VALID_HID) != 0 && |
447 | devinfo->HardwareId.String && |
448 | strncmp(devinfo->HardwareId.String, "SNY6001" , 7) == 0) |
449 | sc->sc_has_pic = true; |
450 | |
451 | ACPI_FREE(devinfo); |
452 | |
453 | return AE_OK; |
454 | } |
455 | |