1 | /* $NetBSD: fujhk_acpi.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2010, 2011 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Gregoire Sutre. |
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 | |
32 | #include <sys/cdefs.h> |
33 | __KERNEL_RCSID(0, "$NetBSD: fujhk_acpi.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $" ); |
34 | |
35 | #include <sys/param.h> |
36 | #include <sys/device.h> |
37 | #include <sys/module.h> |
38 | #include <sys/mutex.h> |
39 | #include <sys/sysctl.h> |
40 | |
41 | #include <dev/sysmon/sysmonvar.h> |
42 | |
43 | #include <dev/acpi/acpireg.h> |
44 | #include <dev/acpi/acpivar.h> |
45 | |
46 | #define _COMPONENT ACPI_RESOURCE_COMPONENT |
47 | ACPI_MODULE_NAME ("fujhk_acpi" ) |
48 | |
49 | #define FUJITSU_HK_NOTIFY 0x80 |
50 | #define FUJITSU_HK_MODMASK 0xc0000000 |
51 | |
52 | /* Values returned by the GIRB method. */ |
53 | #define FUJITSU_HK_IRB_HOTKEY_RELEASE 0x0 |
54 | #define FUJITSU_HK_IRB_HOTKEY_PRESS(x) (0x410 | x) |
55 | |
56 | /* Hotkey index of a value returned by the GIRB method. */ |
57 | #define FUJITSU_HK_IRB_HOTKEY_INDEX(x) (x & 0x3) |
58 | |
59 | /* Values for the first argument of the FUNC method. */ |
60 | #define FUJITSU_FUNC_TARGET_BACKLIGHT 0x1004 |
61 | |
62 | /* Values for the second argument of the FUNC method. */ |
63 | #define FUJITSU_FUNC_COMMAND_SET 0x1 |
64 | #define FUJITSU_FUNC_COMMAND_GET 0x2 |
65 | |
66 | /* Backlight values for the FUNC method. */ |
67 | #define FUJITSU_FUNC_BACKLIGHT_ON 0x0 |
68 | #define FUJITSU_FUNC_BACKLIGHT_REDUCED 0x2 |
69 | #define FUJITSU_FUNC_BACKLIGHT_OFF 0x3 |
70 | |
71 | /* Value returned by FUNC on invalid arguments. */ |
72 | #define FUJITSU_FUNC_INVALID_ARGS 0x80000000 |
73 | |
74 | /* ACPI Fujitsu hotkeys controller capabilities (methods). */ |
75 | #define FUJITSU_HK_CAP_GIRB __BIT(0) |
76 | #define FUJITSU_HK_CAP_FUNC __BIT(1) |
77 | |
78 | struct fujitsu_hk_softc { |
79 | device_t sc_dev; |
80 | struct acpi_devnode *sc_node; |
81 | struct sysctllog *sc_log; |
82 | kmutex_t sc_mtx; |
83 | uint16_t sc_caps; |
84 | #define FUJITSU_HK_PSW_COUNT 4 |
85 | struct sysmon_pswitch sc_smpsw[FUJITSU_HK_PSW_COUNT]; |
86 | char sc_smpsw_name[FUJITSU_HK_PSW_COUNT][16]; |
87 | }; |
88 | |
89 | static const char * const fujitsu_hk_hid[] = { |
90 | "FUJ02E3" , |
91 | NULL |
92 | }; |
93 | |
94 | static int fujitsu_hk_match(device_t, cfdata_t, void *); |
95 | static void fujitsu_hk_attach(device_t, device_t, void *); |
96 | static int fujitsu_hk_detach(device_t, int); |
97 | static bool fujitsu_hk_suspend(device_t, const pmf_qual_t *); |
98 | static bool fujitsu_hk_resume(device_t, const pmf_qual_t *); |
99 | static uint16_t fujitsu_hk_capabilities(const struct acpi_devnode *); |
100 | static void fujitsu_hk_notify_handler(ACPI_HANDLE, uint32_t, void *); |
101 | static void fujitsu_hk_event_callback(void *); |
102 | static void fujitsu_hk_sysctl_setup(struct fujitsu_hk_softc *); |
103 | static int fujitsu_hk_sysctl_backlight(SYSCTLFN_PROTO); |
104 | static int fujitsu_hk_get_irb(struct fujitsu_hk_softc *, uint32_t *); |
105 | static int fujitsu_hk_get_backlight(struct fujitsu_hk_softc *, bool *); |
106 | static int fujitsu_hk_set_backlight(struct fujitsu_hk_softc *, bool); |
107 | static bool fujitsu_hk_cap(ACPI_HANDLE, const char *, ACPI_OBJECT_TYPE); |
108 | static ACPI_STATUS fujitsu_hk_eval_nary_integer(ACPI_HANDLE, |
109 | const char *, const |
110 | ACPI_INTEGER *, uint8_t, ACPI_INTEGER *); |
111 | |
112 | CFATTACH_DECL_NEW(fujhk, sizeof(struct fujitsu_hk_softc), |
113 | fujitsu_hk_match, fujitsu_hk_attach, fujitsu_hk_detach, NULL); |
114 | |
115 | static int |
116 | fujitsu_hk_match(device_t parent, cfdata_t match, void *aux) |
117 | { |
118 | struct acpi_attach_args *aa = aux; |
119 | |
120 | if (aa->aa_node->ad_type != ACPI_TYPE_DEVICE) |
121 | return 0; |
122 | |
123 | return acpi_match_hid(aa->aa_node->ad_devinfo, fujitsu_hk_hid); |
124 | } |
125 | |
126 | static void |
127 | fujitsu_hk_attach(device_t parent, device_t self, void *aux) |
128 | { |
129 | struct fujitsu_hk_softc *sc = device_private(self); |
130 | struct acpi_attach_args *aa = aux; |
131 | struct acpi_devnode *ad = aa->aa_node; |
132 | int i; |
133 | |
134 | aprint_naive(": Fujitsu Hotkeys\n" ); |
135 | aprint_normal(": Fujitsu Hotkeys\n" ); |
136 | |
137 | sc->sc_dev = self; |
138 | sc->sc_node = ad; |
139 | sc->sc_log = NULL; |
140 | sc->sc_caps = fujitsu_hk_capabilities(ad); |
141 | |
142 | mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_NONE); |
143 | |
144 | for (i = 0; i < FUJITSU_HK_PSW_COUNT; i++) { |
145 | (void)snprintf(sc->sc_smpsw_name[i], |
146 | sizeof(sc->sc_smpsw_name[i]), "%s-%d" , |
147 | device_xname(self), i); |
148 | sc->sc_smpsw[i].smpsw_name = sc->sc_smpsw_name[i]; |
149 | sc->sc_smpsw[i].smpsw_type = PSWITCH_TYPE_HOTKEY; |
150 | (void)sysmon_pswitch_register(&sc->sc_smpsw[i]); |
151 | } |
152 | |
153 | fujitsu_hk_sysctl_setup(sc); |
154 | |
155 | (void)pmf_device_register(self, fujitsu_hk_suspend, fujitsu_hk_resume); |
156 | (void)acpi_register_notify(sc->sc_node, fujitsu_hk_notify_handler); |
157 | } |
158 | |
159 | static int |
160 | fujitsu_hk_detach(device_t self, int flags) |
161 | { |
162 | struct fujitsu_hk_softc *sc = device_private(self); |
163 | int i; |
164 | |
165 | pmf_device_deregister(self); |
166 | |
167 | if (sc->sc_log != NULL) |
168 | sysctl_teardown(&sc->sc_log); |
169 | |
170 | acpi_deregister_notify(sc->sc_node); |
171 | |
172 | for (i = 0; i < FUJITSU_HK_PSW_COUNT; i++) |
173 | sysmon_pswitch_unregister(&sc->sc_smpsw[i]); |
174 | |
175 | mutex_destroy(&sc->sc_mtx); |
176 | |
177 | return 0; |
178 | } |
179 | |
180 | /* |
181 | * On the P7120, the backlight needs to be enabled after resume, since the |
182 | * laptop wakes up with the backlight off (even if it was on before suspend). |
183 | */ |
184 | static bool |
185 | fujitsu_hk_suspend(device_t self, const pmf_qual_t *qual) |
186 | { |
187 | struct fujitsu_hk_softc *sc = device_private(self); |
188 | |
189 | mutex_enter(&sc->sc_mtx); |
190 | (void)fujitsu_hk_set_backlight(sc, false); |
191 | mutex_exit(&sc->sc_mtx); |
192 | |
193 | return true; |
194 | } |
195 | |
196 | static bool |
197 | fujitsu_hk_resume(device_t self, const pmf_qual_t *qual) |
198 | { |
199 | struct fujitsu_hk_softc *sc = device_private(self); |
200 | |
201 | mutex_enter(&sc->sc_mtx); |
202 | (void)fujitsu_hk_set_backlight(sc, true); |
203 | mutex_exit(&sc->sc_mtx); |
204 | |
205 | return true; |
206 | } |
207 | |
208 | static uint16_t |
209 | fujitsu_hk_capabilities(const struct acpi_devnode *ad) |
210 | { |
211 | uint16_t caps; |
212 | |
213 | caps = 0; |
214 | |
215 | if (fujitsu_hk_cap(ad->ad_handle, "GIRB" , ACPI_TYPE_INTEGER)) |
216 | caps |= FUJITSU_HK_CAP_GIRB; |
217 | |
218 | if (fujitsu_hk_cap(ad->ad_handle, "FUNC" , ACPI_TYPE_METHOD)) |
219 | caps |= FUJITSU_HK_CAP_FUNC; |
220 | |
221 | return caps; |
222 | } |
223 | |
224 | static void |
225 | fujitsu_hk_notify_handler(ACPI_HANDLE handle, uint32_t evt, void *context) |
226 | { |
227 | struct fujitsu_hk_softc *sc = device_private(context); |
228 | static const int handler = OSL_NOTIFY_HANDLER; |
229 | |
230 | switch (evt) { |
231 | |
232 | case FUJITSU_HK_NOTIFY: |
233 | (void)AcpiOsExecute(handler, fujitsu_hk_event_callback, sc); |
234 | break; |
235 | |
236 | default: |
237 | aprint_debug_dev(sc->sc_dev, "unknown notify 0x%02X\n" , evt); |
238 | } |
239 | } |
240 | |
241 | static void |
242 | fujitsu_hk_event_callback(void *arg) |
243 | { |
244 | struct fujitsu_hk_softc *sc = arg; |
245 | const int max_irb_buffer_size = 100; |
246 | uint32_t irb; |
247 | int i, index; |
248 | |
249 | for (i = 0; i < max_irb_buffer_size; i++) { |
250 | if (fujitsu_hk_get_irb(sc, &irb) || irb == 0) |
251 | return; |
252 | |
253 | switch (irb & ~FUJITSU_HK_MODMASK) { |
254 | case FUJITSU_HK_IRB_HOTKEY_RELEASE: |
255 | /* Hotkey button release event (nothing to do). */ |
256 | break; |
257 | case FUJITSU_HK_IRB_HOTKEY_PRESS(0): |
258 | case FUJITSU_HK_IRB_HOTKEY_PRESS(1): |
259 | case FUJITSU_HK_IRB_HOTKEY_PRESS(2): |
260 | case FUJITSU_HK_IRB_HOTKEY_PRESS(3): |
261 | /* Hotkey button press event. */ |
262 | index = FUJITSU_HK_IRB_HOTKEY_INDEX(irb); |
263 | sysmon_pswitch_event(&sc->sc_smpsw[index], |
264 | PSWITCH_EVENT_PRESSED); |
265 | break; |
266 | default: |
267 | aprint_error_dev(sc->sc_dev, |
268 | "unknown GIRB result: 0x%" PRIx32"\n" , irb); |
269 | break; |
270 | } |
271 | } |
272 | } |
273 | |
274 | static void |
275 | fujitsu_hk_sysctl_setup(struct fujitsu_hk_softc *sc) |
276 | { |
277 | const struct sysctlnode *rnode; |
278 | bool dummy_state; |
279 | |
280 | if (fujitsu_hk_get_backlight(sc, &dummy_state) == 0) { |
281 | if ((sysctl_createv(&sc->sc_log, 0, NULL, &rnode, |
282 | 0, CTLTYPE_NODE, "acpi" , NULL, |
283 | NULL, 0, NULL, 0, |
284 | CTL_HW, CTL_CREATE, CTL_EOL)) != 0) |
285 | goto fail; |
286 | |
287 | if ((sysctl_createv(&sc->sc_log, 0, &rnode, &rnode, |
288 | 0, CTLTYPE_NODE, device_xname(sc->sc_dev), |
289 | SYSCTL_DESCR("Fujitsu hotkeys controls" ), |
290 | NULL, 0, NULL, 0, |
291 | CTL_CREATE, CTL_EOL)) != 0) |
292 | goto fail; |
293 | |
294 | (void)sysctl_createv(&sc->sc_log, 0, &rnode, NULL, |
295 | CTLFLAG_READWRITE, CTLTYPE_BOOL, "backlight" , |
296 | SYSCTL_DESCR("Internal DFP backlight switch state" ), |
297 | fujitsu_hk_sysctl_backlight, 0, (void *)sc, 0, |
298 | CTL_CREATE, CTL_EOL); |
299 | } |
300 | |
301 | return; |
302 | |
303 | fail: |
304 | aprint_error_dev(sc->sc_dev, "couldn't add sysctl nodes\n" ); |
305 | } |
306 | |
307 | static int |
308 | fujitsu_hk_sysctl_backlight(SYSCTLFN_ARGS) |
309 | { |
310 | struct sysctlnode node; |
311 | struct fujitsu_hk_softc *sc; |
312 | bool val; |
313 | int error; |
314 | |
315 | node = *rnode; |
316 | sc = node.sysctl_data; |
317 | |
318 | mutex_enter(&sc->sc_mtx); |
319 | error = fujitsu_hk_get_backlight(sc, &val); |
320 | mutex_exit(&sc->sc_mtx); |
321 | |
322 | if (error) |
323 | return error; |
324 | |
325 | node.sysctl_data = &val; |
326 | error = sysctl_lookup(SYSCTLFN_CALL(&node)); |
327 | if (error || newp == NULL) |
328 | return error; |
329 | |
330 | mutex_enter(&sc->sc_mtx); |
331 | error = fujitsu_hk_set_backlight(sc, val); |
332 | mutex_exit(&sc->sc_mtx); |
333 | |
334 | return error; |
335 | } |
336 | |
337 | static int |
338 | fujitsu_hk_get_irb(struct fujitsu_hk_softc *sc, uint32_t *valuep) |
339 | { |
340 | ACPI_HANDLE hdl = sc->sc_node->ad_handle; |
341 | ACPI_INTEGER val; |
342 | ACPI_STATUS rv; |
343 | |
344 | if (!(sc->sc_caps & FUJITSU_HK_CAP_GIRB)) |
345 | return ENODEV; |
346 | |
347 | rv = acpi_eval_integer(hdl, "GIRB" , &val); |
348 | if (ACPI_FAILURE(rv)) { |
349 | aprint_error_dev(sc->sc_dev, "failed to evaluate %s.%s: %s\n" , |
350 | acpi_name(hdl), "GIRB" , AcpiFormatException(rv)); |
351 | return EIO; |
352 | } |
353 | |
354 | *valuep = (uint32_t)val; |
355 | |
356 | return 0; |
357 | } |
358 | |
359 | static int |
360 | fujitsu_hk_get_backlight(struct fujitsu_hk_softc *sc, bool *valuep) |
361 | { |
362 | ACPI_HANDLE hdl = sc->sc_node->ad_handle; |
363 | ACPI_INTEGER args[] = { |
364 | FUJITSU_FUNC_TARGET_BACKLIGHT, |
365 | FUJITSU_FUNC_COMMAND_GET, |
366 | 0x4, |
367 | 0x0 |
368 | }; |
369 | ACPI_INTEGER val; |
370 | ACPI_STATUS rv; |
371 | |
372 | if (!(sc->sc_caps & FUJITSU_HK_CAP_FUNC)) |
373 | return ENODEV; |
374 | |
375 | rv = fujitsu_hk_eval_nary_integer(hdl, "FUNC" , args, 4, &val); |
376 | if (ACPI_FAILURE(rv)) { |
377 | aprint_error_dev(sc->sc_dev, "failed to evaluate %s.%s: %s\n" , |
378 | acpi_name(hdl), "FUNC" , AcpiFormatException(rv)); |
379 | return EIO; |
380 | } |
381 | |
382 | if (val == FUJITSU_FUNC_INVALID_ARGS) |
383 | return ENODEV; |
384 | |
385 | if (val == FUJITSU_FUNC_BACKLIGHT_ON) |
386 | *valuep = true; |
387 | else if (val == FUJITSU_FUNC_BACKLIGHT_OFF) |
388 | *valuep = false; |
389 | else |
390 | return ERANGE; |
391 | |
392 | return 0; |
393 | } |
394 | |
395 | static int |
396 | fujitsu_hk_set_backlight(struct fujitsu_hk_softc *sc, bool value) |
397 | { |
398 | ACPI_HANDLE hdl = sc->sc_node->ad_handle; |
399 | ACPI_INTEGER args[] = { |
400 | FUJITSU_FUNC_TARGET_BACKLIGHT, |
401 | FUJITSU_FUNC_COMMAND_SET, |
402 | 0x4, |
403 | 0x0 |
404 | }; |
405 | ACPI_INTEGER val; |
406 | ACPI_STATUS rv; |
407 | |
408 | if (!(sc->sc_caps & FUJITSU_HK_CAP_FUNC)) |
409 | return ENODEV; |
410 | |
411 | if (value) |
412 | args[3] = FUJITSU_FUNC_BACKLIGHT_ON; |
413 | else |
414 | args[3] = FUJITSU_FUNC_BACKLIGHT_OFF; |
415 | |
416 | rv = fujitsu_hk_eval_nary_integer(hdl, "FUNC" , args, 4, &val); |
417 | if (ACPI_FAILURE(rv)) { |
418 | aprint_error_dev(sc->sc_dev, "failed to evaluate %s.%s: %s\n" , |
419 | acpi_name(hdl), "FUNC" , AcpiFormatException(rv)); |
420 | return EIO; |
421 | } |
422 | |
423 | if (val == FUJITSU_FUNC_INVALID_ARGS) |
424 | return ENODEV; |
425 | |
426 | return 0; |
427 | } |
428 | |
429 | /* |
430 | * fujitsu_hk_cap: |
431 | * |
432 | * Returns true if and only if (a) the object handle.path exists and |
433 | * (b) this object is a method or has the given type. |
434 | */ |
435 | static bool |
436 | fujitsu_hk_cap(ACPI_HANDLE handle, const char *path, ACPI_OBJECT_TYPE type) |
437 | { |
438 | ACPI_HANDLE hdl; |
439 | ACPI_OBJECT_TYPE typ; |
440 | |
441 | KASSERT(handle != NULL); |
442 | |
443 | if (ACPI_FAILURE(AcpiGetHandle(handle, path, &hdl))) |
444 | return false; |
445 | |
446 | if (ACPI_FAILURE(AcpiGetType(hdl, &typ))) |
447 | return false; |
448 | |
449 | if (typ != ACPI_TYPE_METHOD && typ != type) |
450 | return false; |
451 | |
452 | return true; |
453 | } |
454 | |
455 | /* |
456 | * fujitsu_hk_eval_nary_integer: |
457 | * |
458 | * Evaluate an object that takes as input an arbitrary (possible null) |
459 | * number of integer parameters. If res is not NULL, then *res is filled |
460 | * with the result of the evaluation, and AE_NULL_OBJECT is returned if |
461 | * the evaluation produced no result. |
462 | */ |
463 | static ACPI_STATUS |
464 | fujitsu_hk_eval_nary_integer(ACPI_HANDLE handle, const char *path, const |
465 | ACPI_INTEGER *args, uint8_t count, ACPI_INTEGER *res) |
466 | { |
467 | ACPI_OBJECT_LIST paramlist; |
468 | ACPI_OBJECT retobj, objpool[4], *argobjs; |
469 | ACPI_BUFFER buf; |
470 | ACPI_STATUS rv; |
471 | uint8_t i; |
472 | |
473 | /* Require that (args == NULL) if and only if (count == 0). */ |
474 | KASSERT((args != NULL || count == 0) && (args == NULL || count != 0)); |
475 | |
476 | /* The object pool should be large enough for our callers. */ |
477 | KASSERT(count <= __arraycount(objpool)); |
478 | |
479 | if (handle == NULL) |
480 | handle = ACPI_ROOT_OBJECT; |
481 | |
482 | /* Convert the given array args into an array of ACPI objects. */ |
483 | argobjs = objpool; |
484 | for (i = 0; i < count; i++) { |
485 | argobjs[i].Type = ACPI_TYPE_INTEGER; |
486 | argobjs[i].Integer.Value = args[i]; |
487 | } |
488 | |
489 | paramlist.Count = count; |
490 | paramlist.Pointer = argobjs; |
491 | |
492 | (void)memset(&retobj, 0, sizeof(retobj)); |
493 | buf.Pointer = &retobj; |
494 | buf.Length = sizeof(retobj); |
495 | |
496 | rv = AcpiEvaluateObject(handle, path, ¶mlist, &buf); |
497 | |
498 | if (ACPI_FAILURE(rv)) |
499 | return rv; |
500 | |
501 | /* |
502 | * If a return value is expected and desired (i.e. res != NULL), |
503 | * then copy the result into *res. |
504 | */ |
505 | if (res != NULL) { |
506 | if (buf.Length == 0) |
507 | return AE_NULL_OBJECT; |
508 | |
509 | if (retobj.Type != ACPI_TYPE_INTEGER) |
510 | return AE_TYPE; |
511 | |
512 | *res = retobj.Integer.Value; |
513 | } |
514 | |
515 | return AE_OK; |
516 | } |
517 | |
518 | MODULE(MODULE_CLASS_DRIVER, fujhk, "sysmon_power" ); |
519 | |
520 | #ifdef _MODULE |
521 | #include "ioconf.c" |
522 | #endif |
523 | |
524 | static int |
525 | fujhk_modcmd(modcmd_t cmd, void *aux) |
526 | { |
527 | int rv = 0; |
528 | |
529 | switch (cmd) { |
530 | |
531 | case MODULE_CMD_INIT: |
532 | |
533 | #ifdef _MODULE |
534 | rv = config_init_component(cfdriver_ioconf_fujhk, |
535 | cfattach_ioconf_fujhk, cfdata_ioconf_fujhk); |
536 | #endif |
537 | break; |
538 | |
539 | case MODULE_CMD_FINI: |
540 | |
541 | #ifdef _MODULE |
542 | rv = config_fini_component(cfdriver_ioconf_fujhk, |
543 | cfattach_ioconf_fujhk, cfdata_ioconf_fujhk); |
544 | #endif |
545 | break; |
546 | |
547 | default: |
548 | rv = ENOTTY; |
549 | } |
550 | |
551 | return rv; |
552 | } |
553 | |