xref: /netbsd-src/sys/dev/usb/irmce.c (revision fdd524d4ccd2bb0c6f67401e938dabf773eb0372)
1 /* $NetBSD: irmce.c,v 1.2 2016/04/23 10:15:32 skrll Exp $ */
2 
3 /*-
4  * Copyright (c) 2011 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 AUTHOR AND CONTRIBUTORS ``AS IS''
17  * 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 /*
30  * IR receiver/transceiver for Windows Media Center
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: irmce.c,v 1.2 2016/04/23 10:15:32 skrll Exp $");
35 
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/systm.h>
39 #include <sys/device.h>
40 #include <sys/conf.h>
41 #include <sys/bus.h>
42 #include <sys/select.h>
43 #include <sys/module.h>
44 
45 #include <dev/usb/usb.h>
46 #include <dev/usb/usbdi.h>
47 #include <dev/usb/usbdi_util.h>
48 #include <dev/usb/usbdevs.h>
49 
50 #include <dev/ir/ir.h>
51 #include <dev/ir/cirio.h>
52 #include <dev/ir/cirvar.h>
53 
54 enum irmce_state {
55 	IRMCE_STATE_HEADER,
56 	IRMCE_STATE_IRDATA,
57 	IRMCE_STATE_CMDHEADER,
58 	IRMCE_STATE_CMDDATA,
59 };
60 
61 struct irmce_softc {
62 	device_t		sc_dev;
63 	device_t		sc_cirdev;
64 
65 	struct usbd_device *	sc_udev;
66 	struct usbd_interface *	sc_iface;
67 
68 	int			sc_bulkin_ep;
69 	uint16_t		sc_bulkin_maxpktsize;
70 	struct usbd_pipe *	sc_bulkin_pipe;
71 	struct usbd_xfer *	sc_bulkin_xfer;
72 	uint8_t *		sc_bulkin_buffer;
73 
74 	int			sc_bulkout_ep;
75 	uint16_t		sc_bulkout_maxpktsize;
76 	struct usbd_pipe *	sc_bulkout_pipe;
77 	struct usbd_xfer *	sc_bulkout_xfer;
78 	uint8_t *		sc_bulkout_buffer;
79 
80 	bool			sc_raw;
81 
82 	uint8_t			sc_ir_buf[16];
83 	size_t			sc_ir_bufused;
84 	size_t			sc_ir_resid;
85 	enum irmce_state	sc_ir_state;
86 	uint8_t			sc_ir_header;
87 
88 	bool			sc_rc6_hb[256];
89 	size_t			sc_rc6_nhb;
90 };
91 
92 static int	irmce_match(device_t, cfdata_t, void *);
93 static void	irmce_attach(device_t, device_t, void *);
94 static int	irmce_detach(device_t, int);
95 static void	irmce_childdet(device_t, device_t);
96 static int	irmce_activate(device_t, enum devact);
97 static int	irmce_rescan(device_t, const char *, const int *);
98 
99 static int	irmce_print(void *, const char *);
100 
101 static int	irmce_reset(struct irmce_softc *);
102 
103 static int	irmce_open(void *, int, int, struct proc *);
104 static int	irmce_close(void *, int, int, struct proc *);
105 static int	irmce_read(void *, struct uio *, int);
106 static int	irmce_write(void *, struct uio *, int);
107 static int	irmce_setparams(void *, struct cir_params *);
108 
109 static const struct cir_methods irmce_cir_methods = {
110 	.im_open = irmce_open,
111 	.im_close = irmce_close,
112 	.im_read = irmce_read,
113 	.im_write = irmce_write,
114 	.im_setparams = irmce_setparams,
115 };
116 
117 static const struct {
118 	uint16_t		vendor;
119 	uint16_t		product;
120 } irmce_devices[] = {
121 	{ USB_VENDOR_SMK, USB_PRODUCT_SMK_MCE_IR },
122 };
123 
124 CFATTACH_DECL2_NEW(irmce, sizeof(struct irmce_softc),
125     irmce_match, irmce_attach, irmce_detach, irmce_activate,
126     irmce_rescan, irmce_childdet);
127 
128 static int
129 irmce_match(device_t parent, cfdata_t match, void *opaque)
130 {
131 	struct usbif_attach_arg *uiaa = opaque;
132 	unsigned int i;
133 
134 	for (i = 0; i < __arraycount(irmce_devices); i++) {
135 		if (irmce_devices[i].vendor == uiaa->uiaa_vendor &&
136 		    irmce_devices[i].product == uiaa->uiaa_product)
137 			return UMATCH_VENDOR_PRODUCT;
138 	}
139 
140 	return UMATCH_NONE;
141 }
142 
143 static void
144 irmce_attach(device_t parent, device_t self, void *opaque)
145 {
146 	struct irmce_softc *sc = device_private(self);
147 	struct usbif_attach_arg *uiaa = opaque;
148 	usb_endpoint_descriptor_t *ed;
149 	char *devinfop;
150 	unsigned int i;
151 	uint8_t nep;
152 
153 	pmf_device_register(self, NULL, NULL);
154 
155 	aprint_naive("\n");
156 
157 	devinfop = usbd_devinfo_alloc(uiaa->uiaa_device, 0);
158 	aprint_normal(": %s\n", devinfop);
159 	usbd_devinfo_free(devinfop);
160 
161 	sc->sc_dev = self;
162 	sc->sc_udev = uiaa->uiaa_device;
163 	sc->sc_iface = uiaa->uiaa_iface;
164 
165 	nep = 0;
166 	usbd_endpoint_count(sc->sc_iface, &nep);
167 	sc->sc_bulkin_ep = sc->sc_bulkout_ep = -1;
168 	for (i = 0; i < nep; i++) {
169 		int dir, type;
170 
171 		ed = usbd_interface2endpoint_descriptor(sc->sc_iface, i);
172 		if (ed == NULL) {
173 			aprint_error_dev(self,
174 			    "couldn't read endpoint descriptor %d\n", i);
175 			continue;
176 		}
177 
178 		dir = UE_GET_DIR(ed->bEndpointAddress);
179 		type = UE_GET_XFERTYPE(ed->bmAttributes);
180 
181 		if (type != UE_BULK)
182 			continue;
183 
184 		if (dir == UE_DIR_IN && sc->sc_bulkin_ep == -1) {
185 			sc->sc_bulkin_ep = ed->bEndpointAddress;
186 			sc->sc_bulkin_maxpktsize =
187 			    UE_GET_SIZE(UGETW(ed->wMaxPacketSize)) *
188 			    (UE_GET_TRANS(UGETW(ed->wMaxPacketSize)) + 1);
189 		}
190 		if (dir == UE_DIR_OUT && sc->sc_bulkout_ep == -1) {
191 			sc->sc_bulkout_ep = ed->bEndpointAddress;
192 			sc->sc_bulkout_maxpktsize =
193 			    UE_GET_SIZE(UGETW(ed->wMaxPacketSize)) *
194 			    (UE_GET_TRANS(UGETW(ed->wMaxPacketSize)) + 1);
195 		}
196 	}
197 
198 	aprint_debug_dev(self, "in 0x%02x/%d out 0x%02x/%d\n",
199 	    sc->sc_bulkin_ep, sc->sc_bulkin_maxpktsize,
200 	    sc->sc_bulkout_ep, sc->sc_bulkout_maxpktsize);
201 
202 	if (sc->sc_bulkin_maxpktsize < 16 || sc->sc_bulkout_maxpktsize < 16) {
203 		aprint_error_dev(self, "bad maxpktsize\n");
204 		return;
205 	}
206 	usbd_status err;
207 
208 	err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkin_ep,
209 	    USBD_EXCLUSIVE_USE, &sc->sc_bulkin_pipe);
210 	if (err) {
211 		aprint_error_dev(sc->sc_dev,
212 		    "couldn't open bulk-in pipe: %s\n", usbd_errstr(err));
213 		return;
214 	}
215 	err = usbd_open_pipe(sc->sc_iface, sc->sc_bulkout_ep,
216 	    USBD_EXCLUSIVE_USE, &sc->sc_bulkout_pipe);
217 	if (err) {
218 		aprint_error_dev(sc->sc_dev,
219 		    "couldn't open bulk-out pipe: %s\n", usbd_errstr(err));
220 		usbd_close_pipe(sc->sc_bulkin_pipe);
221 		sc->sc_bulkin_pipe = NULL;
222 		return;
223 	}
224 
225 	int error;
226 	error = usbd_create_xfer(sc->sc_bulkin_pipe, sc->sc_bulkin_maxpktsize,
227 	    USBD_SHORT_XFER_OK, 0, &sc->sc_bulkin_xfer);
228 	if (error) {
229 		goto fail;
230 	}
231 
232 	error = usbd_create_xfer(sc->sc_bulkout_pipe,
233 	    sc->sc_bulkout_maxpktsize, USBD_FORCE_SHORT_XFER, 0,
234 	    &sc->sc_bulkout_xfer);
235 	if (error) {
236 		goto fail;
237 	}
238 	sc->sc_bulkin_buffer = usbd_get_buffer(sc->sc_bulkin_xfer);
239 	sc->sc_bulkout_buffer = usbd_get_buffer(sc->sc_bulkout_xfer);
240 
241 	irmce_rescan(self, NULL, NULL);
242 	return;
243 
244 fail:
245 	if (sc->sc_bulkin_xfer)
246 		usbd_destroy_xfer(sc->sc_bulkin_xfer);
247 	if (sc->sc_bulkout_xfer)
248 		usbd_destroy_xfer(sc->sc_bulkout_xfer);
249 }
250 
251 static int
252 irmce_detach(device_t self, int flags)
253 {
254 	struct irmce_softc *sc = device_private(self);
255 	int error;
256 
257 	if (sc->sc_cirdev) {
258 		error = config_detach(sc->sc_cirdev, flags);
259 		if (error)
260 			return error;
261 	}
262 
263 	if (sc->sc_bulkin_pipe) {
264 		usbd_abort_pipe(sc->sc_bulkin_pipe);
265 	}
266 	if (sc->sc_bulkout_pipe) {
267 		usbd_abort_pipe(sc->sc_bulkout_pipe);
268 	}
269 	if (sc->sc_bulkin_xfer) {
270 		usbd_destroy_xfer(sc->sc_bulkin_xfer);
271 		sc->sc_bulkin_buffer = NULL;
272 		sc->sc_bulkin_xfer = NULL;
273 	}
274 	if (sc->sc_bulkout_xfer) {
275 		usbd_destroy_xfer(sc->sc_bulkout_xfer);
276 		sc->sc_bulkout_buffer = NULL;
277 		sc->sc_bulkout_xfer = NULL;
278 	}
279 	if (sc->sc_bulkin_pipe) {
280 		usbd_close_pipe(sc->sc_bulkin_pipe);
281 		sc->sc_bulkin_pipe = NULL;
282 	}
283 	if (sc->sc_bulkout_pipe) {
284 		usbd_close_pipe(sc->sc_bulkout_pipe);
285 		sc->sc_bulkout_pipe = NULL;
286 	}
287 
288 	pmf_device_deregister(self);
289 
290 	return 0;
291 }
292 
293 static int
294 irmce_activate(device_t self, enum devact act)
295 {
296 	return 0;
297 }
298 
299 static int
300 irmce_rescan(device_t self, const char *ifattr, const int *locators)
301 {
302 	struct irmce_softc *sc = device_private(self);
303 	struct ir_attach_args iaa;
304 
305 	if (sc->sc_cirdev == NULL) {
306 		iaa.ia_type = IR_TYPE_CIR;
307 		iaa.ia_methods = &irmce_cir_methods;
308 		iaa.ia_handle = sc;
309 		sc->sc_cirdev = config_found_ia(self, "irbus",
310 		    &iaa, irmce_print);
311 	}
312 
313 	return 0;
314 }
315 
316 static int
317 irmce_print(void *priv, const char *pnp)
318 {
319 	if (pnp)
320 		aprint_normal("cir at %s", pnp);
321 
322 	return UNCONF;
323 }
324 
325 static void
326 irmce_childdet(device_t self, device_t child)
327 {
328 	struct irmce_softc *sc = device_private(self);
329 
330 	if (sc->sc_cirdev == child)
331 		sc->sc_cirdev = NULL;
332 }
333 
334 static int
335 irmce_reset(struct irmce_softc *sc)
336 {
337 	static const uint8_t reset_cmd[] = { 0x00, 0xff, 0xaa };
338 	uint8_t *p = sc->sc_bulkout_buffer;
339 	usbd_status err;
340 	uint32_t wlen;
341 	unsigned int n;
342 
343 	for (n = 0; n < __arraycount(reset_cmd); n++)
344 		*p++ = reset_cmd[n];
345 
346 	wlen = sizeof(reset_cmd);
347 	err = usbd_bulk_transfer(sc->sc_bulkout_xfer, sc->sc_bulkout_pipe,
348 	    USBD_FORCE_SHORT_XFER, USBD_DEFAULT_TIMEOUT,
349 	    sc->sc_bulkout_buffer, &wlen);
350 	if (err != USBD_NORMAL_COMPLETION) {
351 		if (err == USBD_INTERRUPTED)
352 			return EINTR;
353 		else if (err == USBD_TIMEOUT)
354 			return ETIMEDOUT;
355 		else
356 			return EIO;
357 	}
358 
359 	return 0;
360 }
361 
362 static int
363 irmce_open(void *priv, int flag, int mode, struct proc *p)
364 {
365 	struct irmce_softc *sc = priv;
366 	int err = irmce_reset(sc);
367 	if (err) {
368 		aprint_error_dev(sc->sc_dev,
369 		    "couldn't reset device: %s\n", usbd_errstr(err));
370 	}
371 	sc->sc_ir_state = IRMCE_STATE_HEADER;
372 	sc->sc_rc6_nhb = 0;
373 
374 	return 0;
375 }
376 
377 static int
378 irmce_close(void *priv, int flag, int mode, struct proc *p)
379 {
380 	struct irmce_softc *sc = priv;
381 
382 	if (sc->sc_bulkin_pipe) {
383 		usbd_abort_pipe(sc->sc_bulkin_pipe);
384 	}
385 	if (sc->sc_bulkout_pipe) {
386 		usbd_abort_pipe(sc->sc_bulkout_pipe);
387 	}
388 
389 	return 0;
390 }
391 
392 static int
393 irmce_rc6_decode(struct irmce_softc *sc, uint8_t *buf, size_t buflen,
394     struct uio *uio)
395 {
396 	bool *hb = &sc->sc_rc6_hb[0];
397 	unsigned int n;
398 	int state, pulse;
399 	uint32_t data;
400 	uint8_t mode;
401 	bool idle = false;
402 
403 	for (n = 0; n < buflen; n++) {
404 		state = (buf[n] & 0x80) ? 1 : 0;
405 		pulse = (buf[n] & 0x7f) * 50;
406 
407 		if (pulse >= 300 && pulse <= 600) {
408 			hb[sc->sc_rc6_nhb++] = state;
409 		} else if (pulse >= 680 && pulse <= 1080) {
410 			hb[sc->sc_rc6_nhb++] = state;
411 			hb[sc->sc_rc6_nhb++] = state;
412 		} else if (pulse >= 1150 && pulse <= 1450) {
413 			hb[sc->sc_rc6_nhb++] = state;
414 			hb[sc->sc_rc6_nhb++] = state;
415 			hb[sc->sc_rc6_nhb++] = state;
416 		} else if (pulse >= 2400 && pulse <= 2800) {
417 			hb[sc->sc_rc6_nhb++] = state;
418 			hb[sc->sc_rc6_nhb++] = state;
419 			hb[sc->sc_rc6_nhb++] = state;
420 			hb[sc->sc_rc6_nhb++] = state;
421 			hb[sc->sc_rc6_nhb++] = state;
422 			hb[sc->sc_rc6_nhb++] = state;
423 		} else if (pulse > 3000) {
424 			if (sc->sc_rc6_nhb & 1)
425 				hb[sc->sc_rc6_nhb++] = state;
426 			idle = true;
427 			break;
428 		} else {
429 			aprint_debug_dev(sc->sc_dev,
430 			    "error parsing RC6 stream (pulse=%d)\n", pulse);
431 			return EIO;
432 		}
433 	}
434 
435 	if (!idle)
436 		return 0;
437 
438 	if (sc->sc_rc6_nhb < 20) {
439 		aprint_debug_dev(sc->sc_dev, "not enough RC6 data\n");
440 		return EIO;
441 	}
442 
443 	/* RC6 leader 11111100 */
444 	if (!hb[0] || !hb[1] || !hb[2] || !hb[3] || !hb[4] || !hb[5] ||
445 	    hb[6] || hb[7]) {
446 		aprint_debug_dev(sc->sc_dev, "bad RC6 leader\n");
447 		return EIO;
448 	}
449 
450 	/* start bit 10 */
451 	if (!hb[8] || hb[9]) {
452 		aprint_debug_dev(sc->sc_dev, "missing RC6 start bit\n");
453 		return EIO;
454 	}
455 
456 	/* mode info */
457 	mode = 0x00;
458 	for (n = 10; n < 15; n += 2) {
459 		if (hb[n] && !hb[n + 1])
460 			mode = (mode << 1) | 1;
461 		else if (!hb[n] && hb[n + 1])
462 			mode = (mode << 1) | 0;
463 		else {
464 			aprint_debug_dev(sc->sc_dev, "bad RC6 mode bits\n");
465 			return EIO;
466 		}
467 	}
468 
469 	data = 0;
470 	for (n = 20; n < sc->sc_rc6_nhb; n += 2) {
471 		if (hb[n] && !hb[n + 1])
472 			data = (data << 1) | 1;
473 		else if (!hb[n] && hb[n + 1])
474 			data = (data << 1) | 0;
475 		else {
476 			aprint_debug_dev(sc->sc_dev, "bad RC6 data bits\n");
477 			return EIO;
478 		}
479 	}
480 
481 	sc->sc_rc6_nhb = 0;
482 
483 	return uiomove(&data, sizeof(data), uio);
484 }
485 
486 static int
487 irmce_process(struct irmce_softc *sc, uint8_t *buf, size_t buflen,
488     struct uio *uio)
489 {
490 	uint8_t *p = buf;
491 	uint8_t data, cmd;
492 	int error;
493 
494 	while (p - buf < (ssize_t)buflen) {
495 		switch (sc->sc_ir_state) {
496 		case IRMCE_STATE_HEADER:
497 			sc->sc_ir_header = data = *p++;
498 			if ((data & 0xe0) == 0x80 && (data & 0x1f) != 0x1f) {
499 				sc->sc_ir_bufused = 0;
500 				sc->sc_ir_resid = data & 0x1f;
501 				sc->sc_ir_state = IRMCE_STATE_IRDATA;
502 				if (sc->sc_ir_resid > sizeof(sc->sc_ir_buf))
503 					return EIO;
504 				if (sc->sc_ir_resid == 0)
505 					sc->sc_ir_state = IRMCE_STATE_HEADER;
506 			} else {
507 				sc->sc_ir_state = IRMCE_STATE_CMDHEADER;
508 			}
509 			break;
510 		case IRMCE_STATE_CMDHEADER:
511 			cmd = *p++;
512 			data = sc->sc_ir_header;
513 			if (data == 0x00 && cmd == 0x9f)
514 				sc->sc_ir_resid = 1;
515 			else if (data == 0xff && cmd == 0x0b)
516 				sc->sc_ir_resid = 2;
517 			else if (data == 0x9f) {
518 				if (cmd == 0x04 || cmd == 0x06 ||
519 				    cmd == 0x0c || cmd == 0x15) {
520 					sc->sc_ir_resid = 2;
521 				} else if (cmd == 0x01 || cmd == 0x08 ||
522 				    cmd == 0x14) {
523 					sc->sc_ir_resid = 1;
524 				}
525 			}
526 			if (sc->sc_ir_resid > 0)
527 				sc->sc_ir_state = IRMCE_STATE_CMDDATA;
528 			else
529 				sc->sc_ir_state = IRMCE_STATE_HEADER;
530 			break;
531 		case IRMCE_STATE_IRDATA:
532 			sc->sc_ir_resid--;
533 			sc->sc_ir_buf[sc->sc_ir_bufused++] = *p;
534 			p++;
535 			if (sc->sc_ir_resid == 0) {
536 				sc->sc_ir_state = IRMCE_STATE_HEADER;
537 				error = irmce_rc6_decode(sc,
538 				    sc->sc_ir_buf, sc->sc_ir_bufused, uio);
539 				if (error)
540 					sc->sc_rc6_nhb = 0;
541 			}
542 			break;
543 		case IRMCE_STATE_CMDDATA:
544 			p++;
545 			sc->sc_ir_resid--;
546 			if (sc->sc_ir_resid == 0)
547 				sc->sc_ir_state = IRMCE_STATE_HEADER;
548 			break;
549 		}
550 
551 	}
552 
553 	return 0;
554 }
555 
556 static int
557 irmce_read(void *priv, struct uio *uio, int flag)
558 {
559 	struct irmce_softc *sc = priv;
560 	usbd_status err;
561 	uint32_t rlen;
562 	int error = 0;
563 
564 	while (uio->uio_resid > 0) {
565 		rlen = sc->sc_bulkin_maxpktsize;
566 		err = usbd_bulk_transfer(sc->sc_bulkin_xfer,
567 		    sc->sc_bulkin_pipe, USBD_SHORT_XFER_OK,
568 		    USBD_DEFAULT_TIMEOUT, sc->sc_bulkin_buffer, &rlen);
569 		if (err != USBD_NORMAL_COMPLETION) {
570 			if (err == USBD_INTERRUPTED)
571 				return EINTR;
572 			else if (err == USBD_TIMEOUT)
573 				continue;
574 			else
575 				return EIO;
576 		}
577 
578 		if (sc->sc_raw) {
579 			error = uiomove(sc->sc_bulkin_buffer, rlen, uio);
580 			break;
581 		} else {
582 			error = irmce_process(sc, sc->sc_bulkin_buffer,
583 			    rlen, uio);
584 			if (error)
585 				break;
586 		}
587 	}
588 
589 	return error;
590 }
591 
592 static int
593 irmce_write(void *priv, struct uio *uio, int flag)
594 {
595 	return EIO;
596 }
597 
598 static int
599 irmce_setparams(void *priv, struct cir_params *params)
600 {
601 	struct irmce_softc *sc = priv;
602 
603 	if (params->raw > 1)
604 		return EINVAL;
605 	sc->sc_raw = params->raw;
606 
607 	return 0;
608 }
609 
610 MODULE(MODULE_CLASS_DRIVER, irmce, NULL);
611 
612 #ifdef _MODULE
613 #include "ioconf.c"
614 #endif
615 
616 static int
617 irmce_modcmd(modcmd_t cmd, void *opaque)
618 {
619 	switch (cmd) {
620 	case MODULE_CMD_INIT:
621 #ifdef _MODULE
622 		return config_init_component(cfdriver_ioconf_irmce,
623 		    cfattach_ioconf_irmce, cfdata_ioconf_irmce);
624 #else
625 		return 0;
626 #endif
627 	case MODULE_CMD_FINI:
628 #ifdef _MODULE
629 		return config_fini_component(cfdriver_ioconf_irmce,
630 		    cfattach_ioconf_irmce, cfdata_ioconf_irmce);
631 #else
632 		return 0;
633 #endif
634 	default:
635 		return ENOTTY;
636 	}
637 }
638