1 | /* $NetBSD: i2c.c,v 1.54 2016/07/23 18:00:01 jakllsch Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2003 Wasabi Systems, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * Written by Jason R. Thorpe for Wasabi Systems, Inc. |
8 | * |
9 | * Redistribution and use in source and binary forms, with or without |
10 | * modification, are permitted provided that the following conditions |
11 | * are met: |
12 | * 1. Redistributions of source code must retain the above copyright |
13 | * notice, this list of conditions and the following disclaimer. |
14 | * 2. Redistributions in binary form must reproduce the above copyright |
15 | * notice, this list of conditions and the following disclaimer in the |
16 | * documentation and/or other materials provided with the distribution. |
17 | * 3. All advertising materials mentioning features or use of this software |
18 | * must display the following acknowledgement: |
19 | * This product includes software developed for the NetBSD Project by |
20 | * Wasabi Systems, Inc. |
21 | * 4. The name of Wasabi Systems, Inc. may not be used to endorse |
22 | * or promote products derived from this software without specific prior |
23 | * written permission. |
24 | * |
25 | * THIS SOFTWARE IS PROVIDED BY WASABI SYSTEMS, INC. ``AS IS'' AND |
26 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
27 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
28 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL WASABI SYSTEMS, INC |
29 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
30 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
31 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
32 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
33 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
34 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
35 | * POSSIBILITY OF SUCH DAMAGE. |
36 | */ |
37 | |
38 | #ifdef _KERNEL_OPT |
39 | #include "opt_i2c.h" |
40 | #endif |
41 | |
42 | #include <sys/cdefs.h> |
43 | __KERNEL_RCSID(0, "$NetBSD: i2c.c,v 1.54 2016/07/23 18:00:01 jakllsch Exp $" ); |
44 | |
45 | #include <sys/param.h> |
46 | #include <sys/systm.h> |
47 | #include <sys/device.h> |
48 | #include <sys/event.h> |
49 | #include <sys/conf.h> |
50 | #include <sys/malloc.h> |
51 | #include <sys/kmem.h> |
52 | #include <sys/kthread.h> |
53 | #include <sys/proc.h> |
54 | #include <sys/kernel.h> |
55 | #include <sys/fcntl.h> |
56 | #include <sys/module.h> |
57 | #include <sys/once.h> |
58 | #include <sys/mutex.h> |
59 | |
60 | #include <dev/i2c/i2cvar.h> |
61 | |
62 | #include "locators.h" |
63 | |
64 | #ifndef I2C_MAX_ADDR |
65 | #define I2C_MAX_ADDR 0x3ff /* 10-bit address, max */ |
66 | #endif |
67 | |
68 | struct iic_softc { |
69 | i2c_tag_t sc_tag; |
70 | int sc_type; |
71 | device_t sc_devices[I2C_MAX_ADDR + 1]; |
72 | }; |
73 | |
74 | static dev_type_open(iic_open); |
75 | static dev_type_close(iic_close); |
76 | static dev_type_ioctl(iic_ioctl); |
77 | |
78 | int iic_init(void); |
79 | |
80 | kmutex_t iic_mtx; |
81 | int iic_refcnt; |
82 | |
83 | ONCE_DECL(iic_once); |
84 | |
85 | const struct cdevsw iic_cdevsw = { |
86 | .d_open = iic_open, |
87 | .d_close = iic_close, |
88 | .d_read = noread, |
89 | .d_write = nowrite, |
90 | .d_ioctl = iic_ioctl, |
91 | .d_stop = nostop, |
92 | .d_tty = notty, |
93 | .d_poll = nopoll, |
94 | .d_mmap = nommap, |
95 | .d_kqfilter = nokqfilter, |
96 | .d_discard = nodiscard, |
97 | .d_flag = D_OTHER |
98 | }; |
99 | |
100 | extern struct cfdriver iic_cd; |
101 | |
102 | static void iic_smbus_intr_thread(void *); |
103 | static void iic_fill_compat(struct i2c_attach_args*, const char*, |
104 | size_t, char **); |
105 | |
106 | static int |
107 | iic_print_direct(void *aux, const char *pnp) |
108 | { |
109 | struct i2c_attach_args *ia = aux; |
110 | |
111 | if (pnp != NULL) |
112 | aprint_normal("%s at %s addr 0x%02x" , ia->ia_name, pnp, |
113 | ia->ia_addr); |
114 | else |
115 | aprint_normal(" addr 0x%02x" , ia->ia_addr); |
116 | |
117 | return UNCONF; |
118 | } |
119 | |
120 | static int |
121 | iic_print(void *aux, const char *pnp) |
122 | { |
123 | struct i2c_attach_args *ia = aux; |
124 | |
125 | if (ia->ia_addr != (i2c_addr_t)-1) |
126 | aprint_normal(" addr 0x%x" , ia->ia_addr); |
127 | |
128 | return UNCONF; |
129 | } |
130 | |
131 | static int |
132 | iic_search(device_t parent, cfdata_t cf, const int *ldesc, void *aux) |
133 | { |
134 | struct iic_softc *sc = device_private(parent); |
135 | struct i2c_attach_args ia; |
136 | |
137 | ia.ia_tag = sc->sc_tag; |
138 | ia.ia_size = cf->cf_loc[IICCF_SIZE]; |
139 | ia.ia_type = sc->sc_type; |
140 | |
141 | ia.ia_name = NULL; |
142 | ia.ia_ncompat = 0; |
143 | ia.ia_compat = NULL; |
144 | |
145 | for (ia.ia_addr = 0; ia.ia_addr <= I2C_MAX_ADDR; ia.ia_addr++) { |
146 | if (sc->sc_devices[ia.ia_addr] != NULL) |
147 | continue; |
148 | |
149 | if (cf->cf_loc[IICCF_ADDR] != -1 && |
150 | cf->cf_loc[IICCF_ADDR] != ia.ia_addr) |
151 | continue; |
152 | |
153 | if (config_match(parent, cf, &ia) > 0) |
154 | sc->sc_devices[ia.ia_addr] = |
155 | config_attach(parent, cf, &ia, iic_print); |
156 | } |
157 | |
158 | return 0; |
159 | } |
160 | |
161 | static void |
162 | iic_child_detach(device_t parent, device_t child) |
163 | { |
164 | struct iic_softc *sc = device_private(parent); |
165 | int i; |
166 | |
167 | for (i = 0; i <= I2C_MAX_ADDR; i++) |
168 | if (sc->sc_devices[i] == child) { |
169 | sc->sc_devices[i] = NULL; |
170 | break; |
171 | } |
172 | } |
173 | |
174 | static int |
175 | iic_rescan(device_t self, const char *ifattr, const int *locators) |
176 | { |
177 | config_search_ia(iic_search, self, ifattr, NULL); |
178 | return 0; |
179 | } |
180 | |
181 | static int |
182 | iic_match(device_t parent, cfdata_t cf, void *aux) |
183 | { |
184 | |
185 | return 1; |
186 | } |
187 | |
188 | static void |
189 | iic_attach(device_t parent, device_t self, void *aux) |
190 | { |
191 | struct iic_softc *sc = device_private(self); |
192 | struct i2cbus_attach_args *iba = aux; |
193 | prop_array_t child_devices; |
194 | prop_dictionary_t props; |
195 | char *buf; |
196 | i2c_tag_t ic; |
197 | int rv; |
198 | bool indirect_config; |
199 | |
200 | aprint_naive("\n" ); |
201 | aprint_normal(": I2C bus\n" ); |
202 | |
203 | sc->sc_tag = iba->iba_tag; |
204 | sc->sc_type = iba->iba_type; |
205 | ic = sc->sc_tag; |
206 | ic->ic_devname = device_xname(self); |
207 | |
208 | LIST_INIT(&(sc->sc_tag->ic_list)); |
209 | LIST_INIT(&(sc->sc_tag->ic_proc_list)); |
210 | |
211 | rv = kthread_create(PRI_NONE, KTHREAD_MUSTJOIN, NULL, |
212 | iic_smbus_intr_thread, ic, &ic->ic_intr_thread, |
213 | "%s" , ic->ic_devname); |
214 | if (rv) |
215 | aprint_error_dev(self, "unable to create intr thread\n" ); |
216 | |
217 | if (!pmf_device_register(self, NULL, NULL)) |
218 | aprint_error_dev(self, "couldn't establish power handler\n" ); |
219 | |
220 | if (iba->iba_child_devices) { |
221 | child_devices = iba->iba_child_devices; |
222 | indirect_config = false; |
223 | } else { |
224 | props = device_properties(parent); |
225 | if (!prop_dictionary_get_bool(props, "i2c-indirect-config" , |
226 | &indirect_config)) |
227 | indirect_config = true; |
228 | child_devices = prop_dictionary_get(props, "i2c-child-devices" ); |
229 | } |
230 | |
231 | if (child_devices) { |
232 | unsigned int i, count; |
233 | prop_dictionary_t dev; |
234 | prop_data_t cdata; |
235 | uint32_t addr, size; |
236 | uint64_t cookie; |
237 | const char *name; |
238 | struct i2c_attach_args ia; |
239 | int loc[IICCF_NLOCS]; |
240 | |
241 | memset(loc, 0, sizeof loc); |
242 | count = prop_array_count(child_devices); |
243 | for (i = 0; i < count; i++) { |
244 | dev = prop_array_get(child_devices, i); |
245 | if (!dev) continue; |
246 | if (!prop_dictionary_get_cstring_nocopy( |
247 | dev, "name" , &name)) |
248 | continue; |
249 | if (!prop_dictionary_get_uint32(dev, "addr" , &addr)) |
250 | continue; |
251 | if (!prop_dictionary_get_uint64(dev, "cookie" , &cookie)) |
252 | cookie = 0; |
253 | loc[IICCF_ADDR] = addr; |
254 | if (prop_dictionary_get_uint32(dev, "size" , &size)) |
255 | loc[IICCF_SIZE] = size; |
256 | else |
257 | size = loc[IICCF_SIZE] = IICCF_SIZE_DEFAULT; |
258 | |
259 | memset(&ia, 0, sizeof ia); |
260 | ia.ia_addr = addr; |
261 | ia.ia_type = sc->sc_type; |
262 | ia.ia_tag = ic; |
263 | ia.ia_name = name; |
264 | ia.ia_cookie = cookie; |
265 | ia.ia_size = size; |
266 | |
267 | buf = NULL; |
268 | cdata = prop_dictionary_get(dev, "compatible" ); |
269 | if (cdata) |
270 | iic_fill_compat(&ia, |
271 | prop_data_data_nocopy(cdata), |
272 | prop_data_size(cdata), &buf); |
273 | |
274 | if (addr > I2C_MAX_ADDR) { |
275 | aprint_error_dev(self, |
276 | "WARNING: ignoring bad device address " |
277 | "@ 0x%02x\n" , addr); |
278 | } else if (sc->sc_devices[addr] == NULL) { |
279 | sc->sc_devices[addr] = |
280 | config_found_sm_loc(self, "iic" , loc, &ia, |
281 | iic_print_direct, NULL); |
282 | } |
283 | |
284 | if (ia.ia_compat) |
285 | free(ia.ia_compat, M_TEMP); |
286 | if (buf) |
287 | free(buf, M_TEMP); |
288 | } |
289 | } else if (indirect_config) { |
290 | /* |
291 | * Attach all i2c devices described in the kernel |
292 | * configuration file. |
293 | */ |
294 | iic_rescan(self, "iic" , NULL); |
295 | } |
296 | } |
297 | |
298 | static int |
299 | iic_detach(device_t self, int flags) |
300 | { |
301 | struct iic_softc *sc = device_private(self); |
302 | i2c_tag_t ic = sc->sc_tag; |
303 | int i, error; |
304 | void *hdl; |
305 | |
306 | for (i = 0; i <= I2C_MAX_ADDR; i++) { |
307 | if (sc->sc_devices[i]) { |
308 | error = config_detach(sc->sc_devices[i], flags); |
309 | if (error) |
310 | return error; |
311 | } |
312 | } |
313 | |
314 | if (ic->ic_running) { |
315 | ic->ic_running = 0; |
316 | wakeup(ic); |
317 | kthread_join(ic->ic_intr_thread); |
318 | } |
319 | |
320 | if (!LIST_EMPTY(&ic->ic_list)) { |
321 | device_printf(self, "WARNING: intr handler list not empty\n" ); |
322 | while (!LIST_EMPTY(&ic->ic_list)) { |
323 | hdl = LIST_FIRST(&ic->ic_list); |
324 | iic_smbus_intr_disestablish(ic, hdl); |
325 | } |
326 | } |
327 | if (!LIST_EMPTY(&ic->ic_proc_list)) { |
328 | device_printf(self, "WARNING: proc handler list not empty\n" ); |
329 | while (!LIST_EMPTY(&ic->ic_proc_list)) { |
330 | hdl = LIST_FIRST(&ic->ic_proc_list); |
331 | iic_smbus_intr_disestablish_proc(ic, hdl); |
332 | } |
333 | } |
334 | |
335 | pmf_device_deregister(self); |
336 | |
337 | return 0; |
338 | } |
339 | |
340 | static void |
341 | iic_smbus_intr_thread(void *aux) |
342 | { |
343 | i2c_tag_t ic; |
344 | struct ic_intr_list *il; |
345 | |
346 | ic = (i2c_tag_t)aux; |
347 | ic->ic_running = 1; |
348 | ic->ic_pending = 0; |
349 | |
350 | while (ic->ic_running) { |
351 | if (ic->ic_pending == 0) |
352 | tsleep(ic, PZERO, "iicintr" , hz); |
353 | if (ic->ic_pending > 0) { |
354 | LIST_FOREACH(il, &(ic->ic_proc_list), il_next) { |
355 | (*il->il_intr)(il->il_intrarg); |
356 | } |
357 | ic->ic_pending--; |
358 | } |
359 | } |
360 | |
361 | kthread_exit(0); |
362 | } |
363 | |
364 | void * |
365 | iic_smbus_intr_establish(i2c_tag_t ic, int (*intr)(void *), void *intrarg) |
366 | { |
367 | struct ic_intr_list *il; |
368 | |
369 | il = malloc(sizeof(struct ic_intr_list), M_DEVBUF, M_WAITOK); |
370 | if (il == NULL) |
371 | return NULL; |
372 | |
373 | il->il_intr = intr; |
374 | il->il_intrarg = intrarg; |
375 | |
376 | LIST_INSERT_HEAD(&(ic->ic_list), il, il_next); |
377 | |
378 | return il; |
379 | } |
380 | |
381 | void |
382 | iic_smbus_intr_disestablish(i2c_tag_t ic, void *hdl) |
383 | { |
384 | struct ic_intr_list *il; |
385 | |
386 | il = (struct ic_intr_list *)hdl; |
387 | |
388 | LIST_REMOVE(il, il_next); |
389 | free(il, M_DEVBUF); |
390 | |
391 | return; |
392 | } |
393 | |
394 | void * |
395 | iic_smbus_intr_establish_proc(i2c_tag_t ic, int (*intr)(void *), void *intrarg) |
396 | { |
397 | struct ic_intr_list *il; |
398 | |
399 | il = malloc(sizeof(struct ic_intr_list), M_DEVBUF, M_WAITOK); |
400 | if (il == NULL) |
401 | return NULL; |
402 | |
403 | il->il_intr = intr; |
404 | il->il_intrarg = intrarg; |
405 | |
406 | LIST_INSERT_HEAD(&(ic->ic_proc_list), il, il_next); |
407 | |
408 | return il; |
409 | } |
410 | |
411 | void |
412 | iic_smbus_intr_disestablish_proc(i2c_tag_t ic, void *hdl) |
413 | { |
414 | struct ic_intr_list *il; |
415 | |
416 | il = (struct ic_intr_list *)hdl; |
417 | |
418 | LIST_REMOVE(il, il_next); |
419 | free(il, M_DEVBUF); |
420 | |
421 | return; |
422 | } |
423 | |
424 | int |
425 | iic_smbus_intr(i2c_tag_t ic) |
426 | { |
427 | struct ic_intr_list *il; |
428 | |
429 | LIST_FOREACH(il, &(ic->ic_list), il_next) { |
430 | (*il->il_intr)(il->il_intrarg); |
431 | } |
432 | |
433 | ic->ic_pending++; |
434 | wakeup(ic); |
435 | |
436 | return 1; |
437 | } |
438 | |
439 | static void |
440 | iic_fill_compat(struct i2c_attach_args *ia, const char *compat, size_t len, |
441 | char **buffer) |
442 | { |
443 | int count, i; |
444 | const char *c, *start, **ptr; |
445 | |
446 | *buffer = NULL; |
447 | for (i = count = 0, c = compat; i < len; i++, c++) |
448 | if (*c == 0) |
449 | count++; |
450 | count += 2; |
451 | ptr = malloc(sizeof(char*)*count, M_TEMP, M_WAITOK); |
452 | if (!ptr) return; |
453 | |
454 | for (i = count = 0, start = c = compat; i < len; i++, c++) { |
455 | if (*c == 0) { |
456 | ptr[count++] = start; |
457 | start = c+1; |
458 | } |
459 | } |
460 | if (start < compat+len) { |
461 | /* last string not 0 terminated */ |
462 | size_t l = c-start; |
463 | *buffer = malloc(l+1, M_TEMP, M_WAITOK); |
464 | memcpy(*buffer, start, l); |
465 | (*buffer)[l] = 0; |
466 | ptr[count++] = *buffer; |
467 | } |
468 | ptr[count] = NULL; |
469 | |
470 | ia->ia_compat = ptr; |
471 | ia->ia_ncompat = count; |
472 | } |
473 | |
474 | int |
475 | iic_compat_match(struct i2c_attach_args *ia, const char ** compats) |
476 | { |
477 | int i; |
478 | |
479 | for (; compats && *compats; compats++) { |
480 | for (i = 0; i < ia->ia_ncompat; i++) { |
481 | if (strcmp(*compats, ia->ia_compat[i]) == 0) |
482 | return 1; |
483 | } |
484 | } |
485 | return 0; |
486 | } |
487 | |
488 | static int |
489 | iic_open(dev_t dev, int flag, int fmt, lwp_t *l) |
490 | { |
491 | struct iic_softc *sc = device_lookup_private(&iic_cd, minor(dev)); |
492 | |
493 | mutex_enter(&iic_mtx); |
494 | if (sc == NULL) { |
495 | mutex_exit(&iic_mtx); |
496 | return ENXIO; |
497 | } |
498 | iic_refcnt++; |
499 | mutex_exit(&iic_mtx); |
500 | |
501 | return 0; |
502 | } |
503 | |
504 | static int |
505 | iic_close(dev_t dev, int flag, int fmt, lwp_t *l) |
506 | { |
507 | |
508 | mutex_enter(&iic_mtx); |
509 | iic_refcnt--; |
510 | mutex_exit(&iic_mtx); |
511 | |
512 | return 0; |
513 | } |
514 | |
515 | static int |
516 | iic_ioctl_exec(struct iic_softc *sc, i2c_ioctl_exec_t *iie, int flag) |
517 | { |
518 | i2c_tag_t ic = sc->sc_tag; |
519 | uint8_t buf[I2C_EXEC_MAX_BUFLEN]; |
520 | void *cmd = NULL; |
521 | int error; |
522 | |
523 | /* Validate parameters */ |
524 | if (iie->iie_addr > I2C_MAX_ADDR) |
525 | return EINVAL; |
526 | if (iie->iie_cmdlen > I2C_EXEC_MAX_CMDLEN || |
527 | iie->iie_buflen > I2C_EXEC_MAX_BUFLEN) |
528 | return EINVAL; |
529 | if (iie->iie_cmd != NULL && iie->iie_cmdlen == 0) |
530 | return EINVAL; |
531 | if (iie->iie_buf != NULL && iie->iie_buflen == 0) |
532 | return EINVAL; |
533 | if (I2C_OP_WRITE_P(iie->iie_op) && (flag & FWRITE) == 0) |
534 | return EBADF; |
535 | |
536 | #if 0 |
537 | /* Disallow userspace access to devices that have drivers attached. */ |
538 | if (sc->sc_devices[iie->iie_addr] != NULL) |
539 | return EBUSY; |
540 | #endif |
541 | |
542 | if (iie->iie_cmd != NULL) { |
543 | cmd = kmem_alloc(iie->iie_cmdlen, KM_SLEEP); |
544 | if (cmd == NULL) |
545 | return ENOMEM; |
546 | error = copyin(iie->iie_cmd, cmd, iie->iie_cmdlen); |
547 | if (error) |
548 | goto out; |
549 | } |
550 | |
551 | if (iie->iie_buf != NULL && I2C_OP_WRITE_P(iie->iie_op)) { |
552 | error = copyin(iie->iie_buf, buf, iie->iie_buflen); |
553 | if (error) |
554 | goto out; |
555 | } |
556 | |
557 | iic_acquire_bus(ic, 0); |
558 | error = iic_exec(ic, iie->iie_op, iie->iie_addr, cmd, iie->iie_cmdlen, |
559 | buf, iie->iie_buflen, 0); |
560 | iic_release_bus(ic, 0); |
561 | |
562 | /* |
563 | * Some drivers return error codes on failure, and others return -1. |
564 | */ |
565 | if (error < 0) |
566 | error = EIO; |
567 | |
568 | out: |
569 | if (cmd) |
570 | kmem_free(cmd, iie->iie_cmdlen); |
571 | |
572 | if (error) |
573 | return error; |
574 | |
575 | if (iie->iie_buf != NULL && I2C_OP_READ_P(iie->iie_op)) |
576 | error = copyout(buf, iie->iie_buf, iie->iie_buflen); |
577 | |
578 | return error; |
579 | } |
580 | |
581 | static int |
582 | iic_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l) |
583 | { |
584 | struct iic_softc *sc = device_lookup_private(&iic_cd, minor(dev)); |
585 | |
586 | if (sc == NULL) |
587 | return ENXIO; |
588 | |
589 | switch (cmd) { |
590 | case I2C_IOCTL_EXEC: |
591 | return iic_ioctl_exec(sc, (i2c_ioctl_exec_t *)data, flag); |
592 | default: |
593 | return ENODEV; |
594 | } |
595 | } |
596 | |
597 | |
598 | CFATTACH_DECL2_NEW(iic, sizeof(struct iic_softc), |
599 | iic_match, iic_attach, iic_detach, NULL, iic_rescan, iic_child_detach); |
600 | |
601 | MODULE(MODULE_CLASS_DRIVER, iic, "i2cexec,i2c_bitbang" ); |
602 | |
603 | #ifdef _MODULE |
604 | #include "ioconf.c" |
605 | #endif |
606 | |
607 | int |
608 | iic_init(void) |
609 | { |
610 | |
611 | mutex_init(&iic_mtx, MUTEX_DEFAULT, IPL_NONE); |
612 | iic_refcnt = 0; |
613 | return 0; |
614 | } |
615 | |
616 | static int |
617 | iic_modcmd(modcmd_t cmd, void *opaque) |
618 | { |
619 | #ifdef _MODULE |
620 | int bmajor, cmajor; |
621 | #endif |
622 | int error; |
623 | |
624 | error = 0; |
625 | switch (cmd) { |
626 | case MODULE_CMD_INIT: |
627 | RUN_ONCE(&iic_once, iic_init); |
628 | |
629 | #ifdef _MODULE |
630 | mutex_enter(&iic_mtx); |
631 | bmajor = cmajor = -1; |
632 | error = devsw_attach("iic" , NULL, &bmajor, |
633 | &iic_cdevsw, &cmajor); |
634 | if (error != 0) { |
635 | mutex_exit(&iic_mtx); |
636 | break; |
637 | } |
638 | error = config_init_component(cfdriver_ioconf_iic, |
639 | cfattach_ioconf_iic, cfdata_ioconf_iic); |
640 | if (error) { |
641 | aprint_error("%s: unable to init component\n" , |
642 | iic_cd.cd_name); |
643 | (void)devsw_detach(NULL, &iic_cdevsw); |
644 | } |
645 | mutex_exit(&iic_mtx); |
646 | #endif |
647 | break; |
648 | case MODULE_CMD_FINI: |
649 | mutex_enter(&iic_mtx); |
650 | if (iic_refcnt != 0) { |
651 | mutex_exit(&iic_mtx); |
652 | return EBUSY; |
653 | } |
654 | #ifdef _MODULE |
655 | error = config_fini_component(cfdriver_ioconf_iic, |
656 | cfattach_ioconf_iic, cfdata_ioconf_iic); |
657 | if (error != 0) { |
658 | mutex_exit(&iic_mtx); |
659 | break; |
660 | } |
661 | error = devsw_detach(NULL, &iic_cdevsw); |
662 | if (error != 0) |
663 | config_init_component(cfdriver_ioconf_iic, |
664 | cfattach_ioconf_iic, cfdata_ioconf_iic); |
665 | #endif |
666 | mutex_exit(&iic_mtx); |
667 | break; |
668 | default: |
669 | error = ENOTTY; |
670 | } |
671 | return error; |
672 | } |
673 | |