Home | History | Annotate | Line # | Download | only in isa
      1 /*	$NetBSD: aps.c,v 1.20 2025/02/17 22:56:46 andvar Exp $	*/
      2 /*	$OpenBSD: aps.c,v 1.15 2007/05/19 19:14:11 tedu Exp $	*/
      3 /*	$OpenBSD: aps.c,v 1.17 2008/06/27 06:08:43 canacar Exp $	*/
      4 /*
      5  * Copyright (c) 2005 Jonathan Gray <jsg (at) openbsd.org>
      6  * Copyright (c) 2008 Can Erkin Acar <canacar (at) openbsd.org>
      7  *
      8  * Permission to use, copy, modify, and distribute this software for any
      9  * purpose with or without fee is hereby granted, provided that the above
     10  * copyright notice and this permission notice appear in all copies.
     11  *
     12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     19  */
     20 
     21 /*
     22  * A driver for the ThinkPad Active Protection System based on notes from
     23  * http://www.almaden.ibm.com/cs/people/marksmith/tpaps.html
     24  */
     25 
     26 #include <sys/cdefs.h>
     27 __KERNEL_RCSID(0, "$NetBSD: aps.c,v 1.20 2025/02/17 22:56:46 andvar Exp $");
     28 
     29 #include <sys/param.h>
     30 #include <sys/systm.h>
     31 #include <sys/device.h>
     32 #include <sys/kernel.h>
     33 #include <sys/callout.h>
     34 #include <sys/module.h>
     35 
     36 #include <sys/bus.h>
     37 
     38 #include <dev/sysmon/sysmonvar.h>
     39 
     40 #include <dev/isa/isareg.h>
     41 #include <dev/isa/isavar.h>
     42 
     43 #if defined(APSDEBUG)
     44 #define DPRINTF(x)		do { printf x; } while (0)
     45 #else
     46 #define DPRINTF(x)
     47 #endif
     48 
     49 
     50 /*
     51  * EC interface on Thinkpad Laptops, from Linux HDAPS driver notes.
     52  * From Renesas H8S/2140B Group Hardware Manual
     53  * http://documentation.renesas.com/eng/products/mpumcu/rej09b0300_2140bhm.pdf
     54  *
     55  * EC uses LPC Channel 3 registers TWR0..15
     56  */
     57 
     58 /* STR3 status register */
     59 #define APS_STR3		0x04
     60 
     61 #define APS_STR3_IBF3B	0x80	/* Input buffer full (host->slave) */
     62 #define APS_STR3_OBF3B	0x40	/* Output buffer full (slave->host)*/
     63 #define APS_STR3_MWMF	0x20	/* Master write mode */
     64 #define APS_STR3_SWMF	0x10	/* Slave write mode */
     65 
     66 
     67 /* Base address of TWR registers */
     68 #define APS_TWR_BASE		0x10
     69 #define APS_TWR_RET		0x1f
     70 
     71 /* TWR registers */
     72 #define APS_CMD			0x00
     73 #define APS_ARG1		0x01
     74 #define APS_ARG2		0x02
     75 #define APS_ARG3		0x03
     76 #define APS_RET			0x0f
     77 
     78 /* Sensor values */
     79 #define APS_STATE		0x01
     80 #define	APS_XACCEL		0x02
     81 #define APS_YACCEL		0x04
     82 #define APS_TEMP		0x06
     83 #define	APS_XVAR		0x07
     84 #define APS_YVAR		0x09
     85 #define APS_TEMP2		0x0b
     86 #define APS_UNKNOWN		0x0c
     87 #define APS_INPUT		0x0d
     88 
     89 /* write masks for I/O, send command + 0-3 arguments*/
     90 #define APS_WRITE_0		0x0001
     91 #define APS_WRITE_1		0x0003
     92 #define APS_WRITE_2		0x0007
     93 #define APS_WRITE_3		0x000f
     94 
     95 /* read masks for I/O, read 0-3 values (skip command byte) */
     96 #define APS_READ_0		0x0000
     97 #define APS_READ_1		0x0002
     98 #define APS_READ_2		0x0006
     99 #define APS_READ_3		0x000e
    100 
    101 #define APS_READ_RET		0x8000
    102 #define APS_READ_ALL		0xffff
    103 
    104 /* Bit definitions for APS_INPUT value */
    105 #define APS_INPUT_KB		(1 << 5)
    106 #define APS_INPUT_MS		(1 << 6)
    107 #define APS_INPUT_LIDOPEN	(1 << 7)
    108 
    109 #define APS_ADDR_SIZE		0x1f
    110 
    111 struct sensor_rec {
    112 	uint8_t 	state;
    113 	uint16_t	x_accel;
    114 	uint16_t	y_accel;
    115 	uint8_t 	temp1;
    116 	uint16_t	x_var;
    117 	uint16_t	y_var;
    118 	uint8_t 	temp2;
    119 	uint8_t 	unk;
    120 	uint8_t 	input;
    121 };
    122 
    123 enum aps_sensors {
    124         APS_SENSOR_XACCEL = 0,
    125         APS_SENSOR_YACCEL,
    126         APS_SENSOR_XVAR,
    127         APS_SENSOR_YVAR,
    128         APS_SENSOR_TEMP1,
    129         APS_SENSOR_TEMP2,
    130         APS_SENSOR_KBACT,
    131         APS_SENSOR_MSACT,
    132         APS_SENSOR_LIDOPEN,
    133         APS_NUM_SENSORS
    134 };
    135 
    136 struct aps_softc {
    137 	bus_space_tag_t sc_iot;
    138 	bus_space_handle_t sc_ioh;
    139 	bool sc_bus_space_valid;
    140 
    141 	struct sysmon_envsys *sc_sme;
    142 	envsys_data_t sc_sensor[APS_NUM_SENSORS];
    143 	struct callout sc_callout;
    144 
    145 	struct sensor_rec aps_data;
    146 };
    147 
    148 static int 	aps_match(device_t, cfdata_t, void *);
    149 static void 	aps_attach(device_t, device_t, void *);
    150 static int	aps_detach(device_t, int);
    151 
    152 static int 	aps_init(struct aps_softc *);
    153 static int	aps_read_data(struct aps_softc *);
    154 static void 	aps_refresh_sensor_data(struct aps_softc *);
    155 static void 	aps_refresh(void *);
    156 static int	aps_do_io(bus_space_tag_t, bus_space_handle_t,
    157 			  unsigned char *, int, int);
    158 static bool 	aps_suspend(device_t, const pmf_qual_t *);
    159 static bool 	aps_resume(device_t, const pmf_qual_t *);
    160 
    161 CFATTACH_DECL_NEW(aps, sizeof(struct aps_softc),
    162 	      aps_match, aps_attach, aps_detach, NULL);
    163 
    164 /* properly communicate with the controller, writing a set of memory
    165  * locations and reading back another set  */
    166 static int
    167 aps_do_io(bus_space_tag_t iot, bus_space_handle_t ioh,
    168 	  unsigned char *buf, int wmask, int rmask)
    169 {
    170 	int bp, stat, n;
    171 
    172 	DPRINTF(("aps_do_io: CMD: 0x%02x, wmask: 0x%04x, rmask: 0x%04x\n",
    173 	    buf[0], wmask, rmask));
    174 
    175 	/* write init byte using arbitration */
    176 	for (n = 0; n < 100; n++) {
    177 		stat = bus_space_read_1(iot, ioh, APS_STR3);
    178 		if (stat & (APS_STR3_OBF3B | APS_STR3_SWMF)) {
    179 			bus_space_read_1(iot, ioh, APS_TWR_RET);
    180 			continue;
    181 		}
    182 		bus_space_write_1(iot, ioh, APS_TWR_BASE, buf[0]);
    183 		stat = bus_space_read_1(iot, ioh, APS_STR3);
    184 		if (stat & (APS_STR3_MWMF))
    185 			break;
    186 		delay(1);
    187 	}
    188 
    189 	if (n == 100) {
    190 		DPRINTF(("aps_do_io: Failed to get bus\n"));
    191 		return 1;
    192 	}
    193 
    194 	/* write data bytes, init already sent */
    195 	/* make sure last byte is always written as this will trigger slave */
    196 	wmask |= APS_READ_RET;
    197 	buf[APS_RET] = 0x01;
    198 
    199 	for (n = 1, bp = 2; n < 16; bp <<= 1, n++) {
    200 		if (wmask & bp) {
    201 			bus_space_write_1(iot, ioh, APS_TWR_BASE + n, buf[n]);
    202 			DPRINTF(("aps_do_io:  write %2d 0x%02x\n", n, buf[n]));
    203 		}
    204 	}
    205 
    206 	for (n = 0; n < 100; n++) {
    207 		stat = bus_space_read_1(iot, ioh, APS_STR3);
    208 		if (stat & (APS_STR3_OBF3B))
    209 			break;
    210 		delay(5 * 100);
    211 	}
    212 
    213 	if (n == 100) {
    214 		DPRINTF(("aps_do_io: timeout waiting response\n"));
    215 		return 1;
    216 	}
    217 	/* wait for data available */
    218 	/* make sure to read the final byte to clear status */
    219 	rmask |= APS_READ_RET;
    220 
    221 	/* read cmd and data bytes */
    222 	for (n = 0, bp = 1; n < 16; bp <<= 1, n++) {
    223 		if (rmask & bp) {
    224 			buf[n] = bus_space_read_1(iot, ioh, APS_TWR_BASE + n);
    225 			DPRINTF(("aps_do_io:  read %2d 0x%02x\n", n, buf[n]));
    226 		}
    227 	}
    228 
    229 	return 0;
    230 }
    231 
    232 static int
    233 aps_match(device_t parent, cfdata_t match, void *aux)
    234 {
    235 	struct isa_attach_args *ia = aux;
    236 	bus_space_tag_t iot = ia->ia_iot;
    237 	bus_space_handle_t ioh;
    238 	unsigned char iobuf[16];
    239 	int iobase;
    240 	uint8_t cr;
    241 
    242 	/* Must supply an address */
    243 	if (ia->ia_nio < 1)
    244 		return 0;
    245 
    246 	if (ISA_DIRECT_CONFIG(ia))
    247 		return 0;
    248 
    249 	if (ia->ia_io[0].ir_addr == ISA_UNKNOWN_PORT)
    250 		return 0;
    251 
    252 	iobase = ia->ia_io[0].ir_addr;
    253 
    254 	if (bus_space_map(iot, iobase, APS_ADDR_SIZE, 0, &ioh)) {
    255 		aprint_error("aps: can't map i/o space\n");
    256 		return 0;
    257 	}
    258 
    259 
    260 	/* See if this machine has APS */
    261 
    262 	/* get APS mode */
    263 	iobuf[APS_CMD] = 0x13;
    264 	if (aps_do_io(iot, ioh, iobuf, APS_WRITE_0, APS_READ_1)) {
    265 		bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
    266 		return 0;
    267 	}
    268 
    269 	/*
    270 	 * Observed values from Linux driver:
    271 	 * 0x01: T42
    272 	 * 0x02: chip already initialised
    273 	 * 0x03: T41
    274 	 * 0x05: T61
    275 	 */
    276 
    277 	cr = iobuf[APS_ARG1];
    278 
    279 	bus_space_unmap(iot, ioh, APS_ADDR_SIZE);
    280 	DPRINTF(("aps: state register 0x%x\n", cr));
    281 
    282 	if (iobuf[APS_RET] != 0 || cr < 1 || cr > 5) {
    283 		DPRINTF(("aps0: unsupported state %d\n", cr));
    284 		return 0;
    285 	}
    286 
    287 	ia->ia_nio = 1;
    288 	ia->ia_io[0].ir_size = APS_ADDR_SIZE;
    289 	ia->ia_niomem = 0;
    290 	ia->ia_nirq = 0;
    291 	ia->ia_ndrq = 0;
    292 
    293 	return 1;
    294 }
    295 
    296 static void
    297 aps_attach(device_t parent, device_t self, void *aux)
    298 {
    299 	struct aps_softc *sc = device_private(self);
    300 	struct isa_attach_args *ia = aux;
    301 	int iobase, i;
    302 
    303 	sc->sc_iot = ia->ia_iot;
    304 	iobase = ia->ia_io[0].ir_addr;
    305 
    306 	callout_init(&sc->sc_callout, 0);
    307 	callout_setfunc(&sc->sc_callout, aps_refresh, sc);
    308 
    309 	if (bus_space_map(sc->sc_iot, iobase, APS_ADDR_SIZE, 0, &sc->sc_ioh)) {
    310 		aprint_error(": can't map i/o space\n");
    311 		return;
    312 	}
    313 	sc->sc_bus_space_valid = true;
    314 
    315 	aprint_naive("\n");
    316 	aprint_normal(": Thinkpad Active Protection System\n");
    317 
    318 	if (aps_init(sc)) {
    319 		aprint_error_dev(self, "failed to initialize\n");
    320 		goto out;
    321 	}
    322 
    323 	/* Initialize sensors */
    324 #define INITDATA(idx, unit, string)					\
    325 	sc->sc_sensor[idx].units = unit;				\
    326 	strlcpy(sc->sc_sensor[idx].desc, string,			\
    327 	    sizeof(sc->sc_sensor[idx].desc));
    328 
    329 	INITDATA(APS_SENSOR_XACCEL, ENVSYS_INTEGER, "x-acceleration");
    330 	INITDATA(APS_SENSOR_YACCEL, ENVSYS_INTEGER, "y-acceleration");
    331 	INITDATA(APS_SENSOR_TEMP1, ENVSYS_STEMP, "temperature 1");
    332 	INITDATA(APS_SENSOR_TEMP2, ENVSYS_STEMP, "temperature 2");
    333 	INITDATA(APS_SENSOR_XVAR, ENVSYS_INTEGER, "x-variable");
    334 	INITDATA(APS_SENSOR_YVAR, ENVSYS_INTEGER, "y-variable");
    335 	INITDATA(APS_SENSOR_KBACT, ENVSYS_INDICATOR, "keyboard active");
    336 	INITDATA(APS_SENSOR_MSACT, ENVSYS_INDICATOR, "mouse active");
    337 	INITDATA(APS_SENSOR_LIDOPEN, ENVSYS_INDICATOR, "lid open");
    338 
    339 	sc->sc_sme = sysmon_envsys_create();
    340 
    341 	for (i = 0; i < APS_NUM_SENSORS; i++) {
    342 
    343 		sc->sc_sensor[i].state = ENVSYS_SVALID;
    344 
    345 		if (sc->sc_sensor[i].units == ENVSYS_INTEGER)
    346 			sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY;
    347 
    348 		if (sysmon_envsys_sensor_attach(sc->sc_sme,
    349 			&sc->sc_sensor[i])) {
    350 			sysmon_envsys_destroy(sc->sc_sme);
    351 			sc->sc_sme = NULL;
    352 			goto out;
    353 		}
    354 	}
    355         /*
    356          * Register with the sysmon_envsys(9) framework.
    357          */
    358 	sc->sc_sme->sme_name = device_xname(self);
    359 	sc->sc_sme->sme_flags = SME_DISABLE_REFRESH;
    360 
    361 	if ((i = sysmon_envsys_register(sc->sc_sme))) {
    362 		aprint_error_dev(self,
    363 		    "unable to register with sysmon (%d)\n", i);
    364 		sysmon_envsys_destroy(sc->sc_sme);
    365 		sc->sc_sme = NULL;
    366 		goto out;
    367 	}
    368 
    369 	if (!pmf_device_register(self, aps_suspend, aps_resume))
    370 		aprint_error_dev(self, "couldn't establish power handler\n");
    371 
    372 	/* Refresh sensor data every 0.5 seconds */
    373 	callout_schedule(&sc->sc_callout, (hz) / 2);
    374 
    375 	return;
    376 
    377 out:
    378 	bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
    379 }
    380 
    381 static int
    382 aps_init(struct aps_softc *sc)
    383 {
    384 	unsigned char iobuf[16];
    385 
    386 	/* command 0x17/0x81: check EC */
    387 	iobuf[APS_CMD] = 0x17;
    388 	iobuf[APS_ARG1] = 0x81;
    389 
    390 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_3))
    391 		return 1;
    392 	if (iobuf[APS_RET] != 0 ||iobuf[APS_ARG3] != 0)
    393 		return 1;
    394 
    395 	/* Test values from the Linux driver */
    396 	if ((iobuf[APS_ARG1] != 0 || iobuf[APS_ARG2] != 0x60) &&
    397 	    (iobuf[APS_ARG1] != 1 || iobuf[APS_ARG2] != 0))
    398 		return 1;
    399 
    400 	/* command 0x14: set power */
    401 	iobuf[APS_CMD] = 0x14;
    402 	iobuf[APS_ARG1] = 0x01;
    403 
    404 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_1, APS_READ_0))
    405 		return 1;
    406 
    407 	if (iobuf[APS_RET] != 0)
    408 		return 1;
    409 
    410 	/* command 0x10: set config (sample rate and order) */
    411 	iobuf[APS_CMD] = 0x10;
    412 	iobuf[APS_ARG1] = 0xc8;
    413 	iobuf[APS_ARG2] = 0x00;
    414 	iobuf[APS_ARG3] = 0x02;
    415 
    416 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_3, APS_READ_0))
    417 		return 1;
    418 
    419 	/* command 0x11: refresh data */
    420 	iobuf[APS_CMD] = 0x11;
    421 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1))
    422 		return 1;
    423 	if (iobuf[APS_ARG1] != 0)
    424 		return 1;
    425 
    426 	return 0;
    427 }
    428 
    429 static int
    430 aps_detach(device_t self, int flags)
    431 {
    432 	struct aps_softc *sc = device_private(self);
    433 
    434         callout_halt(&sc->sc_callout, NULL);
    435         callout_destroy(&sc->sc_callout);
    436 
    437 	if (sc->sc_sme)
    438 		sysmon_envsys_unregister(sc->sc_sme);
    439 	if (sc->sc_bus_space_valid == true)
    440 		bus_space_unmap(sc->sc_iot, sc->sc_ioh, APS_ADDR_SIZE);
    441 
    442 	return 0;
    443 }
    444 
    445 static int
    446 aps_read_data(struct aps_softc *sc)
    447 {
    448 	unsigned char iobuf[16];
    449 
    450 	iobuf[APS_CMD] = 0x11;
    451 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_ALL))
    452 		return 1;
    453 
    454 	sc->aps_data.state = iobuf[APS_STATE];
    455 	sc->aps_data.x_accel = iobuf[APS_XACCEL] + 256 * iobuf[APS_XACCEL + 1];
    456 	sc->aps_data.y_accel = iobuf[APS_YACCEL] + 256 * iobuf[APS_YACCEL + 1];
    457 	sc->aps_data.temp1 = iobuf[APS_TEMP];
    458 	sc->aps_data.x_var = iobuf[APS_XVAR] + 256 * iobuf[APS_XVAR + 1];
    459 	sc->aps_data.y_var = iobuf[APS_YVAR] + 256 * iobuf[APS_YVAR + 1];
    460 	sc->aps_data.temp2 = iobuf[APS_TEMP2];
    461 	sc->aps_data.input = iobuf[APS_INPUT];
    462 
    463 	return 0;
    464 }
    465 
    466 static void
    467 aps_refresh_sensor_data(struct aps_softc *sc)
    468 {
    469 	int64_t temp;
    470 
    471 	if (aps_read_data(sc)) {
    472 		printf("aps0: read data failed\n");
    473 		return;
    474 	}
    475 
    476 	sc->sc_sensor[APS_SENSOR_XACCEL].value_cur = sc->aps_data.x_accel;
    477 	sc->sc_sensor[APS_SENSOR_YACCEL].value_cur = sc->aps_data.y_accel;
    478 
    479 	if (sc->aps_data.temp1 == 0xff)
    480 		sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SINVALID;
    481 	else {
    482 		/* convert to micro (mu) degrees */
    483 		temp = sc->aps_data.temp1 * 1000000;
    484 		/* convert to kelvin */
    485 		temp += 273150000;
    486 		sc->sc_sensor[APS_SENSOR_TEMP1].value_cur = temp;
    487 		sc->sc_sensor[APS_SENSOR_TEMP1].state = ENVSYS_SVALID;
    488 	}
    489 
    490 	if (sc->aps_data.temp2 == 0xff)
    491 		sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SINVALID;
    492 	else {
    493 		/* convert to micro (mu) degrees */
    494 		temp = sc->aps_data.temp2 * 1000000;
    495 		/* convert to kelvin */
    496 		temp += 273150000;
    497 		sc->sc_sensor[APS_SENSOR_TEMP2].value_cur = temp;
    498 		sc->sc_sensor[APS_SENSOR_TEMP2].state = ENVSYS_SVALID;
    499 	}
    500 
    501 	sc->sc_sensor[APS_SENSOR_XVAR].value_cur = sc->aps_data.x_var;
    502 	sc->sc_sensor[APS_SENSOR_YVAR].value_cur = sc->aps_data.y_var;
    503 	sc->sc_sensor[APS_SENSOR_KBACT].value_cur =
    504 	    (sc->aps_data.input &  APS_INPUT_KB) ? 1 : 0;
    505 	sc->sc_sensor[APS_SENSOR_MSACT].value_cur =
    506 	    (sc->aps_data.input & APS_INPUT_MS) ? 1 : 0;
    507 	sc->sc_sensor[APS_SENSOR_LIDOPEN].value_cur =
    508 	    (sc->aps_data.input & APS_INPUT_LIDOPEN) ? 1 : 0;
    509 }
    510 
    511 static void
    512 aps_refresh(void *arg)
    513 {
    514 	struct aps_softc *sc = arg;
    515 
    516 	aps_refresh_sensor_data(sc);
    517 	callout_schedule(&sc->sc_callout, (hz) / 2);
    518 }
    519 
    520 static bool
    521 aps_suspend(device_t dv, const pmf_qual_t *qual)
    522 {
    523 	struct aps_softc *sc = device_private(dv);
    524 
    525 	callout_stop(&sc->sc_callout);
    526 
    527 	return true;
    528 }
    529 
    530 static bool
    531 aps_resume(device_t dv, const pmf_qual_t *qual)
    532 {
    533 	struct aps_softc *sc = device_private(dv);
    534 	unsigned char iobuf[16];
    535 
    536 	/*
    537 	 * Redo the init sequence on resume, because APS is
    538 	 * as forgetful as it is deaf.
    539 	 */
    540 
    541 	/* get APS mode */
    542 	iobuf[APS_CMD] = 0x13;
    543 	if (aps_do_io(sc->sc_iot, sc->sc_ioh, iobuf, APS_WRITE_0, APS_READ_1)
    544 	    || aps_init(sc))
    545 		aprint_error_dev(dv, "failed to wake up\n");
    546 	else
    547 		callout_schedule(&sc->sc_callout, (hz) / 2);
    548 
    549 	return true;
    550 }
    551 
    552 MODULE(MODULE_CLASS_DRIVER, aps, "sysmon_envsys");
    553 
    554 #ifdef _MODULE
    555 #include "ioconf.c"
    556 #endif
    557 
    558 static int
    559 aps_modcmd(modcmd_t cmd, void *opaque)
    560 {
    561 	switch (cmd) {
    562 	case MODULE_CMD_INIT:
    563 #ifdef _MODULE
    564 		return config_init_component(cfdriver_ioconf_aps,
    565 		    cfattach_ioconf_aps, cfdata_ioconf_aps);
    566 #else
    567 		return 0;
    568 #endif
    569 	case MODULE_CMD_FINI:
    570 #ifdef _MODULE
    571 		return config_fini_component(cfdriver_ioconf_aps,
    572 		    cfattach_ioconf_aps, cfdata_ioconf_aps);
    573 #else
    574 		return 0;
    575 #endif
    576 	default:
    577 		return ENOTTY;
    578 	}
    579 }
    580