diff options
author | Guenter Roeck <linux@roeck-us.net> | 2014-04-06 11:57:20 -0400 |
---|---|---|
committer | Guenter Roeck <linux@roeck-us.net> | 2014-05-21 19:02:27 -0400 |
commit | 41082d66bfd6fafe001c0902bb4622d7aee6f128 (patch) | |
tree | 8982ce666a86e8f48ff95dba0e8bd612e44a7c62 | |
parent | c254ffdefc002b57f1e9eedddcb3569c0d80578f (diff) |
hwmon: Driver for NCT6683D
Nuvoton NCT6683D is an eSIO with hardware monitoring capabilities.
Signed-off-by: Guenter Roeck <linux@roeck-us.net>
-rw-r--r-- | Documentation/hwmon/nct6683 | 57 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/nct6683.c | 1455 |
4 files changed, 1523 insertions, 0 deletions
diff --git a/Documentation/hwmon/nct6683 b/Documentation/hwmon/nct6683 new file mode 100644 index 000000000000..c1301d4300cd --- /dev/null +++ b/Documentation/hwmon/nct6683 | |||
@@ -0,0 +1,57 @@ | |||
1 | Kernel driver nct6683 | ||
2 | ===================== | ||
3 | |||
4 | Supported chips: | ||
5 | * Nuvoton NCT6683D | ||
6 | Prefix: 'nct6683' | ||
7 | Addresses scanned: ISA address retrieved from Super I/O registers | ||
8 | Datasheet: Available from Nuvoton upon request | ||
9 | |||
10 | Authors: | ||
11 | Guenter Roeck <linux@roeck-us.net> | ||
12 | |||
13 | Description | ||
14 | ----------- | ||
15 | |||
16 | This driver implements support for the Nuvoton NCT6683D eSIO chip. | ||
17 | |||
18 | The chips implement up to shared 32 temperature and voltage sensors. | ||
19 | It supports up to 16 fan rotation sensors and up to 8 fan control engines. | ||
20 | |||
21 | Temperatures are measured in degrees Celsius. Measurement resolution is | ||
22 | 0.5 degrees C. | ||
23 | |||
24 | Voltage sensors (also known as IN sensors) report their values in millivolts. | ||
25 | |||
26 | Fan rotation speeds are reported in RPM (rotations per minute). | ||
27 | |||
28 | Usage Note | ||
29 | ---------- | ||
30 | |||
31 | Limit register locations on Intel boards with EC firmware version 1.0 | ||
32 | build date 04/03/13 do not match the register locations in the Nuvoton | ||
33 | datasheet. Nuvoton confirms that Intel uses a special firmware version | ||
34 | with different register addresses. The specification describing the Intel | ||
35 | firmware is held under NDA by Nuvoton and Intel and not available | ||
36 | to the public. | ||
37 | |||
38 | Some of the register locations can be reverse engineered; others are too | ||
39 | well hidden. Given this, writing any values from the operating system is | ||
40 | considered too risky with this firmware and has been disabled. All limits | ||
41 | must all be written from the BIOS. | ||
42 | |||
43 | The driver has only been tested with the Intel firmware, and by default | ||
44 | only instantiates on Intel boards. To enable it on non-Intel boards, | ||
45 | set the 'force' module parameter to 1. | ||
46 | |||
47 | Tested Boards and Firmware Versions | ||
48 | ----------------------------------- | ||
49 | |||
50 | The driver has been reported to work with the following boards and | ||
51 | firmware versions. | ||
52 | |||
53 | Board Firmware version | ||
54 | --------------------------------------------------------------- | ||
55 | Intel DH87RL NCT6683D EC firmware version 1.0 build 04/03/13 | ||
56 | Intel DH87MC NCT6683D EC firmware version 1.0 build 04/03/13 | ||
57 | Intel DB85FL NCT6683D EC firmware version 1.0 build 04/03/13 | ||
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index bc196f49ec53..b7c0b830d9e4 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -1065,6 +1065,16 @@ config SENSORS_NTC_THERMISTOR | |||
1065 | This driver can also be built as a module. If so, the module | 1065 | This driver can also be built as a module. If so, the module |
1066 | will be called ntc-thermistor. | 1066 | will be called ntc-thermistor. |
1067 | 1067 | ||
1068 | config SENSORS_NCT6683 | ||
1069 | tristate "Nuvoton NCT6683D" | ||
1070 | depends on !PPC | ||
1071 | help | ||
1072 | If you say yes here you get support for the hardware monitoring | ||
1073 | functionality of the Nuvoton NCT6683D eSIO chip. | ||
1074 | |||
1075 | This driver can also be built as a module. If so, the module | ||
1076 | will be called nct6683. | ||
1077 | |||
1068 | config SENSORS_NCT6775 | 1078 | config SENSORS_NCT6775 |
1069 | tristate "Nuvoton NCT6775F and compatibles" | 1079 | tristate "Nuvoton NCT6775F and compatibles" |
1070 | depends on !PPC | 1080 | depends on !PPC |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c48f9873ac73..11798ad7e801 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -114,6 +114,7 @@ obj-$(CONFIG_SENSORS_MAX6650) += max6650.o | |||
114 | obj-$(CONFIG_SENSORS_MAX6697) += max6697.o | 114 | obj-$(CONFIG_SENSORS_MAX6697) += max6697.o |
115 | obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o | 115 | obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o |
116 | obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o | 116 | obj-$(CONFIG_SENSORS_MCP3021) += mcp3021.o |
117 | obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o | ||
117 | obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o | 118 | obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o |
118 | obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o | 119 | obj-$(CONFIG_SENSORS_NTC_THERMISTOR) += ntc_thermistor.o |
119 | obj-$(CONFIG_SENSORS_PC87360) += pc87360.o | 120 | obj-$(CONFIG_SENSORS_PC87360) += pc87360.o |
diff --git a/drivers/hwmon/nct6683.c b/drivers/hwmon/nct6683.c new file mode 100644 index 000000000000..540c81c52344 --- /dev/null +++ b/drivers/hwmon/nct6683.c | |||
@@ -0,0 +1,1455 @@ | |||
1 | /* | ||
2 | * nct6683 - Driver for the hardware monitoring functionality of | ||
3 | * Nuvoton NCT6683D eSIO | ||
4 | * | ||
5 | * Copyright (C) 2013 Guenter Roeck <linux@roeck-us.net> | ||
6 | * | ||
7 | * Derived from nct6775 driver | ||
8 | * Copyright (C) 2012, 2013 Guenter Roeck <linux@roeck-us.net> | ||
9 | * | ||
10 | * This program is free software; you can redistribute it and/or modify | ||
11 | * it under the terms of the GNU General Public License as published by | ||
12 | * the Free Software Foundation; either version 2 of the License, or | ||
13 | * (at your option) any later version. | ||
14 | * | ||
15 | * This program is distributed in the hope that it will be useful, | ||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
18 | * GNU General Public License for more details. | ||
19 | * | ||
20 | * Supports the following chips: | ||
21 | * | ||
22 | * Chip #vin #fan #pwm #temp chip ID | ||
23 | * nct6683d 21(1) 16 8 32(1) 0xc730 | ||
24 | * | ||
25 | * Notes: | ||
26 | * (1) Total number of vin and temp inputs is 32. | ||
27 | */ | ||
28 | |||
29 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
30 | |||
31 | #include <linux/acpi.h> | ||
32 | #include <linux/dmi.h> | ||
33 | #include <linux/err.h> | ||
34 | #include <linux/init.h> | ||
35 | #include <linux/io.h> | ||
36 | #include <linux/jiffies.h> | ||
37 | #include <linux/hwmon.h> | ||
38 | #include <linux/hwmon-sysfs.h> | ||
39 | #include <linux/module.h> | ||
40 | #include <linux/mutex.h> | ||
41 | #include <linux/platform_device.h> | ||
42 | #include <linux/slab.h> | ||
43 | |||
44 | enum kinds { nct6683 }; | ||
45 | |||
46 | static bool force; | ||
47 | module_param(force, bool, 0); | ||
48 | MODULE_PARM_DESC(force, "Set to one to enable detection on non-Intel boards"); | ||
49 | |||
50 | static const char * const nct6683_device_names[] = { | ||
51 | "nct6683", | ||
52 | }; | ||
53 | |||
54 | static const char * const nct6683_chip_names[] = { | ||
55 | "NCT6683D", | ||
56 | }; | ||
57 | |||
58 | #define DRVNAME "nct6683" | ||
59 | |||
60 | /* | ||
61 | * Super-I/O constants and functions | ||
62 | */ | ||
63 | |||
64 | #define NCT6683_LD_ACPI 0x0a | ||
65 | #define NCT6683_LD_HWM 0x0b | ||
66 | #define NCT6683_LD_VID 0x0d | ||
67 | |||
68 | #define SIO_REG_LDSEL 0x07 /* Logical device select */ | ||
69 | #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ | ||
70 | #define SIO_REG_ENABLE 0x30 /* Logical device enable */ | ||
71 | #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ | ||
72 | |||
73 | #define SIO_NCT6681_ID 0xb270 /* for later */ | ||
74 | #define SIO_NCT6683_ID 0xc730 | ||
75 | #define SIO_ID_MASK 0xFFF0 | ||
76 | |||
77 | static inline void | ||
78 | superio_outb(int ioreg, int reg, int val) | ||
79 | { | ||
80 | outb(reg, ioreg); | ||
81 | outb(val, ioreg + 1); | ||
82 | } | ||
83 | |||
84 | static inline int | ||
85 | superio_inb(int ioreg, int reg) | ||
86 | { | ||
87 | outb(reg, ioreg); | ||
88 | return inb(ioreg + 1); | ||
89 | } | ||
90 | |||
91 | static inline void | ||
92 | superio_select(int ioreg, int ld) | ||
93 | { | ||
94 | outb(SIO_REG_LDSEL, ioreg); | ||
95 | outb(ld, ioreg + 1); | ||
96 | } | ||
97 | |||
98 | static inline int | ||
99 | superio_enter(int ioreg) | ||
100 | { | ||
101 | /* | ||
102 | * Try to reserve <ioreg> and <ioreg + 1> for exclusive access. | ||
103 | */ | ||
104 | if (!request_muxed_region(ioreg, 2, DRVNAME)) | ||
105 | return -EBUSY; | ||
106 | |||
107 | outb(0x87, ioreg); | ||
108 | outb(0x87, ioreg); | ||
109 | |||
110 | return 0; | ||
111 | } | ||
112 | |||
113 | static inline void | ||
114 | superio_exit(int ioreg) | ||
115 | { | ||
116 | outb(0xaa, ioreg); | ||
117 | outb(0x02, ioreg); | ||
118 | outb(0x02, ioreg + 1); | ||
119 | release_region(ioreg, 2); | ||
120 | } | ||
121 | |||
122 | /* | ||
123 | * ISA constants | ||
124 | */ | ||
125 | |||
126 | #define IOREGION_ALIGNMENT (~7) | ||
127 | #define IOREGION_OFFSET 4 /* Use EC port 1 */ | ||
128 | #define IOREGION_LENGTH 4 | ||
129 | |||
130 | #define EC_PAGE_REG 0 | ||
131 | #define EC_INDEX_REG 1 | ||
132 | #define EC_DATA_REG 2 | ||
133 | #define EC_EVENT_REG 3 | ||
134 | |||
135 | /* Common and NCT6683 specific data */ | ||
136 | |||
137 | #define NCT6683_NUM_REG_MON 32 | ||
138 | #define NCT6683_NUM_REG_FAN 16 | ||
139 | #define NCT6683_NUM_REG_PWM 8 | ||
140 | |||
141 | #define NCT6683_REG_MON(x) (0x100 + (x) * 2) | ||
142 | #define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2) | ||
143 | #define NCT6683_REG_PWM(x) (0x160 + (x)) | ||
144 | |||
145 | #define NCT6683_REG_MON_STS(x) (0x174 + (x)) | ||
146 | #define NCT6683_REG_IDLE(x) (0x178 + (x)) | ||
147 | |||
148 | #define NCT6683_REG_FAN_STS(x) (0x17c + (x)) | ||
149 | #define NCT6683_REG_FAN_ERRSTS 0x17e | ||
150 | #define NCT6683_REG_FAN_INITSTS 0x17f | ||
151 | |||
152 | #define NCT6683_HWM_CFG 0x180 | ||
153 | |||
154 | #define NCT6683_REG_MON_CFG(x) (0x1a0 + (x)) | ||
155 | #define NCT6683_REG_FANIN_CFG(x) (0x1c0 + (x)) | ||
156 | #define NCT6683_REG_FANOUT_CFG(x) (0x1d0 + (x)) | ||
157 | |||
158 | #define NCT6683_REG_INTEL_TEMP_MAX(x) (0x901 + (x) * 16) | ||
159 | #define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16) | ||
160 | |||
161 | #define NCT6683_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */ | ||
162 | #define NCT6683_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */ | ||
163 | #define NCT6683_REG_MON_HIGH(x) (0x370 + (x) * 2) /* 8 bit */ | ||
164 | #define NCT6683_REG_MON_LOW(x) (0x371 + (x) * 2) /* 8 bit */ | ||
165 | |||
166 | #define NCT6683_REG_FAN_MIN(x) (0x3b8 + (x) * 2) /* 16 bit */ | ||
167 | |||
168 | #define NCT6683_REG_CUSTOMER_ID 0x602 | ||
169 | #define NCT6683_CUSTOMER_ID_INTEL 0x805 | ||
170 | |||
171 | #define NCT6683_REG_BUILD_YEAR 0x604 | ||
172 | #define NCT6683_REG_BUILD_MONTH 0x605 | ||
173 | #define NCT6683_REG_BUILD_DAY 0x606 | ||
174 | #define NCT6683_REG_SERIAL 0x607 | ||
175 | #define NCT6683_REG_VERSION_HI 0x608 | ||
176 | #define NCT6683_REG_VERSION_LO 0x609 | ||
177 | |||
178 | #define NCT6683_REG_CR_CASEOPEN 0xe8 | ||
179 | #define NCT6683_CR_CASEOPEN_MASK (1 << 7) | ||
180 | |||
181 | #define NCT6683_REG_CR_BEEP 0xe0 | ||
182 | #define NCT6683_CR_BEEP_MASK (1 << 6) | ||
183 | |||
184 | static const char *const nct6683_mon_label[] = { | ||
185 | NULL, /* disabled */ | ||
186 | "Local", | ||
187 | "Diode 0 (curr)", | ||
188 | "Diode 1 (curr)", | ||
189 | "Diode 2 (curr)", | ||
190 | "Diode 0 (volt)", | ||
191 | "Diode 1 (volt)", | ||
192 | "Diode 2 (volt)", | ||
193 | "Thermistor 14", | ||
194 | "Thermistor 15", | ||
195 | "Thermistor 16", | ||
196 | "Thermistor 0", | ||
197 | "Thermistor 1", | ||
198 | "Thermistor 2", | ||
199 | "Thermistor 3", | ||
200 | "Thermistor 4", | ||
201 | "Thermistor 5", /* 0x10 */ | ||
202 | "Thermistor 6", | ||
203 | "Thermistor 7", | ||
204 | "Thermistor 8", | ||
205 | "Thermistor 9", | ||
206 | "Thermistor 10", | ||
207 | "Thermistor 11", | ||
208 | "Thermistor 12", | ||
209 | "Thermistor 13", | ||
210 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
211 | "PECI 0.0", /* 0x20 */ | ||
212 | "PECI 1.0", | ||
213 | "PECI 2.0", | ||
214 | "PECI 3.0", | ||
215 | "PECI 0.1", | ||
216 | "PECI 1.1", | ||
217 | "PECI 2.1", | ||
218 | "PECI 3.1", | ||
219 | "PECI DIMM 0", | ||
220 | "PECI DIMM 1", | ||
221 | "PECI DIMM 2", | ||
222 | "PECI DIMM 3", | ||
223 | NULL, NULL, NULL, NULL, | ||
224 | "PCH CPU", /* 0x30 */ | ||
225 | "PCH CHIP", | ||
226 | "PCH CHIP CPU MAX", | ||
227 | "PCH MCH", | ||
228 | "PCH DIMM 0", | ||
229 | "PCH DIMM 1", | ||
230 | "PCH DIMM 2", | ||
231 | "PCH DIMM 3", | ||
232 | "SMBus 0", | ||
233 | "SMBus 1", | ||
234 | "SMBus 2", | ||
235 | "SMBus 3", | ||
236 | "SMBus 4", | ||
237 | "SMBus 5", | ||
238 | "DIMM 0", | ||
239 | "DIMM 1", | ||
240 | "DIMM 2", /* 0x40 */ | ||
241 | "DIMM 3", | ||
242 | "AMD TSI Addr 90h", | ||
243 | "AMD TSI Addr 92h", | ||
244 | "AMD TSI Addr 94h", | ||
245 | "AMD TSI Addr 96h", | ||
246 | "AMD TSI Addr 98h", | ||
247 | "AMD TSI Addr 9ah", | ||
248 | "AMD TSI Addr 9ch", | ||
249 | "AMD TSI Addr 9dh", | ||
250 | NULL, NULL, NULL, NULL, NULL, NULL, | ||
251 | "Virtual 0", /* 0x50 */ | ||
252 | "Virtual 1", | ||
253 | "Virtual 2", | ||
254 | "Virtual 3", | ||
255 | "Virtual 4", | ||
256 | "Virtual 5", | ||
257 | "Virtual 6", | ||
258 | "Virtual 7", | ||
259 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | ||
260 | "VCC", /* 0x60 voltage sensors */ | ||
261 | "VSB", | ||
262 | "AVSB", | ||
263 | "VTT", | ||
264 | "VBAT", | ||
265 | "VREF", | ||
266 | "VIN0", | ||
267 | "VIN1", | ||
268 | "VIN2", | ||
269 | "VIN3", | ||
270 | "VIN4", | ||
271 | "VIN5", | ||
272 | "VIN6", | ||
273 | "VIN7", | ||
274 | "VIN8", | ||
275 | "VIN9", | ||
276 | "VIN10", | ||
277 | "VIN11", | ||
278 | "VIN12", | ||
279 | "VIN13", | ||
280 | "VIN14", | ||
281 | "VIN15", | ||
282 | "VIN16", | ||
283 | }; | ||
284 | |||
285 | #define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label) | ||
286 | #define MON_VOLTAGE_START 0x60 | ||
287 | |||
288 | /* ------------------------------------------------------- */ | ||
289 | |||
290 | struct nct6683_data { | ||
291 | int addr; /* IO base of EC space */ | ||
292 | int sioreg; /* SIO register */ | ||
293 | enum kinds kind; | ||
294 | u16 customer_id; | ||
295 | |||
296 | struct device *hwmon_dev; | ||
297 | const struct attribute_group *groups[6]; | ||
298 | |||
299 | int temp_num; /* number of temperature attributes */ | ||
300 | u8 temp_index[NCT6683_NUM_REG_MON]; | ||
301 | u8 temp_src[NCT6683_NUM_REG_MON]; | ||
302 | |||
303 | u8 in_num; /* number of voltage attributes */ | ||
304 | u8 in_index[NCT6683_NUM_REG_MON]; | ||
305 | u8 in_src[NCT6683_NUM_REG_MON]; | ||
306 | |||
307 | struct mutex update_lock; /* used to protect sensor updates */ | ||
308 | bool valid; /* true if following fields are valid */ | ||
309 | unsigned long last_updated; /* In jiffies */ | ||
310 | |||
311 | /* Voltage attribute values */ | ||
312 | u8 in[3][NCT6683_NUM_REG_MON]; /* [0]=in, [1]=in_max, [2]=in_min */ | ||
313 | |||
314 | /* Temperature attribute values */ | ||
315 | s16 temp_in[NCT6683_NUM_REG_MON]; | ||
316 | s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst, | ||
317 | * [3]=crit | ||
318 | */ | ||
319 | |||
320 | /* Fan attribute values */ | ||
321 | unsigned int rpm[NCT6683_NUM_REG_FAN]; | ||
322 | u16 fan_min[NCT6683_NUM_REG_FAN]; | ||
323 | u8 fanin_cfg[NCT6683_NUM_REG_FAN]; | ||
324 | u8 fanout_cfg[NCT6683_NUM_REG_FAN]; | ||
325 | u16 have_fan; /* some fan inputs can be disabled */ | ||
326 | |||
327 | u8 have_pwm; | ||
328 | u8 pwm[NCT6683_NUM_REG_PWM]; | ||
329 | |||
330 | #ifdef CONFIG_PM | ||
331 | /* Remember extra register values over suspend/resume */ | ||
332 | u8 hwm_cfg; | ||
333 | #endif | ||
334 | }; | ||
335 | |||
336 | struct nct6683_sio_data { | ||
337 | int sioreg; | ||
338 | enum kinds kind; | ||
339 | }; | ||
340 | |||
341 | struct sensor_device_template { | ||
342 | struct device_attribute dev_attr; | ||
343 | union { | ||
344 | struct { | ||
345 | u8 nr; | ||
346 | u8 index; | ||
347 | } s; | ||
348 | int index; | ||
349 | } u; | ||
350 | bool s2; /* true if both index and nr are used */ | ||
351 | }; | ||
352 | |||
353 | struct sensor_device_attr_u { | ||
354 | union { | ||
355 | struct sensor_device_attribute a1; | ||
356 | struct sensor_device_attribute_2 a2; | ||
357 | } u; | ||
358 | char name[32]; | ||
359 | }; | ||
360 | |||
361 | #define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ | ||
362 | .attr = {.name = _template, .mode = _mode }, \ | ||
363 | .show = _show, \ | ||
364 | .store = _store, \ | ||
365 | } | ||
366 | |||
367 | #define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ | ||
368 | { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ | ||
369 | .u.index = _index, \ | ||
370 | .s2 = false } | ||
371 | |||
372 | #define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ | ||
373 | _nr, _index) \ | ||
374 | { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ | ||
375 | .u.s.index = _index, \ | ||
376 | .u.s.nr = _nr, \ | ||
377 | .s2 = true } | ||
378 | |||
379 | #define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ | ||
380 | static struct sensor_device_template sensor_dev_template_##_name \ | ||
381 | = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ | ||
382 | _index) | ||
383 | |||
384 | #define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ | ||
385 | _nr, _index) \ | ||
386 | static struct sensor_device_template sensor_dev_template_##_name \ | ||
387 | = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ | ||
388 | _nr, _index) | ||
389 | |||
390 | struct sensor_template_group { | ||
391 | struct sensor_device_template **templates; | ||
392 | umode_t (*is_visible)(struct kobject *, struct attribute *, int); | ||
393 | int base; | ||
394 | }; | ||
395 | |||
396 | static struct attribute_group * | ||
397 | nct6683_create_attr_group(struct device *dev, struct sensor_template_group *tg, | ||
398 | int repeat) | ||
399 | { | ||
400 | struct sensor_device_attribute_2 *a2; | ||
401 | struct sensor_device_attribute *a; | ||
402 | struct sensor_device_template **t; | ||
403 | struct sensor_device_attr_u *su; | ||
404 | struct attribute_group *group; | ||
405 | struct attribute **attrs; | ||
406 | int i, j, count; | ||
407 | |||
408 | if (repeat <= 0) | ||
409 | return ERR_PTR(-EINVAL); | ||
410 | |||
411 | t = tg->templates; | ||
412 | for (count = 0; *t; t++, count++) | ||
413 | ; | ||
414 | |||
415 | if (count == 0) | ||
416 | return ERR_PTR(-EINVAL); | ||
417 | |||
418 | group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); | ||
419 | if (group == NULL) | ||
420 | return ERR_PTR(-ENOMEM); | ||
421 | |||
422 | attrs = devm_kzalloc(dev, sizeof(*attrs) * (repeat * count + 1), | ||
423 | GFP_KERNEL); | ||
424 | if (attrs == NULL) | ||
425 | return ERR_PTR(-ENOMEM); | ||
426 | |||
427 | su = devm_kzalloc(dev, sizeof(*su) * repeat * count, | ||
428 | GFP_KERNEL); | ||
429 | if (su == NULL) | ||
430 | return ERR_PTR(-ENOMEM); | ||
431 | |||
432 | group->attrs = attrs; | ||
433 | group->is_visible = tg->is_visible; | ||
434 | |||
435 | for (i = 0; i < repeat; i++) { | ||
436 | t = tg->templates; | ||
437 | for (j = 0; *t != NULL; j++) { | ||
438 | snprintf(su->name, sizeof(su->name), | ||
439 | (*t)->dev_attr.attr.name, tg->base + i); | ||
440 | if ((*t)->s2) { | ||
441 | a2 = &su->u.a2; | ||
442 | a2->dev_attr.attr.name = su->name; | ||
443 | a2->nr = (*t)->u.s.nr + i; | ||
444 | a2->index = (*t)->u.s.index; | ||
445 | a2->dev_attr.attr.mode = | ||
446 | (*t)->dev_attr.attr.mode; | ||
447 | a2->dev_attr.show = (*t)->dev_attr.show; | ||
448 | a2->dev_attr.store = (*t)->dev_attr.store; | ||
449 | *attrs = &a2->dev_attr.attr; | ||
450 | } else { | ||
451 | a = &su->u.a1; | ||
452 | a->dev_attr.attr.name = su->name; | ||
453 | a->index = (*t)->u.index + i; | ||
454 | a->dev_attr.attr.mode = | ||
455 | (*t)->dev_attr.attr.mode; | ||
456 | a->dev_attr.show = (*t)->dev_attr.show; | ||
457 | a->dev_attr.store = (*t)->dev_attr.store; | ||
458 | *attrs = &a->dev_attr.attr; | ||
459 | } | ||
460 | attrs++; | ||
461 | su++; | ||
462 | t++; | ||
463 | } | ||
464 | } | ||
465 | |||
466 | return group; | ||
467 | } | ||
468 | |||
469 | /* LSB is 16 mV, except for the following sources, where it is 32 mV */ | ||
470 | #define MON_SRC_VCC 0x60 | ||
471 | #define MON_SRC_VSB 0x61 | ||
472 | #define MON_SRC_AVSB 0x62 | ||
473 | #define MON_SRC_VBAT 0x64 | ||
474 | |||
475 | static inline long in_from_reg(u16 reg, u8 src) | ||
476 | { | ||
477 | int scale = 16; | ||
478 | |||
479 | if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || | ||
480 | src == MON_SRC_VBAT) | ||
481 | scale <<= 1; | ||
482 | return reg * scale; | ||
483 | } | ||
484 | |||
485 | static inline u16 in_to_reg(u32 val, u8 src) | ||
486 | { | ||
487 | int scale = 16; | ||
488 | |||
489 | if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || | ||
490 | src == MON_SRC_VBAT) | ||
491 | scale <<= 1; | ||
492 | |||
493 | return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127); | ||
494 | } | ||
495 | |||
496 | static u16 nct6683_read(struct nct6683_data *data, u16 reg) | ||
497 | { | ||
498 | int res; | ||
499 | |||
500 | outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ | ||
501 | outb_p(reg >> 8, data->addr + EC_PAGE_REG); | ||
502 | outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | ||
503 | res = inb_p(data->addr + EC_DATA_REG); | ||
504 | return res; | ||
505 | } | ||
506 | |||
507 | static u16 nct6683_read16(struct nct6683_data *data, u16 reg) | ||
508 | { | ||
509 | return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1); | ||
510 | } | ||
511 | |||
512 | static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value) | ||
513 | { | ||
514 | outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ | ||
515 | outb_p(reg >> 8, data->addr + EC_PAGE_REG); | ||
516 | outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | ||
517 | outb_p(value & 0xff, data->addr + EC_DATA_REG); | ||
518 | } | ||
519 | |||
520 | static int get_in_reg(struct nct6683_data *data, int nr, int index) | ||
521 | { | ||
522 | int ch = data->in_index[index]; | ||
523 | int reg = -EINVAL; | ||
524 | |||
525 | switch (nr) { | ||
526 | case 0: | ||
527 | reg = NCT6683_REG_MON(ch); | ||
528 | break; | ||
529 | case 1: | ||
530 | if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | ||
531 | reg = NCT6683_REG_MON_LOW(ch); | ||
532 | break; | ||
533 | case 2: | ||
534 | if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | ||
535 | reg = NCT6683_REG_MON_HIGH(ch); | ||
536 | break; | ||
537 | default: | ||
538 | break; | ||
539 | } | ||
540 | return reg; | ||
541 | } | ||
542 | |||
543 | static int get_temp_reg(struct nct6683_data *data, int nr, int index) | ||
544 | { | ||
545 | int ch = data->temp_index[index]; | ||
546 | int reg = -EINVAL; | ||
547 | |||
548 | switch (data->customer_id) { | ||
549 | case NCT6683_CUSTOMER_ID_INTEL: | ||
550 | switch (nr) { | ||
551 | default: | ||
552 | case 1: /* max */ | ||
553 | reg = NCT6683_REG_INTEL_TEMP_MAX(ch); | ||
554 | break; | ||
555 | case 3: /* crit */ | ||
556 | reg = NCT6683_REG_INTEL_TEMP_CRIT(ch); | ||
557 | break; | ||
558 | } | ||
559 | break; | ||
560 | default: | ||
561 | switch (nr) { | ||
562 | default: | ||
563 | case 0: /* min */ | ||
564 | reg = NCT6683_REG_MON_LOW(ch); | ||
565 | break; | ||
566 | case 1: /* max */ | ||
567 | reg = NCT6683_REG_TEMP_MAX(ch); | ||
568 | break; | ||
569 | case 2: /* hyst */ | ||
570 | reg = NCT6683_REG_TEMP_HYST(ch); | ||
571 | break; | ||
572 | case 3: /* crit */ | ||
573 | reg = NCT6683_REG_MON_HIGH(ch); | ||
574 | break; | ||
575 | } | ||
576 | break; | ||
577 | } | ||
578 | return reg; | ||
579 | } | ||
580 | |||
581 | static void nct6683_update_pwm(struct device *dev) | ||
582 | { | ||
583 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
584 | int i; | ||
585 | |||
586 | for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { | ||
587 | if (!(data->have_pwm & (1 << i))) | ||
588 | continue; | ||
589 | data->pwm[i] = nct6683_read(data, NCT6683_REG_PWM(i)); | ||
590 | } | ||
591 | } | ||
592 | |||
593 | static struct nct6683_data *nct6683_update_device(struct device *dev) | ||
594 | { | ||
595 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
596 | int i, j; | ||
597 | |||
598 | mutex_lock(&data->update_lock); | ||
599 | |||
600 | if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { | ||
601 | /* Measured voltages and limits */ | ||
602 | for (i = 0; i < data->in_num; i++) { | ||
603 | for (j = 0; j < 3; j++) { | ||
604 | int reg = get_in_reg(data, j, i); | ||
605 | |||
606 | if (reg >= 0) | ||
607 | data->in[j][i] = | ||
608 | nct6683_read(data, reg); | ||
609 | } | ||
610 | } | ||
611 | |||
612 | /* Measured temperatures and limits */ | ||
613 | for (i = 0; i < data->temp_num; i++) { | ||
614 | u8 ch = data->temp_index[i]; | ||
615 | |||
616 | data->temp_in[i] = nct6683_read16(data, | ||
617 | NCT6683_REG_MON(ch)); | ||
618 | for (j = 0; j < 4; j++) { | ||
619 | int reg = get_temp_reg(data, j, i); | ||
620 | |||
621 | if (reg >= 0) | ||
622 | data->temp[j][i] = | ||
623 | nct6683_read(data, reg); | ||
624 | } | ||
625 | } | ||
626 | |||
627 | /* Measured fan speeds and limits */ | ||
628 | for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { | ||
629 | if (!(data->have_fan & (1 << i))) | ||
630 | continue; | ||
631 | |||
632 | data->rpm[i] = nct6683_read16(data, | ||
633 | NCT6683_REG_FAN_RPM(i)); | ||
634 | data->fan_min[i] = nct6683_read16(data, | ||
635 | NCT6683_REG_FAN_MIN(i)); | ||
636 | } | ||
637 | |||
638 | nct6683_update_pwm(dev); | ||
639 | |||
640 | data->last_updated = jiffies; | ||
641 | data->valid = true; | ||
642 | } | ||
643 | |||
644 | mutex_unlock(&data->update_lock); | ||
645 | return data; | ||
646 | } | ||
647 | |||
648 | /* | ||
649 | * Sysfs callback functions | ||
650 | */ | ||
651 | static ssize_t | ||
652 | show_in_label(struct device *dev, struct device_attribute *attr, char *buf) | ||
653 | { | ||
654 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
655 | struct nct6683_data *data = nct6683_update_device(dev); | ||
656 | int nr = sattr->index; | ||
657 | |||
658 | return sprintf(buf, "%s\n", nct6683_mon_label[data->in_src[nr]]); | ||
659 | } | ||
660 | |||
661 | static ssize_t | ||
662 | show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) | ||
663 | { | ||
664 | struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | ||
665 | struct nct6683_data *data = nct6683_update_device(dev); | ||
666 | int index = sattr->index; | ||
667 | int nr = sattr->nr; | ||
668 | |||
669 | return sprintf(buf, "%ld\n", | ||
670 | in_from_reg(data->in[index][nr], data->in_index[index])); | ||
671 | } | ||
672 | |||
673 | static umode_t nct6683_in_is_visible(struct kobject *kobj, | ||
674 | struct attribute *attr, int index) | ||
675 | { | ||
676 | struct device *dev = container_of(kobj, struct device, kobj); | ||
677 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
678 | int nr = index % 4; /* attribute */ | ||
679 | |||
680 | /* | ||
681 | * Voltage limits exist for Intel boards, | ||
682 | * but register location and encoding is unknown | ||
683 | */ | ||
684 | if ((nr == 2 || nr == 3) && | ||
685 | data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | ||
686 | return 0; | ||
687 | |||
688 | return attr->mode; | ||
689 | } | ||
690 | |||
691 | SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0); | ||
692 | SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); | ||
693 | SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1); | ||
694 | SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2); | ||
695 | |||
696 | static struct sensor_device_template *nct6683_attributes_in_template[] = { | ||
697 | &sensor_dev_template_in_label, | ||
698 | &sensor_dev_template_in_input, | ||
699 | &sensor_dev_template_in_min, | ||
700 | &sensor_dev_template_in_max, | ||
701 | NULL | ||
702 | }; | ||
703 | |||
704 | static struct sensor_template_group nct6683_in_template_group = { | ||
705 | .templates = nct6683_attributes_in_template, | ||
706 | .is_visible = nct6683_in_is_visible, | ||
707 | }; | ||
708 | |||
709 | static ssize_t | ||
710 | show_fan(struct device *dev, struct device_attribute *attr, char *buf) | ||
711 | { | ||
712 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
713 | struct nct6683_data *data = nct6683_update_device(dev); | ||
714 | |||
715 | return sprintf(buf, "%d\n", data->rpm[sattr->index]); | ||
716 | } | ||
717 | |||
718 | static ssize_t | ||
719 | show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) | ||
720 | { | ||
721 | struct nct6683_data *data = nct6683_update_device(dev); | ||
722 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
723 | int nr = sattr->index; | ||
724 | |||
725 | return sprintf(buf, "%d\n", data->fan_min[nr]); | ||
726 | } | ||
727 | |||
728 | static ssize_t | ||
729 | show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) | ||
730 | { | ||
731 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
732 | struct nct6683_data *data = nct6683_update_device(dev); | ||
733 | |||
734 | return sprintf(buf, "%d\n", | ||
735 | ((data->fanin_cfg[sattr->index] >> 5) & 0x03) + 1); | ||
736 | } | ||
737 | |||
738 | static umode_t nct6683_fan_is_visible(struct kobject *kobj, | ||
739 | struct attribute *attr, int index) | ||
740 | { | ||
741 | struct device *dev = container_of(kobj, struct device, kobj); | ||
742 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
743 | int fan = index / 3; /* fan index */ | ||
744 | int nr = index % 3; /* attribute index */ | ||
745 | |||
746 | if (!(data->have_fan & (1 << fan))) | ||
747 | return 0; | ||
748 | |||
749 | /* | ||
750 | * Intel may have minimum fan speed limits, | ||
751 | * but register location and encoding are unknown. | ||
752 | */ | ||
753 | if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | ||
754 | return 0; | ||
755 | |||
756 | return attr->mode; | ||
757 | } | ||
758 | |||
759 | SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); | ||
760 | SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0); | ||
761 | SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0); | ||
762 | |||
763 | /* | ||
764 | * nct6683_fan_is_visible uses the index into the following array | ||
765 | * to determine if attributes should be created or not. | ||
766 | * Any change in order or content must be matched. | ||
767 | */ | ||
768 | static struct sensor_device_template *nct6683_attributes_fan_template[] = { | ||
769 | &sensor_dev_template_fan_input, | ||
770 | &sensor_dev_template_fan_pulses, | ||
771 | &sensor_dev_template_fan_min, | ||
772 | NULL | ||
773 | }; | ||
774 | |||
775 | static struct sensor_template_group nct6683_fan_template_group = { | ||
776 | .templates = nct6683_attributes_fan_template, | ||
777 | .is_visible = nct6683_fan_is_visible, | ||
778 | .base = 1, | ||
779 | }; | ||
780 | |||
781 | static ssize_t | ||
782 | show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) | ||
783 | { | ||
784 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
785 | struct nct6683_data *data = nct6683_update_device(dev); | ||
786 | int nr = sattr->index; | ||
787 | |||
788 | return sprintf(buf, "%s\n", nct6683_mon_label[data->temp_src[nr]]); | ||
789 | } | ||
790 | |||
791 | static ssize_t | ||
792 | show_temp8(struct device *dev, struct device_attribute *attr, char *buf) | ||
793 | { | ||
794 | struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | ||
795 | struct nct6683_data *data = nct6683_update_device(dev); | ||
796 | int index = sattr->index; | ||
797 | int nr = sattr->nr; | ||
798 | |||
799 | return sprintf(buf, "%d\n", data->temp[index][nr] * 1000); | ||
800 | } | ||
801 | |||
802 | static ssize_t | ||
803 | show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf) | ||
804 | { | ||
805 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
806 | struct nct6683_data *data = nct6683_update_device(dev); | ||
807 | int nr = sattr->index; | ||
808 | int temp = data->temp[1][nr] - data->temp[2][nr]; | ||
809 | |||
810 | return sprintf(buf, "%d\n", temp * 1000); | ||
811 | } | ||
812 | |||
813 | static ssize_t | ||
814 | show_temp16(struct device *dev, struct device_attribute *attr, char *buf) | ||
815 | { | ||
816 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
817 | struct nct6683_data *data = nct6683_update_device(dev); | ||
818 | int index = sattr->index; | ||
819 | |||
820 | return sprintf(buf, "%d\n", (data->temp_in[index] / 128) * 500); | ||
821 | } | ||
822 | |||
823 | /* | ||
824 | * Temperature sensor type is determined by temperature source | ||
825 | * and can not be modified. | ||
826 | * 0x02..0x07: Thermal diode | ||
827 | * 0x08..0x18: Thermistor | ||
828 | * 0x20..0x2b: Intel PECI | ||
829 | * 0x42..0x49: AMD TSI | ||
830 | * Others are unspecified (not visible) | ||
831 | */ | ||
832 | |||
833 | static int get_temp_type(u8 src) | ||
834 | { | ||
835 | if (src >= 0x02 && src <= 0x07) | ||
836 | return 3; /* thermal diode */ | ||
837 | else if (src >= 0x08 && src <= 0x18) | ||
838 | return 4; /* thermistor */ | ||
839 | else if (src >= 0x20 && src <= 0x2b) | ||
840 | return 6; /* PECI */ | ||
841 | else if (src >= 0x42 && src <= 0x49) | ||
842 | return 5; | ||
843 | |||
844 | return 0; | ||
845 | } | ||
846 | |||
847 | static ssize_t | ||
848 | show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) | ||
849 | { | ||
850 | struct nct6683_data *data = nct6683_update_device(dev); | ||
851 | struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | ||
852 | int nr = sattr->index; | ||
853 | return sprintf(buf, "%d\n", get_temp_type(data->temp_src[nr])); | ||
854 | } | ||
855 | |||
856 | static umode_t nct6683_temp_is_visible(struct kobject *kobj, | ||
857 | struct attribute *attr, int index) | ||
858 | { | ||
859 | struct device *dev = container_of(kobj, struct device, kobj); | ||
860 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
861 | int temp = index / 7; /* temp index */ | ||
862 | int nr = index % 7; /* attribute index */ | ||
863 | |||
864 | /* | ||
865 | * Intel does not have low temperature limits or temperature hysteresis | ||
866 | * registers, or at least register location and encoding is unknown. | ||
867 | */ | ||
868 | if ((nr == 2 || nr == 4) && | ||
869 | data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | ||
870 | return 0; | ||
871 | |||
872 | if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0) | ||
873 | return 0; /* type */ | ||
874 | |||
875 | return attr->mode; | ||
876 | } | ||
877 | |||
878 | SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0); | ||
879 | SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); | ||
880 | SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0); | ||
881 | SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1); | ||
882 | SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL, | ||
883 | 0); | ||
884 | SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3); | ||
885 | SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0); | ||
886 | |||
887 | /* | ||
888 | * nct6683_temp_is_visible uses the index into the following array | ||
889 | * to determine if attributes should be created or not. | ||
890 | * Any change in order or content must be matched. | ||
891 | */ | ||
892 | static struct sensor_device_template *nct6683_attributes_temp_template[] = { | ||
893 | &sensor_dev_template_temp_input, | ||
894 | &sensor_dev_template_temp_label, | ||
895 | &sensor_dev_template_temp_min, /* 2 */ | ||
896 | &sensor_dev_template_temp_max, /* 3 */ | ||
897 | &sensor_dev_template_temp_max_hyst, /* 4 */ | ||
898 | &sensor_dev_template_temp_crit, /* 5 */ | ||
899 | &sensor_dev_template_temp_type, /* 6 */ | ||
900 | NULL | ||
901 | }; | ||
902 | |||
903 | static struct sensor_template_group nct6683_temp_template_group = { | ||
904 | .templates = nct6683_attributes_temp_template, | ||
905 | .is_visible = nct6683_temp_is_visible, | ||
906 | .base = 1, | ||
907 | }; | ||
908 | |||
909 | static ssize_t | ||
910 | show_pwm(struct device *dev, struct device_attribute *attr, char *buf) | ||
911 | { | ||
912 | struct nct6683_data *data = nct6683_update_device(dev); | ||
913 | struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | ||
914 | int index = sattr->index; | ||
915 | |||
916 | return sprintf(buf, "%d\n", data->pwm[index]); | ||
917 | } | ||
918 | |||
919 | SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, NULL, 0); | ||
920 | |||
921 | static umode_t nct6683_pwm_is_visible(struct kobject *kobj, | ||
922 | struct attribute *attr, int index) | ||
923 | { | ||
924 | struct device *dev = container_of(kobj, struct device, kobj); | ||
925 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
926 | int pwm = index; /* pwm index */ | ||
927 | |||
928 | if (!(data->have_pwm & (1 << pwm))) | ||
929 | return 0; | ||
930 | |||
931 | return attr->mode; | ||
932 | } | ||
933 | |||
934 | static struct sensor_device_template *nct6683_attributes_pwm_template[] = { | ||
935 | &sensor_dev_template_pwm, | ||
936 | NULL | ||
937 | }; | ||
938 | |||
939 | static struct sensor_template_group nct6683_pwm_template_group = { | ||
940 | .templates = nct6683_attributes_pwm_template, | ||
941 | .is_visible = nct6683_pwm_is_visible, | ||
942 | .base = 1, | ||
943 | }; | ||
944 | |||
945 | static ssize_t | ||
946 | show_global_beep(struct device *dev, struct device_attribute *attr, char *buf) | ||
947 | { | ||
948 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
949 | int ret; | ||
950 | u8 reg; | ||
951 | |||
952 | mutex_lock(&data->update_lock); | ||
953 | |||
954 | ret = superio_enter(data->sioreg); | ||
955 | if (ret) | ||
956 | goto error; | ||
957 | superio_select(data->sioreg, NCT6683_LD_HWM); | ||
958 | reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); | ||
959 | superio_exit(data->sioreg); | ||
960 | |||
961 | mutex_unlock(&data->update_lock); | ||
962 | |||
963 | return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK)); | ||
964 | |||
965 | error: | ||
966 | mutex_unlock(&data->update_lock); | ||
967 | return ret; | ||
968 | } | ||
969 | |||
970 | static ssize_t | ||
971 | store_global_beep(struct device *dev, struct device_attribute *attr, | ||
972 | const char *buf, size_t count) | ||
973 | { | ||
974 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
975 | unsigned long val; | ||
976 | u8 reg; | ||
977 | int ret; | ||
978 | |||
979 | if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1)) | ||
980 | return -EINVAL; | ||
981 | |||
982 | mutex_lock(&data->update_lock); | ||
983 | |||
984 | ret = superio_enter(data->sioreg); | ||
985 | if (ret) { | ||
986 | count = ret; | ||
987 | goto error; | ||
988 | } | ||
989 | |||
990 | superio_select(data->sioreg, NCT6683_LD_HWM); | ||
991 | reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); | ||
992 | if (val) | ||
993 | reg |= NCT6683_CR_BEEP_MASK; | ||
994 | else | ||
995 | reg &= ~NCT6683_CR_BEEP_MASK; | ||
996 | superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg); | ||
997 | superio_exit(data->sioreg); | ||
998 | error: | ||
999 | mutex_unlock(&data->update_lock); | ||
1000 | return count; | ||
1001 | } | ||
1002 | |||
1003 | /* Case open detection */ | ||
1004 | |||
1005 | static ssize_t | ||
1006 | show_caseopen(struct device *dev, struct device_attribute *attr, char *buf) | ||
1007 | { | ||
1008 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
1009 | int ret; | ||
1010 | u8 reg; | ||
1011 | |||
1012 | mutex_lock(&data->update_lock); | ||
1013 | |||
1014 | ret = superio_enter(data->sioreg); | ||
1015 | if (ret) | ||
1016 | goto error; | ||
1017 | superio_select(data->sioreg, NCT6683_LD_ACPI); | ||
1018 | reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); | ||
1019 | superio_exit(data->sioreg); | ||
1020 | |||
1021 | mutex_unlock(&data->update_lock); | ||
1022 | |||
1023 | return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK)); | ||
1024 | |||
1025 | error: | ||
1026 | mutex_unlock(&data->update_lock); | ||
1027 | return ret; | ||
1028 | } | ||
1029 | |||
1030 | static ssize_t | ||
1031 | clear_caseopen(struct device *dev, struct device_attribute *attr, | ||
1032 | const char *buf, size_t count) | ||
1033 | { | ||
1034 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
1035 | unsigned long val; | ||
1036 | u8 reg; | ||
1037 | int ret; | ||
1038 | |||
1039 | if (kstrtoul(buf, 10, &val) || val != 0) | ||
1040 | return -EINVAL; | ||
1041 | |||
1042 | mutex_lock(&data->update_lock); | ||
1043 | |||
1044 | /* | ||
1045 | * Use CR registers to clear caseopen status. | ||
1046 | * Caseopen is activ low, clear by writing 1 into the register. | ||
1047 | */ | ||
1048 | |||
1049 | ret = superio_enter(data->sioreg); | ||
1050 | if (ret) { | ||
1051 | count = ret; | ||
1052 | goto error; | ||
1053 | } | ||
1054 | |||
1055 | superio_select(data->sioreg, NCT6683_LD_ACPI); | ||
1056 | reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); | ||
1057 | reg |= NCT6683_CR_CASEOPEN_MASK; | ||
1058 | superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); | ||
1059 | reg &= ~NCT6683_CR_CASEOPEN_MASK; | ||
1060 | superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); | ||
1061 | superio_exit(data->sioreg); | ||
1062 | |||
1063 | data->valid = false; /* Force cache refresh */ | ||
1064 | error: | ||
1065 | mutex_unlock(&data->update_lock); | ||
1066 | return count; | ||
1067 | } | ||
1068 | |||
1069 | static DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen, | ||
1070 | clear_caseopen); | ||
1071 | static DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_global_beep, | ||
1072 | store_global_beep); | ||
1073 | |||
1074 | static struct attribute *nct6683_attributes_other[] = { | ||
1075 | &dev_attr_intrusion0_alarm.attr, | ||
1076 | &dev_attr_beep_enable.attr, | ||
1077 | NULL | ||
1078 | }; | ||
1079 | |||
1080 | static const struct attribute_group nct6683_group_other = { | ||
1081 | .attrs = nct6683_attributes_other, | ||
1082 | }; | ||
1083 | |||
1084 | /* Get the monitoring functions started */ | ||
1085 | static inline void nct6683_init_device(struct nct6683_data *data) | ||
1086 | { | ||
1087 | u8 tmp; | ||
1088 | |||
1089 | /* Start hardware monitoring if needed */ | ||
1090 | tmp = nct6683_read(data, NCT6683_HWM_CFG); | ||
1091 | if (!(tmp & 0x80)) | ||
1092 | nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80); | ||
1093 | } | ||
1094 | |||
1095 | /* | ||
1096 | * There are a total of 24 fan inputs. Each can be configured as input | ||
1097 | * or as output. A maximum of 16 inputs and 8 outputs is configurable. | ||
1098 | */ | ||
1099 | static void | ||
1100 | nct6683_setup_fans(struct nct6683_data *data) | ||
1101 | { | ||
1102 | int i; | ||
1103 | u8 reg; | ||
1104 | |||
1105 | for (i = 0; i < NCT6683_NUM_REG_FAN; i++) { | ||
1106 | reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i)); | ||
1107 | if (reg & 0x80) | ||
1108 | data->have_fan |= 1 << i; | ||
1109 | data->fanin_cfg[i] = reg; | ||
1110 | } | ||
1111 | for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { | ||
1112 | reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i)); | ||
1113 | if (reg & 0x80) | ||
1114 | data->have_pwm |= 1 << i; | ||
1115 | data->fanout_cfg[i] = reg; | ||
1116 | } | ||
1117 | } | ||
1118 | |||
1119 | /* | ||
1120 | * Translation from monitoring register to temperature and voltage attributes | ||
1121 | * ========================================================================== | ||
1122 | * | ||
1123 | * There are a total of 32 monitoring registers. Each can be assigned to either | ||
1124 | * a temperature or voltage monitoring source. | ||
1125 | * NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source. | ||
1126 | * | ||
1127 | * Temperature and voltage attribute mapping is determined by walking through | ||
1128 | * the NCT6683_REG_MON_CFG registers. If the assigned source is | ||
1129 | * a temperature, temp_index[n] is set to the monitor register index, and | ||
1130 | * temp_src[n] is set to the temperature source. If the assigned source is | ||
1131 | * a voltage, the respective values are stored in in_index[] and in_src[], | ||
1132 | * respectively. | ||
1133 | */ | ||
1134 | |||
1135 | static void nct6683_setup_sensors(struct nct6683_data *data) | ||
1136 | { | ||
1137 | u8 reg; | ||
1138 | int i; | ||
1139 | |||
1140 | data->temp_num = 0; | ||
1141 | data->in_num = 0; | ||
1142 | for (i = 0; i < NCT6683_NUM_REG_MON; i++) { | ||
1143 | reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f; | ||
1144 | /* Ignore invalid assignments */ | ||
1145 | if (reg >= NUM_MON_LABELS) | ||
1146 | continue; | ||
1147 | /* Skip if disabled or reserved */ | ||
1148 | if (nct6683_mon_label[reg] == NULL) | ||
1149 | continue; | ||
1150 | if (reg < MON_VOLTAGE_START) { | ||
1151 | data->temp_index[data->temp_num] = i; | ||
1152 | data->temp_src[data->temp_num] = reg; | ||
1153 | data->temp_num++; | ||
1154 | } else { | ||
1155 | data->in_index[data->in_num] = i; | ||
1156 | data->in_src[data->in_num] = reg; | ||
1157 | data->in_num++; | ||
1158 | } | ||
1159 | } | ||
1160 | } | ||
1161 | |||
1162 | static int nct6683_probe(struct platform_device *pdev) | ||
1163 | { | ||
1164 | struct device *dev = &pdev->dev; | ||
1165 | struct nct6683_sio_data *sio_data = dev->platform_data; | ||
1166 | struct attribute_group *group; | ||
1167 | struct nct6683_data *data; | ||
1168 | struct device *hwmon_dev; | ||
1169 | struct resource *res; | ||
1170 | int groups = 0; | ||
1171 | |||
1172 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | ||
1173 | if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) | ||
1174 | return -EBUSY; | ||
1175 | |||
1176 | data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL); | ||
1177 | if (!data) | ||
1178 | return -ENOMEM; | ||
1179 | |||
1180 | data->kind = sio_data->kind; | ||
1181 | data->sioreg = sio_data->sioreg; | ||
1182 | data->addr = res->start; | ||
1183 | mutex_init(&data->update_lock); | ||
1184 | platform_set_drvdata(pdev, data); | ||
1185 | |||
1186 | data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID); | ||
1187 | |||
1188 | nct6683_init_device(data); | ||
1189 | nct6683_setup_fans(data); | ||
1190 | nct6683_setup_sensors(data); | ||
1191 | |||
1192 | /* Register sysfs hooks */ | ||
1193 | |||
1194 | if (data->have_pwm) { | ||
1195 | group = nct6683_create_attr_group(dev, | ||
1196 | &nct6683_pwm_template_group, | ||
1197 | fls(data->have_pwm)); | ||
1198 | if (IS_ERR(group)) | ||
1199 | return PTR_ERR(group); | ||
1200 | data->groups[groups++] = group; | ||
1201 | } | ||
1202 | |||
1203 | if (data->in_num) { | ||
1204 | group = nct6683_create_attr_group(dev, | ||
1205 | &nct6683_in_template_group, | ||
1206 | data->in_num); | ||
1207 | if (IS_ERR(group)) | ||
1208 | return PTR_ERR(group); | ||
1209 | data->groups[groups++] = group; | ||
1210 | } | ||
1211 | |||
1212 | if (data->have_fan) { | ||
1213 | group = nct6683_create_attr_group(dev, | ||
1214 | &nct6683_fan_template_group, | ||
1215 | fls(data->have_fan)); | ||
1216 | if (IS_ERR(group)) | ||
1217 | return PTR_ERR(group); | ||
1218 | data->groups[groups++] = group; | ||
1219 | } | ||
1220 | |||
1221 | if (data->temp_num) { | ||
1222 | group = nct6683_create_attr_group(dev, | ||
1223 | &nct6683_temp_template_group, | ||
1224 | data->temp_num); | ||
1225 | if (IS_ERR(group)) | ||
1226 | return PTR_ERR(group); | ||
1227 | data->groups[groups++] = group; | ||
1228 | } | ||
1229 | data->groups[groups++] = &nct6683_group_other; | ||
1230 | |||
1231 | dev_info(dev, "%s EC firmware version %d.%d build %02x/%02x/%02x\n", | ||
1232 | nct6683_chip_names[data->kind], | ||
1233 | nct6683_read(data, NCT6683_REG_VERSION_HI), | ||
1234 | nct6683_read(data, NCT6683_REG_VERSION_LO), | ||
1235 | nct6683_read(data, NCT6683_REG_BUILD_MONTH), | ||
1236 | nct6683_read(data, NCT6683_REG_BUILD_DAY), | ||
1237 | nct6683_read(data, NCT6683_REG_BUILD_YEAR)); | ||
1238 | |||
1239 | hwmon_dev = devm_hwmon_device_register_with_groups(dev, | ||
1240 | nct6683_device_names[data->kind], data, data->groups); | ||
1241 | return PTR_ERR_OR_ZERO(hwmon_dev); | ||
1242 | } | ||
1243 | |||
1244 | #ifdef CONFIG_PM | ||
1245 | static int nct6683_suspend(struct device *dev) | ||
1246 | { | ||
1247 | struct nct6683_data *data = nct6683_update_device(dev); | ||
1248 | |||
1249 | mutex_lock(&data->update_lock); | ||
1250 | data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG); | ||
1251 | mutex_unlock(&data->update_lock); | ||
1252 | |||
1253 | return 0; | ||
1254 | } | ||
1255 | |||
1256 | static int nct6683_resume(struct device *dev) | ||
1257 | { | ||
1258 | struct nct6683_data *data = dev_get_drvdata(dev); | ||
1259 | |||
1260 | mutex_lock(&data->update_lock); | ||
1261 | |||
1262 | nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg); | ||
1263 | |||
1264 | /* Force re-reading all values */ | ||
1265 | data->valid = false; | ||
1266 | mutex_unlock(&data->update_lock); | ||
1267 | |||
1268 | return 0; | ||
1269 | } | ||
1270 | |||
1271 | static const struct dev_pm_ops nct6683_dev_pm_ops = { | ||
1272 | .suspend = nct6683_suspend, | ||
1273 | .resume = nct6683_resume, | ||
1274 | .freeze = nct6683_suspend, | ||
1275 | .restore = nct6683_resume, | ||
1276 | }; | ||
1277 | |||
1278 | #define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops) | ||
1279 | #else | ||
1280 | #define NCT6683_DEV_PM_OPS NULL | ||
1281 | #endif /* CONFIG_PM */ | ||
1282 | |||
1283 | static struct platform_driver nct6683_driver = { | ||
1284 | .driver = { | ||
1285 | .owner = THIS_MODULE, | ||
1286 | .name = DRVNAME, | ||
1287 | .pm = NCT6683_DEV_PM_OPS, | ||
1288 | }, | ||
1289 | .probe = nct6683_probe, | ||
1290 | }; | ||
1291 | |||
1292 | static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) | ||
1293 | { | ||
1294 | const char *board_vendor; | ||
1295 | int addr; | ||
1296 | u16 val; | ||
1297 | int err; | ||
1298 | |||
1299 | /* | ||
1300 | * Only run on Intel boards unless the 'force' module parameter is set | ||
1301 | */ | ||
1302 | if (!force) { | ||
1303 | board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); | ||
1304 | if (!board_vendor || strcmp(board_vendor, "Intel Corporation")) | ||
1305 | return -ENODEV; | ||
1306 | } | ||
1307 | |||
1308 | err = superio_enter(sioaddr); | ||
1309 | if (err) | ||
1310 | return err; | ||
1311 | |||
1312 | val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | ||
1313 | | superio_inb(sioaddr, SIO_REG_DEVID + 1); | ||
1314 | |||
1315 | switch (val & SIO_ID_MASK) { | ||
1316 | case SIO_NCT6683_ID: | ||
1317 | sio_data->kind = nct6683; | ||
1318 | break; | ||
1319 | default: | ||
1320 | if (val != 0xffff) | ||
1321 | pr_debug("unsupported chip ID: 0x%04x\n", val); | ||
1322 | goto fail; | ||
1323 | } | ||
1324 | |||
1325 | /* We have a known chip, find the HWM I/O address */ | ||
1326 | superio_select(sioaddr, NCT6683_LD_HWM); | ||
1327 | val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) | ||
1328 | | superio_inb(sioaddr, SIO_REG_ADDR + 1); | ||
1329 | addr = val & IOREGION_ALIGNMENT; | ||
1330 | if (addr == 0) { | ||
1331 | pr_err("EC base I/O port unconfigured\n"); | ||
1332 | goto fail; | ||
1333 | } | ||
1334 | |||
1335 | /* Activate logical device if needed */ | ||
1336 | val = superio_inb(sioaddr, SIO_REG_ENABLE); | ||
1337 | if (!(val & 0x01)) { | ||
1338 | pr_err("EC is disabled\n"); | ||
1339 | goto fail; | ||
1340 | } | ||
1341 | |||
1342 | superio_exit(sioaddr); | ||
1343 | pr_info("Found %s or compatible chip at %#x:%#x\n", | ||
1344 | nct6683_chip_names[sio_data->kind], sioaddr, addr); | ||
1345 | sio_data->sioreg = sioaddr; | ||
1346 | |||
1347 | return addr; | ||
1348 | |||
1349 | fail: | ||
1350 | superio_exit(sioaddr); | ||
1351 | return -ENODEV; | ||
1352 | } | ||
1353 | |||
1354 | /* | ||
1355 | * when Super-I/O functions move to a separate file, the Super-I/O | ||
1356 | * bus will manage the lifetime of the device and this module will only keep | ||
1357 | * track of the nct6683 driver. But since we use platform_device_alloc(), we | ||
1358 | * must keep track of the device | ||
1359 | */ | ||
1360 | static struct platform_device *pdev[2]; | ||
1361 | |||
1362 | static int __init sensors_nct6683_init(void) | ||
1363 | { | ||
1364 | struct nct6683_sio_data sio_data; | ||
1365 | int sioaddr[2] = { 0x2e, 0x4e }; | ||
1366 | struct resource res; | ||
1367 | bool found = false; | ||
1368 | int address; | ||
1369 | int i, err; | ||
1370 | |||
1371 | err = platform_driver_register(&nct6683_driver); | ||
1372 | if (err) | ||
1373 | return err; | ||
1374 | |||
1375 | /* | ||
1376 | * initialize sio_data->kind and sio_data->sioreg. | ||
1377 | * | ||
1378 | * when Super-I/O functions move to a separate file, the Super-I/O | ||
1379 | * driver will probe 0x2e and 0x4e and auto-detect the presence of a | ||
1380 | * nct6683 hardware monitor, and call probe() | ||
1381 | */ | ||
1382 | for (i = 0; i < ARRAY_SIZE(pdev); i++) { | ||
1383 | address = nct6683_find(sioaddr[i], &sio_data); | ||
1384 | if (address <= 0) | ||
1385 | continue; | ||
1386 | |||
1387 | found = true; | ||
1388 | |||
1389 | pdev[i] = platform_device_alloc(DRVNAME, address); | ||
1390 | if (!pdev[i]) { | ||
1391 | err = -ENOMEM; | ||
1392 | goto exit_device_put; | ||
1393 | } | ||
1394 | |||
1395 | err = platform_device_add_data(pdev[i], &sio_data, | ||
1396 | sizeof(struct nct6683_sio_data)); | ||
1397 | if (err) | ||
1398 | goto exit_device_put; | ||
1399 | |||
1400 | memset(&res, 0, sizeof(res)); | ||
1401 | res.name = DRVNAME; | ||
1402 | res.start = address + IOREGION_OFFSET; | ||
1403 | res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; | ||
1404 | res.flags = IORESOURCE_IO; | ||
1405 | |||
1406 | err = acpi_check_resource_conflict(&res); | ||
1407 | if (err) { | ||
1408 | platform_device_put(pdev[i]); | ||
1409 | pdev[i] = NULL; | ||
1410 | continue; | ||
1411 | } | ||
1412 | |||
1413 | err = platform_device_add_resources(pdev[i], &res, 1); | ||
1414 | if (err) | ||
1415 | goto exit_device_put; | ||
1416 | |||
1417 | /* platform_device_add calls probe() */ | ||
1418 | err = platform_device_add(pdev[i]); | ||
1419 | if (err) | ||
1420 | goto exit_device_put; | ||
1421 | } | ||
1422 | if (!found) { | ||
1423 | err = -ENODEV; | ||
1424 | goto exit_unregister; | ||
1425 | } | ||
1426 | |||
1427 | return 0; | ||
1428 | |||
1429 | exit_device_put: | ||
1430 | for (i = 0; i < ARRAY_SIZE(pdev); i++) { | ||
1431 | if (pdev[i]) | ||
1432 | platform_device_put(pdev[i]); | ||
1433 | } | ||
1434 | exit_unregister: | ||
1435 | platform_driver_unregister(&nct6683_driver); | ||
1436 | return err; | ||
1437 | } | ||
1438 | |||
1439 | static void __exit sensors_nct6683_exit(void) | ||
1440 | { | ||
1441 | int i; | ||
1442 | |||
1443 | for (i = 0; i < ARRAY_SIZE(pdev); i++) { | ||
1444 | if (pdev[i]) | ||
1445 | platform_device_unregister(pdev[i]); | ||
1446 | } | ||
1447 | platform_driver_unregister(&nct6683_driver); | ||
1448 | } | ||
1449 | |||
1450 | MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); | ||
1451 | MODULE_DESCRIPTION("NCT6683D driver"); | ||
1452 | MODULE_LICENSE("GPL"); | ||
1453 | |||
1454 | module_init(sensors_nct6683_init); | ||
1455 | module_exit(sensors_nct6683_exit); | ||