xref: /netbsd-src/sys/dev/acpi/hpacel_acpi.c (revision 3772555307d774424243e6d1cdc66aa160602c26)
1 /*	$NetBSD: hpacel_acpi.c,v 1.6 2021/01/29 15:49:55 thorpej Exp $ */
2 
3 /*-
4  * Copyright (c) 2009, 2011 Jukka Ruohonen <jruohonen@iki.fi>
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  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 #include <sys/cdefs.h>
30 __KERNEL_RCSID(0, "$NetBSD: hpacel_acpi.c,v 1.6 2021/01/29 15:49:55 thorpej Exp $");
31 
32 #include <sys/param.h>
33 #include <sys/module.h>
34 
35 #include <dev/acpi/acpireg.h>
36 #include <dev/acpi/acpivar.h>
37 #include <dev/acpi/acpi_power.h>
38 
39 #include <dev/sysmon/sysmonvar.h>
40 
41 #define _COMPONENT	ACPI_RESOURCE_COMPONENT
42 ACPI_MODULE_NAME	("hpacel_acpi")
43 
44 /*
45  * An ACPI driver for Hewlett-Packard 3D DriveGuard accelerometer.
46  *
47  * The supported chipset is LIS3LV02DL from STMicroelectronics:
48  *
49  *    http://www.st.com/stonline/products/literature/anp/12441.pdf
50  *
51  *    (Obtained on Sat Apr 25 00:32:04 EEST 2009.)
52  *
53  * The chip is a three axes digital output linear accelerometer
54  * that is controllable through I2C / SPI serial interface. This
55  * implementation however supports only indirect connection through
56  * ACPI. Other chips from the same family, such as LIS3LV02DQ, may
57  * also work with the driver, provided that there is a suitable DSDT.
58  *
59  * The chip can generate wake-up, direction detection and free-fall
60  * interrupts. The latter could be used to evoke emergency action.
61  * None of this is however supported. Only sysmon_envsys(9) is used.
62  */
63 enum {
64 	HPACEL_SENSOR_X = 0,
65 	HPACEL_SENSOR_Y,
66 	HPACEL_SENSOR_Z,
67 	HPACEL_SENSOR_COUNT
68 };
69 
70 #define LIS3LV02DL_ID     0x3A
71 
72 enum lis3lv02dl_reg {
73 	WHO_AM_I        = 0x0F,	/* r  */
74 	OFFSET_X        = 0x16,	/* rw */
75 	OFFSET_Y        = 0x17,	/* rw */
76 	OFFSET_Z        = 0x18,	/* rw */
77 	GAIN_X          = 0x19,	/* rw */
78 	GAIN_Y          = 0x1A,	/* rw */
79 	GAIN_Z          = 0x1B,	/* rw */
80 	CTRL_REG1       = 0x20,	/* rw */
81 	CTRL_REG2       = 0x21,	/* rw */
82 	CTRL_REG3       = 0x22,	/* rw */
83 	HP_FILTER_RESET = 0x23,	/* r  */
84 	STATUS_REG      = 0x27,	/* rw */
85 	OUTX_L          = 0x28,	/* r  */
86 	OUTX_H          = 0x29,	/* r  */
87 	OUTY_L          = 0x2A,	/* r  */
88 	OUTY_H          = 0x2B,	/* r  */
89 	OUTZ_L          = 0x2C,	/* r  */
90 	OUTZ_H          = 0x2D,	/* r  */
91 	FF_WU_CFG       = 0x30,	/* r  */
92 	FF_WU_SRC       = 0x31,	/* rw */
93 	FF_WU_ACK       = 0x32,	/* r  */
94 	FF_WU_THS_L     = 0x34,	/* rw */
95 	FF_WU_THS_H     = 0x35,	/* rw */
96 	FF_WU_DURATION  = 0x36,	/* rw */
97 	DD_CFG          = 0x38,	/* rw */
98 	DD_SRC          = 0x39,	/* rw */
99 	DD_ACK          = 0x3A,	/* r  */
100 	DD_THSI_L       = 0x3C,	/* rw */
101 	DD_THSI_H       = 0x3D,	/* rw */
102 	DD_THSE_L       = 0x3E,	/* rw */
103 	DD_THSE_H       = 0x3F	/* rw */
104 };
105 
106 enum lis3lv02dl_ctrl1 {
107 	CTRL1_Xen  = (1 << 0),	/* X-axis enable */
108 	CTRL1_Yen  = (1 << 1),	/* Y-axis enable */
109 	CTRL1_Zen  = (1 << 2),	/* Z-axis enable */
110 	CTRL1_ST   = (1 << 3),	/* Self test enable */
111 	CTRL1_DF0  = (1 << 4),	/* Decimation factor control */
112 	CTRL1_DF1  = (1 << 5),	/* Decimation factor control */
113 	CTRL1_PD0  = (1 << 6),	/* Power down control */
114 	CTRL1_PD1  = (1 << 7)	/* Power down control */
115 };
116 
117 enum lis3lv02dl_ctrl2 {
118 	CTRL2_DAS  = (1 << 0),  /* Data alignment selection */
119 	CTRL2_SIM  = (1 << 1),  /* SPI serial interface mode */
120 	CTRL2_DRDY = (1 << 2),  /* Enable data-ready generation */
121 	CTRL2_IEN  = (1 << 3),  /* Enable interrupt mode */
122 	CTRL2_BOOT = (1 << 4),  /* Reboot memory contents */
123 	CTRL2_BLE  = (1 << 5),  /* Endian mode */
124 	CTRL2_BDU  = (1 << 6),  /* Block data update */
125 	CTRL2_FS   = (1 << 7)   /* Full scale selection */
126 };
127 
128 enum lis3lv02dl_ctrl3 {
129 	CTRL3_CFS0 = (1 << 0),	/* High-pass filter cut-off frequency */
130 	CTRL3_CFS1 = (1 << 1),  /* High-pass filter cut-off frequency */
131 	CTRL3_FDS  = (1 << 4),  /* Filtered data selection */
132 	CTRL3_HPFF = (1 << 5),  /* High pass filter for free-fall */
133 	CTRL3_HPDD = (1 << 6),  /* High pass filter for DD */
134 	CTRL3_ECK  = (1 << 7)   /* External clock */
135 };
136 
137 struct hpacel_softc {
138 	device_t		 sc_dev;
139 	struct acpi_devnode	*sc_node;
140 	struct sysmon_envsys	*sc_sme;
141 	bool			 sc_state;
142 	uint8_t			 sc_whoami;
143 	uint8_t			 sc_ctrl[3];
144 	envsys_data_t		 sc_sensor[HPACEL_SENSOR_COUNT];
145 };
146 
147 static const struct device_compatible_entry compat_data[] = {
148 	{ .compat = "HPQ0004" },
149 	DEVICE_COMPAT_EOL
150 };
151 
152 static int		hpacel_match(device_t, cfdata_t, void *);
153 static void		hpacel_attach(device_t, device_t, void *);
154 static int		hpacel_detach(device_t, int);
155 static bool		hpacel_reg_init(device_t);
156 static bool		hpacel_suspend(device_t, const pmf_qual_t *);
157 static bool		hpacel_resume(device_t, const pmf_qual_t *);
158 static ACPI_STATUS	hpacel_reg_info(device_t);
159 static ACPI_STATUS	hpacel_reg_read(ACPI_HANDLE, ACPI_INTEGER, uint8_t *);
160 static ACPI_STATUS	hpacel_reg_write(ACPI_HANDLE, ACPI_INTEGER, uint8_t);
161 static ACPI_STATUS	hpacel_reg_xyz(ACPI_HANDLE, const int, int16_t *);
162 static ACPI_STATUS	hpacel_power(device_t, bool);
163 static bool		hpacel_sensor_init(device_t);
164 static void		hpacel_sensor_refresh(struct sysmon_envsys *,
165 					      envsys_data_t *);
166 
167 CFATTACH_DECL_NEW(hpacel, sizeof(struct hpacel_softc),
168     hpacel_match, hpacel_attach, hpacel_detach, NULL);
169 
170 static int
hpacel_match(device_t parent,cfdata_t match,void * aux)171 hpacel_match(device_t parent, cfdata_t match, void *aux)
172 {
173 	struct acpi_attach_args *aa = aux;
174 
175 	return acpi_compatible_match(aa, compat_data);
176 }
177 
178 static void
hpacel_attach(device_t parent,device_t self,void * aux)179 hpacel_attach(device_t parent, device_t self, void *aux)
180 {
181 	struct hpacel_softc *sc = device_private(self);
182 	struct acpi_attach_args *aa = aux;
183 
184 	sc->sc_sme = NULL;
185 	sc->sc_dev = self;
186 	sc->sc_state = false;
187 	sc->sc_node = aa->aa_node;
188 
189 	aprint_naive("\n");
190 	aprint_normal(": HP 3D DriveGuard accelerometer\n");
191 
192 	if (hpacel_reg_init(self) != true)
193 		return;
194 
195 	(void)pmf_device_register(self, hpacel_suspend, hpacel_resume);
196 
197 	if (hpacel_sensor_init(self) != false)
198 		(void)hpacel_power(self, true);
199 
200 	sc->sc_state = true;
201 }
202 
203 static int
hpacel_detach(device_t self,int flags)204 hpacel_detach(device_t self, int flags)
205 {
206 	struct hpacel_softc *sc = device_private(self);
207 
208 	if (sc->sc_state != false)
209 		(void)hpacel_power(self, false);
210 
211 	if (sc->sc_sme != NULL)
212 		sysmon_envsys_unregister(sc->sc_sme);
213 
214 	return 0;
215 }
216 
217 static bool
hpacel_suspend(device_t self,const pmf_qual_t * qual)218 hpacel_suspend(device_t self, const pmf_qual_t *qual)
219 {
220 	struct hpacel_softc *sc = device_private(self);
221 
222 	if (sc->sc_state != false)
223 		(void)hpacel_power(self, false);
224 
225 	return true;
226 }
227 
228 static bool
hpacel_resume(device_t self,const pmf_qual_t * qual)229 hpacel_resume(device_t self, const pmf_qual_t *qual)
230 {
231 	struct hpacel_softc *sc = device_private(self);
232 
233 	if (sc->sc_state != false)
234 		(void)hpacel_power(self, true);
235 
236 	return true;
237 }
238 
239 static bool
hpacel_reg_init(device_t self)240 hpacel_reg_init(device_t self)
241 {
242 	struct hpacel_softc *sc = device_private(self);
243 	ACPI_HANDLE hdl = sc->sc_node->ad_handle;
244 	ACPI_STATUS rv;
245 	uint8_t val;
246 
247 	rv = AcpiEvaluateObject(hdl, "_INI", NULL, NULL);
248 
249 	if (ACPI_FAILURE(rv))
250 		goto out;
251 
252         /*
253 	 * Since the "_INI" is practically
254 	 * a black box, it is better to verify
255 	 * the control registers manually.
256 	 */
257 	rv = hpacel_reg_info(self);
258 
259 	if (ACPI_FAILURE(rv))
260 		goto out;
261 
262 	val = sc->sc_ctrl[0];
263 
264 	if ((sc->sc_ctrl[0] & CTRL1_Xen) == 0)
265 		val |= CTRL1_Xen;
266 
267 	if ((sc->sc_ctrl[0] & CTRL1_Yen) == 0)
268 		val |= CTRL1_Yen;
269 
270 	if ((sc->sc_ctrl[0] & CTRL1_Zen) == 0)
271 		val |= CTRL1_Zen;
272 
273 	if (val != sc->sc_ctrl[0]) {
274 
275 		rv = hpacel_reg_write(hdl, CTRL_REG1, val);
276 
277 		if (ACPI_FAILURE(rv))
278 			return rv;
279 	}
280 
281         val = sc->sc_ctrl[1];
282 
283 	if ((sc->sc_ctrl[1] & CTRL2_BDU) == 0)
284 		val |= CTRL2_BDU;
285 
286 	if ((sc->sc_ctrl[1] & CTRL2_BLE) != 0)
287 		val &= ~CTRL2_BLE;
288 
289 	if ((sc->sc_ctrl[1] & CTRL2_DAS) != 0)
290 		val &= ~CTRL2_DAS;
291 
292 	/*
293 	 * Given the use of sysmon_envsys(9),
294 	 * there is no need for the data-ready pin.
295 	 */
296 	if ((sc->sc_ctrl[1] & CTRL2_DRDY) != 0)
297 		val &= ~CTRL2_DRDY;
298 
299 	/*
300 	 * Disable interrupt mode.
301 	 */
302 	if ((sc->sc_ctrl[1] & CTRL2_IEN) != 0)
303 		val &= ~CTRL2_IEN;
304 
305 	if (val != sc->sc_ctrl[1]) {
306 
307 		rv = hpacel_reg_write(hdl, CTRL_REG2, val);
308 
309 		if (ACPI_FAILURE(rv))
310 			return rv;
311 	}
312 
313 	/*
314 	 * Clear possible interrupt setups from
315 	 * the direction-detection register and
316 	 * from the free-fall-wake-up register.
317 	 */
318 	(void)hpacel_reg_write(hdl, DD_CFG, 0x00);
319 	(void)hpacel_reg_write(hdl, FF_WU_CFG, 0x00);
320 
321 	/*
322 	 * Update the register information.
323 	 */
324 	(void)hpacel_reg_info(self);
325 
326 out:
327 	if (ACPI_FAILURE(rv))
328 		aprint_error_dev(self, "failed to initialize "
329 		    "device: %s\n", AcpiFormatException(rv));
330 
331 	return (rv != AE_OK) ? false : true;
332 }
333 
334 static ACPI_STATUS
hpacel_reg_info(device_t self)335 hpacel_reg_info(device_t self)
336 {
337 	struct hpacel_softc *sc = device_private(self);
338 	ACPI_HANDLE hdl = sc->sc_node->ad_handle;
339 	ACPI_STATUS rv;
340 	size_t i;
341 
342 	rv = hpacel_reg_read(hdl, WHO_AM_I, &sc->sc_whoami);
343 
344 	if (ACPI_FAILURE(rv))
345 		return rv;
346 
347 	for (i = 0; i < __arraycount(sc->sc_sensor); i++) {
348 
349 		rv = hpacel_reg_read(hdl, CTRL_REG1 + i, &sc->sc_ctrl[i]);
350 
351 		if (ACPI_FAILURE(rv))
352 			return rv;
353 	}
354 
355 	return AE_OK;
356 }
357 
358 static ACPI_STATUS
hpacel_reg_read(ACPI_HANDLE hdl,ACPI_INTEGER reg,uint8_t * valp)359 hpacel_reg_read(ACPI_HANDLE hdl, ACPI_INTEGER reg, uint8_t *valp)
360 {
361 	ACPI_OBJECT_LIST arg;
362 	ACPI_OBJECT obj, val;
363 	ACPI_BUFFER buf;
364 	ACPI_STATUS rv;
365 
366 	obj.Type = ACPI_TYPE_INTEGER;
367 	obj.Integer.Value = reg;
368 
369 	buf.Pointer = &val;
370 	buf.Length = sizeof(val);
371 
372 	arg.Count = 1;
373 	arg.Pointer = &obj;
374 
375 	rv = AcpiEvaluateObjectTyped(hdl, "ALRD",
376 	    &arg, &buf, ACPI_TYPE_INTEGER);
377 
378 	if (ACPI_FAILURE(rv))
379 		return rv;
380 
381 	if (val.Integer.Value > UINT8_MAX)
382 		return AE_AML_NUMERIC_OVERFLOW;
383 
384 	*valp = val.Integer.Value;
385 
386 	return AE_OK;
387 }
388 
389 static ACPI_STATUS
hpacel_reg_write(ACPI_HANDLE hdl,ACPI_INTEGER reg,uint8_t val)390 hpacel_reg_write(ACPI_HANDLE hdl, ACPI_INTEGER reg, uint8_t val)
391 {
392 	ACPI_OBJECT_LIST arg;
393 	ACPI_OBJECT obj[2];
394 
395 	obj[0].Type = obj[1].Type = ACPI_TYPE_INTEGER;
396 
397 	obj[0].Integer.Value = reg;
398 	obj[1].Integer.Value = val;
399 
400 	arg.Count = 2;
401 	arg.Pointer = obj;
402 
403 	return AcpiEvaluateObject(hdl, "ALWR", &arg, NULL);
404 }
405 
406 static ACPI_STATUS
hpacel_reg_xyz(ACPI_HANDLE hdl,const int xyz,int16_t * out)407 hpacel_reg_xyz(ACPI_HANDLE hdl, const int xyz, int16_t *out)
408 {
409 	ACPI_INTEGER reg[2];
410 	ACPI_STATUS rv[2];
411 	uint8_t hi, lo;
412 
413 	switch (xyz) {
414 
415 	case HPACEL_SENSOR_X:
416 		reg[0] = OUTX_L;
417 		reg[1] = OUTX_H;
418 		break;
419 
420 	case HPACEL_SENSOR_Y:
421 		reg[0] = OUTY_L;
422 		reg[1] = OUTY_H;
423 		break;
424 
425 	case HPACEL_SENSOR_Z:
426 		reg[0] = OUTZ_L;
427 		reg[1] = OUTZ_H;
428 		break;
429 
430 	default:
431 		return AE_BAD_PARAMETER;
432 	}
433 
434 	rv[0] = hpacel_reg_read(hdl, reg[0], &lo);
435 	rv[1] = hpacel_reg_read(hdl, reg[1], &hi);
436 
437 	if (ACPI_FAILURE(rv[0]) || ACPI_FAILURE(rv[1]))
438 		return AE_ERROR;
439 
440 	/*
441 	 * These registers are read in "12 bit right
442 	 * justified mode", meaning that the four
443 	 * most significant bits are replaced with
444 	 * the value of bit 12. Note the signed type.
445 	 */
446 	hi = (hi & 0x10) ? hi | 0xE0 : hi & ~0xE0;
447 
448 	*out = (hi << 8) | lo;
449 
450 	return AE_OK;
451 }
452 
453 static ACPI_STATUS
hpacel_power(device_t self,bool enable)454 hpacel_power(device_t self, bool enable)
455 {
456 	struct hpacel_softc *sc = device_private(self);
457 	ACPI_HANDLE hdl = sc->sc_node->ad_handle;
458 	ACPI_OBJECT_LIST arg;
459 	ACPI_OBJECT obj;
460 	ACPI_STATUS rv;
461 	uint8_t val;
462 
463 	rv = hpacel_reg_info(self);
464 
465 	if (ACPI_FAILURE(rv))
466 		return rv;
467 
468 	val = sc->sc_ctrl[0];
469 
470 	if (enable != false)
471 		val |= CTRL1_PD0 | CTRL1_PD1;
472 	else {
473 		val &= ~(CTRL1_PD0 | CTRL1_PD1);
474 	}
475 
476 	if (val != sc->sc_ctrl[0]) {
477 
478 		rv = hpacel_reg_write(hdl, CTRL_REG1, val);
479 
480 		if (ACPI_FAILURE(rv))
481 			return rv;
482 	}
483 
484 	obj.Type = ACPI_TYPE_INTEGER;
485 	obj.Integer.Value = enable;
486 
487 	arg.Count = 1;
488 	arg.Pointer = &obj;
489 
490 	/*
491 	 * This should turn on/off a led, if available.
492 	 */
493 	(void)AcpiEvaluateObject(hdl, "ALED", &arg, NULL);
494 
495 	return rv;
496 }
497 
498 static bool
hpacel_sensor_init(device_t self)499 hpacel_sensor_init(device_t self)
500 {
501 	const char zyx[HPACEL_SENSOR_COUNT] = { 'x', 'y', 'z' };
502 	struct hpacel_softc *sc = device_private(self);
503 	size_t i;
504 	int rv;
505 
506 	CTASSERT(HPACEL_SENSOR_X == 0);
507 	CTASSERT(HPACEL_SENSOR_Y == 1);
508 	CTASSERT(HPACEL_SENSOR_Z == 2);
509 
510 	sc->sc_sme = sysmon_envsys_create();
511 
512 	for (i = 0; i < __arraycount(sc->sc_sensor); i++) {
513 
514 		sc->sc_sensor[i].units = ENVSYS_INTEGER;
515 		sc->sc_sensor[i].state = ENVSYS_SINVALID;
516 		sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
517 
518 		(void)snprintf(sc->sc_sensor[i].desc,
519 		    ENVSYS_DESCLEN, "%c-acceleration", zyx[i]);
520 
521 		rv = sysmon_envsys_sensor_attach(sc->sc_sme,&sc->sc_sensor[i]);
522 
523 		if (rv != 0)
524 			goto fail;
525 	}
526 
527 	/*
528 	 * We only do polling, given the hopelessly
529 	 * slow way of reading registers with ACPI.
530 	 */
531 	sc->sc_sme->sme_cookie = sc;
532 	sc->sc_sme->sme_flags = SME_POLL_ONLY;
533 	sc->sc_sme->sme_name = device_xname(self);
534 	sc->sc_sme->sme_refresh = hpacel_sensor_refresh;
535 
536 	rv = sysmon_envsys_register(sc->sc_sme);
537 
538 	if (rv != 0)
539 		goto fail;
540 
541 	return true;
542 
543 fail:
544 	aprint_error_dev(self, "failed to initialize sensors\n");
545 
546 	sysmon_envsys_destroy(sc->sc_sme);
547 	sc->sc_sme = NULL;
548 
549 	return false;
550 }
551 
552 static void
hpacel_sensor_refresh(struct sysmon_envsys * sme,envsys_data_t * edata)553 hpacel_sensor_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
554 {
555         struct hpacel_softc *sc = sme->sme_cookie;
556 	ACPI_STATUS rv;
557 	int16_t val;
558 	size_t i;
559 
560 	for (i = 0; i < __arraycount(sc->sc_sensor); i++) {
561 
562 		rv = hpacel_reg_xyz(sc->sc_node->ad_handle, i, &val);
563 
564 		if (ACPI_SUCCESS(rv)) {
565 			sc->sc_sensor[i].value_cur = val;
566 			sc->sc_sensor[i].state = ENVSYS_SVALID;
567 			continue;
568 		}
569 
570 		sc->sc_sensor[i].state = ENVSYS_SINVALID;
571 	}
572 }
573 
574 MODULE(MODULE_CLASS_DRIVER, hpacel, "sysmon_envsys");
575 
576 #ifdef _MODULE
577 #include "ioconf.c"
578 #endif
579 
580 static int
hpacel_modcmd(modcmd_t cmd,void * aux)581 hpacel_modcmd(modcmd_t cmd, void *aux)
582 {
583 	int rv = 0;
584 
585 	switch (cmd) {
586 
587 	case MODULE_CMD_INIT:
588 
589 #ifdef _MODULE
590 		rv = config_init_component(cfdriver_ioconf_hpacel,
591 		    cfattach_ioconf_hpacel, cfdata_ioconf_hpacel);
592 #endif
593 		break;
594 
595 	case MODULE_CMD_FINI:
596 
597 #ifdef _MODULE
598 		rv = config_fini_component(cfdriver_ioconf_hpacel,
599 		    cfattach_ioconf_hpacel, cfdata_ioconf_hpacel);
600 #endif
601 		break;
602 
603 	default:
604 		rv = ENOTTY;
605 	}
606 
607 	return rv;
608 }
609