1 1.6 riastrad /* $NetBSD: imc.c,v 1.6 2023/05/10 00:07:49 riastradh Exp $ */ 2 1.1 pgoyette 3 1.1 pgoyette /*- 4 1.1 pgoyette * Copyright (c) 2018 The NetBSD Foundation, Inc. 5 1.1 pgoyette * All rights reserved. 6 1.1 pgoyette * 7 1.1 pgoyette * This code is derived from software contributed to The NetBSD Foundation 8 1.1 pgoyette * by Paul Goyette 9 1.1 pgoyette * 10 1.1 pgoyette * Redistribution and use in source and binary forms, with or without 11 1.1 pgoyette * modification, are permitted provided that the following conditions 12 1.1 pgoyette * are met: 13 1.1 pgoyette * 1. Redistributions of source code must retain the above copyright 14 1.1 pgoyette * notice, this list of conditions and the following disclaimer. 15 1.1 pgoyette * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 pgoyette * notice, this list of conditions and the following disclaimer in the 17 1.1 pgoyette * documentation and/or other materials provided with the distribution. 18 1.1 pgoyette * 19 1.1 pgoyette * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 pgoyette * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 pgoyette * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 pgoyette * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 pgoyette * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 pgoyette * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 pgoyette * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 pgoyette * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 pgoyette * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 pgoyette * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 pgoyette * POSSIBILITY OF SUCH DAMAGE. 30 1.1 pgoyette */ 31 1.1 pgoyette 32 1.1 pgoyette /*- 33 1.1 pgoyette * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 34 1.1 pgoyette * 35 1.1 pgoyette * Authors: Joe Kloss; Ravi Pokala (rpokala (at) freebsd.org) 36 1.1 pgoyette * 37 1.1 pgoyette * Copyright (c) 2017-2018 Panasas 38 1.1 pgoyette * All rights reserved. 39 1.1 pgoyette * 40 1.1 pgoyette * Redistribution and use in source and binary forms, with or without 41 1.1 pgoyette * modification, are permitted provided that the following conditions 42 1.1 pgoyette * are met: 43 1.1 pgoyette * 1. Redistributions of source code must retain the above copyright 44 1.1 pgoyette * notice, this list of conditions and the following disclaimer. 45 1.1 pgoyette * 2. Redistributions in binary form must reproduce the above copyright 46 1.1 pgoyette * notice, this list of conditions and the following disclaimer in the 47 1.1 pgoyette * documentation and/or other materials provided with the distribution. 48 1.1 pgoyette * 49 1.1 pgoyette * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 50 1.1 pgoyette * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 51 1.1 pgoyette * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 52 1.1 pgoyette * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 53 1.1 pgoyette * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 54 1.1 pgoyette * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 55 1.1 pgoyette * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 56 1.1 pgoyette * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 57 1.1 pgoyette * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 58 1.1 pgoyette * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 59 1.1 pgoyette * SUCH DAMAGE. 60 1.1 pgoyette */ 61 1.1 pgoyette 62 1.1 pgoyette /* 63 1.1 pgoyette * Driver to expose the SMBus controllers in Intel's Integrated 64 1.1 pgoyette * Memory Controllers in certain CPUs. 65 1.1 pgoyette */ 66 1.1 pgoyette 67 1.1 pgoyette #include <sys/cdefs.h> 68 1.6 riastrad __KERNEL_RCSID(0, "$NetBSD: imc.c,v 1.6 2023/05/10 00:07:49 riastradh Exp $"); 69 1.1 pgoyette 70 1.1 pgoyette #include <sys/param.h> 71 1.1 pgoyette #include <sys/kernel.h> 72 1.1 pgoyette #include <sys/module.h> 73 1.1 pgoyette #include <sys/errno.h> 74 1.1 pgoyette #include <sys/mutex.h> 75 1.1 pgoyette #include <sys/bus.h> 76 1.1 pgoyette 77 1.1 pgoyette #include <dev/pci/pcidevs.h> 78 1.1 pgoyette #include <dev/pci/pcivar.h> 79 1.1 pgoyette #include <dev/pci/pcireg.h> 80 1.1 pgoyette 81 1.1 pgoyette #include "imcsmb_reg.h" 82 1.1 pgoyette #include "imcsmb_var.h" 83 1.1 pgoyette 84 1.1 pgoyette #include "ioconf.h" 85 1.1 pgoyette 86 1.1 pgoyette /* (Sandy,Ivy)bridge-Xeon and (Has,Broad)well-Xeon CPUs contain one or two 87 1.1 pgoyette * "Integrated Memory Controllers" (iMCs), and each iMC contains two separate 88 1.1 pgoyette * SMBus controllers. These are used for reading SPD data from the DIMMs, and 89 1.1 pgoyette * for reading the "Thermal Sensor on DIMM" (TSODs). The iMC SMBus controllers 90 1.1 pgoyette * are very simple devices, and have limited functionality compared to 91 1.1 pgoyette * full-fledged SMBus controllers, like the one in Intel ICHs and PCHs. 92 1.1 pgoyette * 93 1.1 pgoyette * The publicly available documentation for the iMC SMBus controllers can be 94 1.1 pgoyette * found in the CPU datasheets for (Sandy,Ivy)bridge-Xeon and 95 1.1 pgoyette * (Has,broad)well-Xeon, respectively: 96 1.1 pgoyette * 97 1.1 pgoyette * https://www.intel.com/content/dam/www/public/us/en/documents/datasheets/ 98 1.1 pgoyette * Sandybridge xeon-e5-1600-2600-vol-2-datasheet.pdf 99 1.1 pgoyette * Ivybridge xeon-e5-v2-datasheet-vol-2.pdf 100 1.1 pgoyette * Haswell xeon-e5-v3-datasheet-vol-2.pdf 101 1.1 pgoyette * Broadwell xeon-e5-v4-datasheet-vol-2.pdf 102 1.1 pgoyette * 103 1.1 pgoyette * Another useful resource is the Linux driver. It is not in the main tree. 104 1.1 pgoyette * 105 1.1 pgoyette * https://www.mail-archive.com/linux-kernel@vger.kernel.org/msg840043.html 106 1.1 pgoyette * 107 1.1 pgoyette * The iMC SMBus controllers do not support interrupts (thus, they must be 108 1.1 pgoyette * polled for IO completion). All of the iMC registers are in PCI configuration 109 1.1 pgoyette * space; there is no support for PIO or MMIO. As a result, this driver does 110 1.1 pgoyette * not need to perform and newbus resource manipulation. 111 1.1 pgoyette * 112 1.1 pgoyette * Because there are multiple SMBus controllers sharing the same PCI device, 113 1.1 pgoyette * this driver is actually *two* drivers: 114 1.1 pgoyette * 115 1.1 pgoyette * - "imcsmb" is an smbus(4)-compliant SMBus controller driver 116 1.1 pgoyette * 117 1.1 pgoyette * - "imcsmb_pci" recognizes the PCI device and assigns the appropriate set of 118 1.1 pgoyette * PCI config registers to a specific "imcsmb" instance. 119 1.1 pgoyette */ 120 1.1 pgoyette 121 1.1 pgoyette /* Depending on the motherboard and firmware, the TSODs might be polled by 122 1.1 pgoyette * firmware. Therefore, when this driver accesses these SMBus controllers, the 123 1.1 pgoyette * firmware polling must be disabled as part of requesting the bus, and 124 1.1 pgoyette * re-enabled when releasing the bus. Unfortunately, the details of how to do 125 1.1 pgoyette * this are vendor-specific. Contact your motherboard vendor to get the 126 1.1 pgoyette * information you need to do proper implementations. 127 1.1 pgoyette * 128 1.1 pgoyette * For NVDIMMs which conform to the ACPI "NFIT" standard, the ACPI firmware 129 1.1 pgoyette * manages the NVDIMM; for those which pre-date the standard, the operating 130 1.1 pgoyette * system interacts with the NVDIMM controller using a vendor-proprietary API 131 1.1 pgoyette * over the SMBus. In that case, the NVDIMM driver would be an SMBus slave 132 1.1 pgoyette * device driver, and would interface with the hardware via an SMBus controller 133 1.1 pgoyette * driver such as this one. 134 1.1 pgoyette */ 135 1.1 pgoyette 136 1.1 pgoyette /* PCIe device IDs for (Sandy,Ivy)bridge)-Xeon and (Has,Broad)well-Xeon */ 137 1.1 pgoyette 138 1.1 pgoyette #define IMCSMB_PCI_DEV_ID_IMC0_SBX 0x3ca8 139 1.1 pgoyette #define IMCSMB_PCI_DEV_ID_IMC0_IBX 0x0ea8 140 1.5 msaitoh #define IMCSMB_PCI_DEV_ID_IMC0_HSX PCI_PRODUCT_INTEL_XE5_V3_IMC0_TATRR 141 1.1 pgoyette #define IMCSMB_PCI_DEV_ID_IMC0_BDX PCI_PRODUCT_INTEL_XEOND_MEM_0_TTR_1 142 1.1 pgoyette 143 1.1 pgoyette /* (Sandy,Ivy)bridge-Xeon only have a single memory controller per socket */ 144 1.1 pgoyette 145 1.5 msaitoh #define IMCSMB_PCI_DEV_ID_IMC1_HSX PCI_PRODUCT_INTEL_XE5_V3_IMC1_TATRR 146 1.1 pgoyette #define IMCSMB_PCI_DEV_ID_IMC1_BDX PCI_PRODUCT_INTEL_COREI76K_IMC_0 147 1.1 pgoyette 148 1.1 pgoyette /* There are two SMBus controllers in each device. These define the registers 149 1.1 pgoyette * for each of these devices. 150 1.1 pgoyette */ 151 1.1 pgoyette static struct imcsmb_reg_set imcsmb_regs[] = { 152 1.1 pgoyette { 153 1.1 pgoyette .smb_stat = IMCSMB_REG_STATUS0, 154 1.1 pgoyette .smb_cmd = IMCSMB_REG_COMMAND0, 155 1.1 pgoyette .smb_cntl = IMCSMB_REG_CONTROL0 156 1.1 pgoyette }, 157 1.1 pgoyette { 158 1.1 pgoyette .smb_stat = IMCSMB_REG_STATUS1, 159 1.1 pgoyette .smb_cmd = IMCSMB_REG_COMMAND1, 160 1.1 pgoyette .smb_cntl = IMCSMB_REG_CONTROL1 161 1.1 pgoyette }, 162 1.1 pgoyette }; 163 1.1 pgoyette 164 1.1 pgoyette static struct imcsmb_pci_device { 165 1.1 pgoyette uint16_t id; 166 1.1 pgoyette const char *name; 167 1.1 pgoyette } imcsmb_pci_devices[] = { 168 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC0_SBX, 169 1.1 pgoyette "Intel Sandybridge Xeon iMC 0 SMBus controllers" }, 170 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC0_IBX, 171 1.1 pgoyette "Intel Ivybridge Xeon iMC 0 SMBus controllers" }, 172 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC0_HSX, 173 1.1 pgoyette "Intel Haswell Xeon iMC 0 SMBus controllers" }, 174 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC1_HSX, 175 1.1 pgoyette "Intel Haswell Xeon iMC 1 SMBus controllers" }, 176 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC0_BDX, 177 1.1 pgoyette "Intel Broadwell Xeon iMC 0 SMBus controllers" }, 178 1.1 pgoyette {IMCSMB_PCI_DEV_ID_IMC1_BDX, 179 1.1 pgoyette "Intel Broadwell Xeon iMC 1 SMBus controllers" }, 180 1.1 pgoyette {0, NULL}, 181 1.1 pgoyette }; 182 1.1 pgoyette 183 1.1 pgoyette /* Device methods. */ 184 1.1 pgoyette static void imc_attach(device_t, device_t, void *); 185 1.1 pgoyette static int imc_rescan(device_t, const char *, const int *); 186 1.1 pgoyette static int imc_detach(device_t, int); 187 1.1 pgoyette static int imc_probe(device_t, cfdata_t, void *); 188 1.1 pgoyette static void imc_chdet(device_t, device_t); 189 1.1 pgoyette 190 1.1 pgoyette CFATTACH_DECL3_NEW(imc, sizeof(struct imc_softc), 191 1.1 pgoyette imc_probe, imc_attach, imc_detach, NULL, imc_rescan, imc_chdet, 0); 192 1.1 pgoyette 193 1.1 pgoyette /** 194 1.1 pgoyette * device_attach() method. Set up the PCI device's softc, then explicitly create 195 1.1 pgoyette * children for the actual imcsmbX controllers. Set up the child's ivars to 196 1.1 pgoyette * point to the proper set of the PCI device's config registers. Finally, probe 197 1.1 pgoyette * and attach anything which might be downstream. 198 1.1 pgoyette * 199 1.1 pgoyette * @author Joe Kloss, rpokala 200 1.1 pgoyette * 201 1.1 pgoyette * @param[in,out] dev 202 1.1 pgoyette * Device being attached. 203 1.1 pgoyette */ 204 1.1 pgoyette static void 205 1.1 pgoyette imc_attach(device_t parent, device_t self, void *aux) 206 1.1 pgoyette { 207 1.1 pgoyette struct imc_softc *sc = device_private(self); 208 1.1 pgoyette struct pci_attach_args *pa = aux; 209 1.3 thorpej int i; 210 1.1 pgoyette 211 1.1 pgoyette sc->sc_dev = self; 212 1.1 pgoyette sc->sc_pci_tag = pa->pa_tag; 213 1.1 pgoyette sc->sc_pci_chipset_tag = pa->pa_pc; 214 1.1 pgoyette 215 1.1 pgoyette pci_aprint_devinfo(pa, NULL); 216 1.1 pgoyette 217 1.1 pgoyette for (i = 0; imcsmb_pci_devices[i].id != 0; i++) { 218 1.1 pgoyette if (PCI_PRODUCT(pa->pa_id) == imcsmb_pci_devices[i].id) { 219 1.1 pgoyette aprint_normal_dev(self, "%s\n", 220 1.1 pgoyette imcsmb_pci_devices[i].name); 221 1.1 pgoyette break; 222 1.1 pgoyette } 223 1.1 pgoyette } 224 1.1 pgoyette 225 1.1 pgoyette if (!pmf_device_register(self, NULL, NULL)) 226 1.1 pgoyette aprint_error_dev(self, "couldn't establish power handler\n"); 227 1.1 pgoyette 228 1.3 thorpej imc_rescan(self, NULL, NULL); 229 1.1 pgoyette } 230 1.1 pgoyette 231 1.1 pgoyette /* Create the imcsmbX children */ 232 1.1 pgoyette 233 1.1 pgoyette static int 234 1.3 thorpej imc_rescan(device_t self, const char *ifattr, const int *locs) 235 1.1 pgoyette { 236 1.1 pgoyette struct imc_softc *sc = device_private(self); 237 1.1 pgoyette struct imc_attach_args imca; 238 1.1 pgoyette int unit; 239 1.1 pgoyette 240 1.1 pgoyette for (unit = 0; unit < 2; unit++) { 241 1.1 pgoyette if (sc->sc_smbchild[unit] != NULL) 242 1.1 pgoyette continue; 243 1.1 pgoyette 244 1.1 pgoyette imca.ia_unit = unit; 245 1.1 pgoyette imca.ia_regs = &imcsmb_regs[unit]; 246 1.1 pgoyette imca.ia_pci_tag = sc->sc_pci_tag; 247 1.1 pgoyette imca.ia_pci_chipset_tag = sc->sc_pci_chipset_tag; 248 1.3 thorpej sc->sc_smbchild[unit] = 249 1.4 thorpej config_found(self, &imca, NULL, CFARGS_NONE); 250 1.1 pgoyette } 251 1.1 pgoyette 252 1.1 pgoyette return 0; 253 1.1 pgoyette } 254 1.1 pgoyette 255 1.1 pgoyette /* 256 1.1 pgoyette * device_detach() method. attach() didn't do any allocations, so there's 257 1.1 pgoyette * nothing special needed 258 1.1 pgoyette */ 259 1.1 pgoyette static int 260 1.1 pgoyette imc_detach(device_t self, int flags) 261 1.1 pgoyette { 262 1.6 riastrad int error; 263 1.1 pgoyette 264 1.6 riastrad error = config_detach_children(self, flags); 265 1.6 riastrad if (error) 266 1.6 riastrad return error; 267 1.1 pgoyette 268 1.1 pgoyette pmf_device_deregister(self); 269 1.1 pgoyette return 0; 270 1.1 pgoyette } 271 1.1 pgoyette 272 1.1 pgoyette /** 273 1.1 pgoyette * device_probe() method. Look for the right PCI vendor/device IDs. 274 1.1 pgoyette * 275 1.1 pgoyette * @author Joe Kloss, rpokala 276 1.1 pgoyette * 277 1.1 pgoyette * @param[in,out] dev 278 1.1 pgoyette * Device being probed. 279 1.1 pgoyette */ 280 1.1 pgoyette static int 281 1.1 pgoyette imc_probe(device_t dev, cfdata_t cf, void *aux) 282 1.1 pgoyette { 283 1.1 pgoyette struct pci_attach_args *pa = aux; 284 1.1 pgoyette 285 1.1 pgoyette if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_INTEL) { 286 1.1 pgoyette switch(PCI_PRODUCT(pa->pa_id)) { 287 1.1 pgoyette case PCI_PRODUCT_INTEL_COREI76K_IMC_0: 288 1.1 pgoyette case PCI_PRODUCT_INTEL_XEOND_MEM_0_TTR_1: 289 1.5 msaitoh case PCI_PRODUCT_INTEL_XE5_V3_IMC0_TATRR: 290 1.5 msaitoh case PCI_PRODUCT_INTEL_XE5_V3_IMC1_TATRR: 291 1.1 pgoyette case PCI_PRODUCT_INTEL_E5_IMC_TA: 292 1.1 pgoyette case PCI_PRODUCT_INTEL_E5V2_IMC_TA: 293 1.1 pgoyette return 1; 294 1.1 pgoyette } 295 1.1 pgoyette } 296 1.1 pgoyette return 0; 297 1.1 pgoyette } 298 1.1 pgoyette 299 1.1 pgoyette /* 300 1.1 pgoyette * child_detach() method 301 1.1 pgoyette */ 302 1.1 pgoyette static void 303 1.1 pgoyette imc_chdet(device_t self, device_t child) 304 1.1 pgoyette { 305 1.1 pgoyette struct imc_softc *sc = device_private(self); 306 1.1 pgoyette int unit; 307 1.1 pgoyette 308 1.1 pgoyette for (unit = 0; unit < 2; unit++) 309 1.1 pgoyette if (sc->sc_smbchild[unit] == child) 310 1.1 pgoyette sc->sc_smbchild[unit] = NULL; 311 1.1 pgoyette return; 312 1.1 pgoyette } 313 1.1 pgoyette 314 1.1 pgoyette /* 315 1.1 pgoyette * bios/motherboard access control 316 1.1 pgoyette * 317 1.1 pgoyette * XXX 318 1.1 pgoyette * If necessary, add the code here to prevent concurrent access to the 319 1.1 pgoyette * IMC controllers. The softc argument is for the imcsmb child device 320 1.1 pgoyette * (for the specific i2cbus instance); if you need to disable all 321 1.1 pgoyette * i2cbus instances on a given IMC (or all instances on all IMCs), you 322 1.1 pgoyette * may need to examine the softc's parent. 323 1.1 pgoyette * XXX 324 1.1 pgoyette */ 325 1.1 pgoyette 326 1.1 pgoyette kmutex_t imc_access_mutex; 327 1.1 pgoyette static int imc_access_count = 0; 328 1.1 pgoyette 329 1.1 pgoyette void 330 1.1 pgoyette imc_callback(struct imcsmb_softc *sc, imc_bios_control action) 331 1.1 pgoyette { 332 1.1 pgoyette 333 1.1 pgoyette mutex_enter(&imc_access_mutex); 334 1.1 pgoyette switch (action) { 335 1.1 pgoyette case IMC_BIOS_ENABLE: 336 1.1 pgoyette imc_access_count--; 337 1.1 pgoyette if (imc_access_count == 0) { 338 1.1 pgoyette /* 339 1.1 pgoyette * Insert motherboard-specific enable code here! 340 1.1 pgoyette */ 341 1.1 pgoyette } 342 1.1 pgoyette break; 343 1.1 pgoyette case IMC_BIOS_DISABLE: 344 1.1 pgoyette if (imc_access_count == 0) { 345 1.1 pgoyette /* 346 1.1 pgoyette * Insert motherboard-specific disable code here! 347 1.1 pgoyette */ 348 1.1 pgoyette } 349 1.1 pgoyette imc_access_count++; 350 1.1 pgoyette break; 351 1.1 pgoyette } 352 1.1 pgoyette mutex_exit(&imc_access_mutex); 353 1.1 pgoyette } 354 1.1 pgoyette 355 1.1 pgoyette MODULE(MODULE_CLASS_DRIVER, imc, "pci"); 356 1.1 pgoyette 357 1.1 pgoyette #ifdef _MODULE 358 1.1 pgoyette #include "ioconf.c" 359 1.1 pgoyette #endif 360 1.1 pgoyette 361 1.1 pgoyette static int 362 1.1 pgoyette imc_modcmd(modcmd_t cmd, void *opaque) 363 1.1 pgoyette { 364 1.1 pgoyette int error = 0; 365 1.1 pgoyette 366 1.1 pgoyette switch (cmd) { 367 1.1 pgoyette case MODULE_CMD_INIT: 368 1.1 pgoyette mutex_init(&imc_access_mutex, MUTEX_DEFAULT, IPL_NONE); 369 1.1 pgoyette #ifdef _MODULE 370 1.1 pgoyette error = config_init_component(cfdriver_ioconf_imc, 371 1.1 pgoyette cfattach_ioconf_imc, cfdata_ioconf_imc); 372 1.1 pgoyette #endif 373 1.1 pgoyette break; 374 1.1 pgoyette 375 1.1 pgoyette case MODULE_CMD_FINI: 376 1.1 pgoyette #ifdef _MODULE 377 1.1 pgoyette error = config_fini_component(cfdriver_ioconf_imc, 378 1.1 pgoyette cfattach_ioconf_imc, cfdata_ioconf_imc); 379 1.1 pgoyette #endif 380 1.1 pgoyette if (error == 0) 381 1.1 pgoyette mutex_destroy(&imc_access_mutex); 382 1.1 pgoyette break; 383 1.1 pgoyette default: 384 1.1 pgoyette error = ENOTTY; 385 1.1 pgoyette break; 386 1.1 pgoyette } 387 1.1 pgoyette 388 1.1 pgoyette return error; 389 1.1 pgoyette } 390