1 | /* $NetBSD: amdtemp.c,v 1.19 2015/04/23 23:23:00 pgoyette Exp $ */ |
2 | /* $OpenBSD: kate.c,v 1.2 2008/03/27 04:52:03 cnst Exp $ */ |
3 | |
4 | /* |
5 | * Copyright (c) 2008 The NetBSD Foundation, Inc. |
6 | * All rights reserved. |
7 | * |
8 | * This code is derived from software contributed to The NetBSD Foundation |
9 | * by Christoph Egger. |
10 | * |
11 | * Redistribution and use in source and binary forms, with or without |
12 | * modification, are permitted provided that the following conditions |
13 | * are met: |
14 | * 1. Redistributions of source code must retain the above copyright |
15 | * notice, this list of conditions and the following disclaimer. |
16 | * 2. Redistributions in binary form must reproduce the above copyright |
17 | * notice, this list of conditions and the following disclaimer in the |
18 | * documentation and/or other materials provided with the distribution. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
21 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
24 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
30 | * POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | /* |
34 | * Copyright (c) 2008 Constantine A. Murenin <cnst+openbsd@bugmail.mojo.ru> |
35 | * |
36 | * Permission to use, copy, modify, and distribute this software for any |
37 | * purpose with or without fee is hereby granted, provided that the above |
38 | * copyright notice and this permission notice appear in all copies. |
39 | * |
40 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
41 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
42 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
43 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
44 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
45 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
46 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
47 | */ |
48 | |
49 | |
50 | #include <sys/cdefs.h> |
51 | __KERNEL_RCSID(0, "$NetBSD: amdtemp.c,v 1.19 2015/04/23 23:23:00 pgoyette Exp $ " ); |
52 | |
53 | #include <sys/param.h> |
54 | #include <sys/bus.h> |
55 | #include <sys/cpu.h> |
56 | #include <sys/systm.h> |
57 | #include <sys/device.h> |
58 | #include <sys/kmem.h> |
59 | #include <sys/module.h> |
60 | |
61 | #include <machine/specialreg.h> |
62 | |
63 | #include <dev/pci/pcireg.h> |
64 | #include <dev/pci/pcivar.h> |
65 | #include <dev/pci/pcidevs.h> |
66 | |
67 | #include <dev/sysmon/sysmonvar.h> |
68 | |
69 | /* |
70 | * AMD K8: |
71 | * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/32559.pdf |
72 | * AMD K8 Errata: #141 |
73 | * http://support.amd.com/us/Processor_TechDocs/33610_PUB_Rev3%2042v3.pdf |
74 | * |
75 | * Family10h: |
76 | * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/31116.PDF |
77 | * Family10h Errata: #319 |
78 | * http://support.amd.com/de/Processor_TechDocs/41322.pdf |
79 | * |
80 | * Family11h: |
81 | * http://www.amd.com/us-en/assets/content_type/white_papers_and_tech_docs/41256.pdf |
82 | */ |
83 | |
84 | /* AMD Processors, Function 3 -- Miscellaneous Control |
85 | */ |
86 | |
87 | /* Function 3 Registers */ |
88 | #define THERMTRIP_STAT_R 0xe4 |
89 | #define NORTHBRIDGE_CAP_R 0xe8 |
90 | #define CPUID_FAMILY_MODEL_R 0xfc |
91 | |
92 | /* |
93 | * AMD NPT Family 0Fh Processors, Function 3 -- Miscellaneous Control |
94 | */ |
95 | |
96 | /* Bits within Thermtrip Status Register */ |
97 | #define K8_THERM_SENSE_SEL (1 << 6) |
98 | #define K8_THERM_SENSE_CORE_SEL (1 << 2) |
99 | |
100 | /* Flip core and sensor selection bits */ |
101 | #define K8_T_SEL_C0(v) (v |= K8_THERM_SENSE_CORE_SEL) |
102 | #define K8_T_SEL_C1(v) (v &= ~(K8_THERM_SENSE_CORE_SEL)) |
103 | #define K8_T_SEL_S0(v) (v &= ~(K8_THERM_SENSE_SEL)) |
104 | #define K8_T_SEL_S1(v) (v |= K8_THERM_SENSE_SEL) |
105 | |
106 | |
107 | |
108 | /* |
109 | * AMD Family 10h Processors, Function 3 -- Miscellaneous Control |
110 | */ |
111 | |
112 | /* Function 3 Registers */ |
113 | #define F10_TEMPERATURE_CTL_R 0xa4 |
114 | |
115 | /* Bits within Reported Temperature Control Register */ |
116 | #define F10_TEMP_CURTEMP (1 << 21) |
117 | |
118 | /* |
119 | * Revision Guide for AMD NPT Family 0Fh Processors, |
120 | * Publication # 33610, Revision 3.30, February 2008 |
121 | */ |
122 | #define K8_SOCKET_F 1 /* Server */ |
123 | #define K8_SOCKET_AM2 2 /* Desktop */ |
124 | #define K8_SOCKET_S1 3 /* Laptop */ |
125 | |
126 | static const struct { |
127 | const char rev[5]; |
128 | const struct { |
129 | const pcireg_t cpuid; |
130 | const uint8_t socket; |
131 | } cpu[5]; |
132 | } amdtemp_core[] = { |
133 | { "BH-F" , { { 0x00040FB0, K8_SOCKET_AM2 }, /* F2 */ |
134 | { 0x00040F80, K8_SOCKET_S1 }, /* F2 */ |
135 | { 0, 0 }, { 0, 0 }, { 0, 0 } } }, |
136 | { "DH-F" , { { 0x00040FF0, K8_SOCKET_AM2 }, /* F2 */ |
137 | { 0x00040FC0, K8_SOCKET_S1 }, /* F2 */ |
138 | { 0x00050FF0, K8_SOCKET_AM2 }, /* F2, F3 */ |
139 | { 0, 0 }, { 0, 0 } } }, |
140 | { "JH-F" , { { 0x00040F10, K8_SOCKET_F }, /* F2, F3 */ |
141 | { 0x00040F30, K8_SOCKET_AM2 }, /* F2, F3 */ |
142 | { 0x000C0F10, K8_SOCKET_F }, /* F3 */ |
143 | { 0, 0 }, { 0, 0 } } }, |
144 | { "BH-G" , { { 0x00060FB0, K8_SOCKET_AM2 }, /* G1, G2 */ |
145 | { 0x00060F80, K8_SOCKET_S1 }, /* G1, G2 */ |
146 | { 0, 0 }, { 0, 0 }, { 0, 0 } } }, |
147 | { "DH-G" , { { 0x00060FF0, K8_SOCKET_AM2 }, /* G1, G2 */ |
148 | { 0x00060FC0, K8_SOCKET_S1 }, /* G2 */ |
149 | { 0x00070FF0, K8_SOCKET_AM2 }, /* G1, G2 */ |
150 | { 0x00070FC0, K8_SOCKET_S1 }, /* G2 */ |
151 | { 0, 0 } } } |
152 | }; |
153 | |
154 | |
155 | struct amdtemp_softc { |
156 | pci_chipset_tag_t sc_pc; |
157 | pcitag_t sc_pcitag; |
158 | |
159 | struct sysmon_envsys *sc_sme; |
160 | envsys_data_t *sc_sensor; |
161 | size_t sc_sensor_len; |
162 | |
163 | char sc_rev; |
164 | int8_t sc_numsensors; |
165 | uint32_t sc_family; |
166 | int32_t sc_adjustment; |
167 | }; |
168 | |
169 | |
170 | static int amdtemp_match(device_t, cfdata_t, void *); |
171 | static void amdtemp_attach(device_t, device_t, void *); |
172 | static int amdtemp_detach(device_t, int); |
173 | |
174 | static void amdtemp_k8_init(struct amdtemp_softc *, pcireg_t); |
175 | static void amdtemp_k8_setup_sensors(struct amdtemp_softc *, int); |
176 | static void amdtemp_k8_refresh(struct sysmon_envsys *, envsys_data_t *); |
177 | |
178 | static void amdtemp_family10_init(struct amdtemp_softc *); |
179 | static void amdtemp_family10_setup_sensors(struct amdtemp_softc *, int); |
180 | static void amdtemp_family10_refresh(struct sysmon_envsys *, envsys_data_t *); |
181 | |
182 | CFATTACH_DECL_NEW(amdtemp, sizeof(struct amdtemp_softc), |
183 | amdtemp_match, amdtemp_attach, amdtemp_detach, NULL); |
184 | |
185 | static int |
186 | amdtemp_match(device_t parent, cfdata_t match, void *aux) |
187 | { |
188 | struct pci_attach_args *pa = aux; |
189 | pcireg_t cpu_signature; |
190 | uint32_t family; |
191 | |
192 | KASSERT(PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD); |
193 | |
194 | cpu_signature = pci_conf_read(pa->pa_pc, |
195 | pa->pa_tag, CPUID_FAMILY_MODEL_R); |
196 | |
197 | /* This CPUID northbridge register has been introduced |
198 | * in Revision F */ |
199 | if (cpu_signature == 0x0) |
200 | return 0; |
201 | |
202 | family = CPUID_TO_FAMILY(cpu_signature); |
203 | |
204 | /* Errata #319: This has been fixed in Revision C2. */ |
205 | if (family == 0x10) { |
206 | if (CPUID_TO_BASEMODEL(cpu_signature) < 4) |
207 | return 0; |
208 | if (CPUID_TO_BASEMODEL(cpu_signature) == 4 |
209 | && CPUID_TO_STEPPING(cpu_signature) < 2) |
210 | return 0; |
211 | } |
212 | |
213 | |
214 | /* Not yet supported CPUs */ |
215 | if (family > 0x15) |
216 | return 0; |
217 | |
218 | return 1; |
219 | } |
220 | |
221 | static void |
222 | amdtemp_attach(device_t parent, device_t self, void *aux) |
223 | { |
224 | struct amdtemp_softc *sc = device_private(self); |
225 | struct pci_attach_args *pa = aux; |
226 | pcireg_t cpu_signature; |
227 | int error; |
228 | uint8_t i; |
229 | |
230 | aprint_naive("\n" ); |
231 | aprint_normal(": AMD CPU Temperature Sensors" ); |
232 | |
233 | cpu_signature = pci_conf_read(pa->pa_pc, |
234 | pa->pa_tag, CPUID_FAMILY_MODEL_R); |
235 | |
236 | /* If we hit this, then match routine is wrong. */ |
237 | KASSERT(cpu_signature != 0x0); |
238 | |
239 | sc->sc_family = CPUID_TO_FAMILY(cpu_signature); |
240 | |
241 | KASSERT(sc->sc_family >= 0xf); |
242 | |
243 | sc->sc_sme = NULL; |
244 | sc->sc_sensor = NULL; |
245 | |
246 | sc->sc_pc = pa->pa_pc; |
247 | sc->sc_pcitag = pa->pa_tag; |
248 | sc->sc_adjustment = 0; |
249 | |
250 | switch (sc->sc_family) { |
251 | case 0xf: /* AMD K8 NPT */ |
252 | amdtemp_k8_init(sc, cpu_signature); |
253 | break; |
254 | |
255 | case 0x10: /* AMD Barcelona/Phenom */ |
256 | case 0x11: /* AMD Griffin */ |
257 | case 0x12: /* AMD Lynx/Sabine (Llano) */ |
258 | case 0x14: /* AMD Brazos (Ontario/Zacate/Desna) */ |
259 | case 0x15: |
260 | amdtemp_family10_init(sc); |
261 | break; |
262 | |
263 | default: |
264 | aprint_normal(", family 0x%x not supported\n" , |
265 | sc->sc_family); |
266 | return; |
267 | } |
268 | |
269 | aprint_normal("\n" ); |
270 | |
271 | if (sc->sc_adjustment != 0) |
272 | aprint_debug_dev(self, "Workaround enabled\n" ); |
273 | |
274 | sc->sc_sme = sysmon_envsys_create(); |
275 | sc->sc_sensor_len = sizeof(envsys_data_t) * sc->sc_numsensors; |
276 | sc->sc_sensor = kmem_zalloc(sc->sc_sensor_len, KM_SLEEP); |
277 | |
278 | if (sc->sc_sensor == NULL) |
279 | goto bad; |
280 | |
281 | switch (sc->sc_family) { |
282 | case 0xf: |
283 | amdtemp_k8_setup_sensors(sc, device_unit(self)); |
284 | break; |
285 | case 0x10: |
286 | case 0x11: |
287 | case 0x12: |
288 | case 0x14: |
289 | case 0x15: |
290 | amdtemp_family10_setup_sensors(sc, device_unit(self)); |
291 | break; |
292 | } |
293 | |
294 | /* |
295 | * Set properties in sensors. |
296 | */ |
297 | for (i = 0; i < sc->sc_numsensors; i++) { |
298 | if (sysmon_envsys_sensor_attach(sc->sc_sme, |
299 | &sc->sc_sensor[i])) |
300 | goto bad; |
301 | } |
302 | |
303 | /* |
304 | * Register the sysmon_envsys device. |
305 | */ |
306 | sc->sc_sme->sme_name = device_xname(self); |
307 | sc->sc_sme->sme_cookie = sc; |
308 | |
309 | switch (sc->sc_family) { |
310 | case 0xf: |
311 | sc->sc_sme->sme_refresh = amdtemp_k8_refresh; |
312 | break; |
313 | case 0x10: |
314 | case 0x11: |
315 | case 0x12: |
316 | case 0x14: |
317 | case 0x15: |
318 | sc->sc_sme->sme_refresh = amdtemp_family10_refresh; |
319 | break; |
320 | } |
321 | |
322 | error = sysmon_envsys_register(sc->sc_sme); |
323 | if (error) { |
324 | aprint_error_dev(self, "unable to register with sysmon " |
325 | "(error=%d)\n" , error); |
326 | goto bad; |
327 | } |
328 | |
329 | (void)pmf_device_register(self, NULL, NULL); |
330 | |
331 | return; |
332 | |
333 | bad: |
334 | if (sc->sc_sme != NULL) { |
335 | sysmon_envsys_destroy(sc->sc_sme); |
336 | sc->sc_sme = NULL; |
337 | } |
338 | |
339 | if (sc->sc_sensor != NULL) { |
340 | kmem_free(sc->sc_sensor, sc->sc_sensor_len); |
341 | sc->sc_sensor = NULL; |
342 | } |
343 | } |
344 | |
345 | static int |
346 | amdtemp_detach(device_t self, int flags) |
347 | { |
348 | struct amdtemp_softc *sc = device_private(self); |
349 | |
350 | pmf_device_deregister(self); |
351 | if (sc->sc_sme != NULL) |
352 | sysmon_envsys_unregister(sc->sc_sme); |
353 | |
354 | if (sc->sc_sensor != NULL) |
355 | kmem_free(sc->sc_sensor, sc->sc_sensor_len); |
356 | |
357 | return 0; |
358 | } |
359 | |
360 | static void |
361 | amdtemp_k8_init(struct amdtemp_softc *sc, pcireg_t cpu_signature) |
362 | { |
363 | pcireg_t data; |
364 | uint32_t cmpcap; |
365 | uint8_t i, j; |
366 | |
367 | aprint_normal(" (K8" ); |
368 | |
369 | for (i = 0; i < __arraycount(amdtemp_core) && sc->sc_rev == '\0'; i++) { |
370 | for (j = 0; amdtemp_core[i].cpu[j].cpuid != 0; j++) { |
371 | if ((cpu_signature & ~0xf) |
372 | != amdtemp_core[i].cpu[j].cpuid) |
373 | continue; |
374 | |
375 | sc->sc_rev = amdtemp_core[i].rev[3]; |
376 | aprint_normal(": core rev %.4s%.1x" , |
377 | amdtemp_core[i].rev, |
378 | CPUID_TO_STEPPING(cpu_signature)); |
379 | |
380 | switch (amdtemp_core[i].cpu[j].socket) { |
381 | case K8_SOCKET_AM2: |
382 | if (sc->sc_rev == 'G') |
383 | sc->sc_adjustment = 21000000; |
384 | aprint_normal(", socket AM2" ); |
385 | break; |
386 | case K8_SOCKET_S1: |
387 | aprint_normal(", socket S1" ); |
388 | break; |
389 | case K8_SOCKET_F: |
390 | aprint_normal(", socket F" ); |
391 | break; |
392 | } |
393 | } |
394 | } |
395 | |
396 | if (sc->sc_rev == '\0') { |
397 | /* CPUID Family Model Register was introduced in |
398 | * Revision F */ |
399 | sc->sc_rev = 'G'; /* newer than E, assume G */ |
400 | aprint_normal(": cpuid 0x%x" , cpu_signature); |
401 | } |
402 | |
403 | aprint_normal(")" ); |
404 | |
405 | data = pci_conf_read(sc->sc_pc, sc->sc_pcitag, NORTHBRIDGE_CAP_R); |
406 | cmpcap = (data >> 12) & 0x3; |
407 | |
408 | sc->sc_numsensors = cmpcap ? 4 : 2; |
409 | } |
410 | |
411 | |
412 | static void |
413 | amdtemp_k8_setup_sensors(struct amdtemp_softc *sc, int dv_unit) |
414 | { |
415 | uint8_t i; |
416 | |
417 | /* There are two sensors per CPU core. So we use the |
418 | * device unit as socket counter to correctly enumerate |
419 | * the CPUs on multi-socket machines. |
420 | */ |
421 | dv_unit *= (sc->sc_numsensors / 2); |
422 | for (i = 0; i < sc->sc_numsensors; i++) { |
423 | sc->sc_sensor[i].units = ENVSYS_STEMP; |
424 | sc->sc_sensor[i].state = ENVSYS_SVALID; |
425 | sc->sc_sensor[i].flags = ENVSYS_FHAS_ENTROPY; |
426 | |
427 | snprintf(sc->sc_sensor[i].desc, sizeof(sc->sc_sensor[i].desc), |
428 | "CPU%u Sensor%u" , dv_unit + (i / 2), i % 2); |
429 | } |
430 | } |
431 | |
432 | |
433 | static void |
434 | amdtemp_k8_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) |
435 | { |
436 | struct amdtemp_softc *sc = sme->sme_cookie; |
437 | pcireg_t status, match, tmp; |
438 | uint32_t value; |
439 | |
440 | status = pci_conf_read(sc->sc_pc, sc->sc_pcitag, THERMTRIP_STAT_R); |
441 | |
442 | switch(edata->sensor) { /* sensor number */ |
443 | case 0: /* Core 0 Sensor 0 */ |
444 | K8_T_SEL_C0(status); |
445 | K8_T_SEL_S0(status); |
446 | break; |
447 | case 1: /* Core 0 Sensor 1 */ |
448 | K8_T_SEL_C0(status); |
449 | K8_T_SEL_S1(status); |
450 | break; |
451 | case 2: /* Core 1 Sensor 0 */ |
452 | K8_T_SEL_C1(status); |
453 | K8_T_SEL_S0(status); |
454 | break; |
455 | case 3: /* Core 1 Sensor 1 */ |
456 | K8_T_SEL_C1(status); |
457 | K8_T_SEL_S1(status); |
458 | break; |
459 | } |
460 | |
461 | match = status & (K8_THERM_SENSE_CORE_SEL | K8_THERM_SENSE_SEL); |
462 | pci_conf_write(sc->sc_pc, sc->sc_pcitag, THERMTRIP_STAT_R, status); |
463 | status = pci_conf_read(sc->sc_pc, sc->sc_pcitag, THERMTRIP_STAT_R); |
464 | tmp = status & (K8_THERM_SENSE_CORE_SEL | K8_THERM_SENSE_SEL); |
465 | |
466 | value = 0x3ff & (status >> 14); |
467 | if (sc->sc_rev != 'G') |
468 | value &= ~0x3; |
469 | |
470 | edata->state = ENVSYS_SINVALID; |
471 | if ((tmp == match) && ((value & ~0x3) != 0)) { |
472 | edata->state = ENVSYS_SVALID; |
473 | edata->value_cur = (value * 250000 - 49000000) + 273150000 |
474 | + sc->sc_adjustment; |
475 | } |
476 | } |
477 | |
478 | |
479 | static void |
480 | amdtemp_family10_init(struct amdtemp_softc *sc) |
481 | { |
482 | aprint_normal(" (Family%02xh)" , sc->sc_family); |
483 | |
484 | sc->sc_numsensors = 1; |
485 | } |
486 | |
487 | static void |
488 | amdtemp_family10_setup_sensors(struct amdtemp_softc *sc, int dv_unit) |
489 | { |
490 | /* sanity check for future enhancements */ |
491 | KASSERT(sc->sc_numsensors == 1); |
492 | |
493 | /* There's one sensor per memory controller (= socket) |
494 | * so we use the device unit as socket counter |
495 | * to correctly enumerate the CPUs |
496 | */ |
497 | sc->sc_sensor[0].units = ENVSYS_STEMP; |
498 | sc->sc_sensor[0].state = ENVSYS_SVALID; |
499 | sc->sc_sensor[0].flags = ENVSYS_FHAS_ENTROPY; |
500 | |
501 | snprintf(sc->sc_sensor[0].desc, sizeof(sc->sc_sensor[0].desc), |
502 | "cpu%u temperature" , dv_unit); |
503 | } |
504 | |
505 | |
506 | static void |
507 | amdtemp_family10_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) |
508 | { |
509 | struct amdtemp_softc *sc = sme->sme_cookie; |
510 | pcireg_t status; |
511 | uint32_t value; |
512 | |
513 | status = pci_conf_read(sc->sc_pc, |
514 | sc->sc_pcitag, F10_TEMPERATURE_CTL_R); |
515 | |
516 | value = (status >> 21); |
517 | |
518 | edata->state = ENVSYS_SVALID; |
519 | edata->value_cur = (value * 125000) + 273150000; /* From C to uK. */ |
520 | } |
521 | |
522 | MODULE(MODULE_CLASS_DRIVER, amdtemp, "sysmon_envsys" ); |
523 | |
524 | #ifdef _MODULE |
525 | #include "ioconf.c" |
526 | #endif |
527 | |
528 | static int |
529 | amdtemp_modcmd(modcmd_t cmd, void *aux) |
530 | { |
531 | int error = 0; |
532 | |
533 | switch (cmd) { |
534 | case MODULE_CMD_INIT: |
535 | #ifdef _MODULE |
536 | error = config_init_component(cfdriver_ioconf_amdtemp, |
537 | cfattach_ioconf_amdtemp, cfdata_ioconf_amdtemp); |
538 | #endif |
539 | return error; |
540 | case MODULE_CMD_FINI: |
541 | #ifdef _MODULE |
542 | error = config_fini_component(cfdriver_ioconf_amdtemp, |
543 | cfattach_ioconf_amdtemp, cfdata_ioconf_amdtemp); |
544 | #endif |
545 | return error; |
546 | default: |
547 | return ENOTTY; |
548 | } |
549 | } |
550 | |