diff options
-rw-r--r-- | Documentation/hwmon/sysfs-interface | 18 | ||||
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/hwmon/Kconfig | 10 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/f71805f.c | 908 |
5 files changed, 942 insertions, 1 deletions
diff --git a/Documentation/hwmon/sysfs-interface b/Documentation/hwmon/sysfs-interface index 764cdc5480e7..a0d0ab24288e 100644 --- a/Documentation/hwmon/sysfs-interface +++ b/Documentation/hwmon/sysfs-interface | |||
@@ -179,11 +179,12 @@ temp[1-*]_auto_point[1-*]_temp_hyst | |||
179 | **************** | 179 | **************** |
180 | 180 | ||
181 | temp[1-3]_type Sensor type selection. | 181 | temp[1-3]_type Sensor type selection. |
182 | Integers 1, 2, 3 or thermistor Beta value (3435) | 182 | Integers 1 to 4 or thermistor Beta value (typically 3435) |
183 | Read/Write. | 183 | Read/Write. |
184 | 1: PII/Celeron Diode | 184 | 1: PII/Celeron Diode |
185 | 2: 3904 transistor | 185 | 2: 3904 transistor |
186 | 3: thermal diode | 186 | 3: thermal diode |
187 | 4: thermistor (default/unknown Beta) | ||
187 | Not all types are supported by all chips | 188 | Not all types are supported by all chips |
188 | 189 | ||
189 | temp[1-4]_max Temperature max value. | 190 | temp[1-4]_max Temperature max value. |
@@ -261,6 +262,21 @@ alarms Alarm bitmask. | |||
261 | of individual bits. | 262 | of individual bits. |
262 | Bits are defined in kernel/include/sensors.h. | 263 | Bits are defined in kernel/include/sensors.h. |
263 | 264 | ||
265 | alarms_in Alarm bitmask relative to in (voltage) channels | ||
266 | Read only | ||
267 | A '1' bit means an alarm, LSB corresponds to in0 and so on | ||
268 | Prefered to 'alarms' for newer chips | ||
269 | |||
270 | alarms_fan Alarm bitmask relative to fan channels | ||
271 | Read only | ||
272 | A '1' bit means an alarm, LSB corresponds to fan1 and so on | ||
273 | Prefered to 'alarms' for newer chips | ||
274 | |||
275 | alarms_temp Alarm bitmask relative to temp (temperature) channels | ||
276 | Read only | ||
277 | A '1' bit means an alarm, LSB corresponds to temp1 and so on | ||
278 | Prefered to 'alarms' for newer chips | ||
279 | |||
264 | beep_enable Beep/interrupt enable | 280 | beep_enable Beep/interrupt enable |
265 | 0 to disable. | 281 | 0 to disable. |
266 | 1 to enable. | 282 | 1 to enable. |
diff --git a/MAINTAINERS b/MAINTAINERS index 11d44daa6025..5b7a154d4432 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -929,6 +929,12 @@ M: sct@redhat.com, akpm@osdl.org, adilger@clusterfs.com | |||
929 | L: ext3-users@redhat.com | 929 | L: ext3-users@redhat.com |
930 | S: Maintained | 930 | S: Maintained |
931 | 931 | ||
932 | F71805F HARDWARE MONITORING DRIVER | ||
933 | P: Jean Delvare | ||
934 | M: khali@linux-fr.org | ||
935 | L: lm-sensors@lm-sensors.org | ||
936 | S: Maintained | ||
937 | |||
932 | FARSYNC SYNCHRONOUS DRIVER | 938 | FARSYNC SYNCHRONOUS DRIVER |
933 | P: Kevin Curtis | 939 | P: Kevin Curtis |
934 | M: kevin.curtis@farsite.co.uk | 940 | M: kevin.curtis@farsite.co.uk |
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c58295914365..7230d4e08196 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -113,6 +113,16 @@ config SENSORS_DS1621 | |||
113 | This driver can also be built as a module. If so, the module | 113 | This driver can also be built as a module. If so, the module |
114 | will be called ds1621. | 114 | will be called ds1621. |
115 | 115 | ||
116 | config SENSORS_F71805F | ||
117 | tristate "Fintek F71805F/FG" | ||
118 | depends on HWMON && EXPERIMENTAL | ||
119 | help | ||
120 | If you say yes here you get support for hardware monitoring | ||
121 | features of the Fintek F71805F/FG chips. | ||
122 | |||
123 | This driver can also be built as a module. If so, the module | ||
124 | will be called f71805f. | ||
125 | |||
116 | config SENSORS_FSCHER | 126 | config SENSORS_FSCHER |
117 | tristate "FSC Hermes" | 127 | tristate "FSC Hermes" |
118 | depends on HWMON && I2C && EXPERIMENTAL | 128 | depends on HWMON && I2C && EXPERIMENTAL |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 06d4a1d14105..fbdb8d911a72 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -18,6 +18,7 @@ obj-$(CONFIG_SENSORS_ADM1031) += adm1031.o | |||
18 | obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o | 18 | obj-$(CONFIG_SENSORS_ADM9240) += adm9240.o |
19 | obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o | 19 | obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o |
20 | obj-$(CONFIG_SENSORS_DS1621) += ds1621.o | 20 | obj-$(CONFIG_SENSORS_DS1621) += ds1621.o |
21 | obj-$(CONFIG_SENSORS_F71805F) += f71805f.o | ||
21 | obj-$(CONFIG_SENSORS_FSCHER) += fscher.o | 22 | obj-$(CONFIG_SENSORS_FSCHER) += fscher.o |
22 | obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o | 23 | obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o |
23 | obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o | 24 | obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o |
diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c new file mode 100644 index 000000000000..e029e0a94ecc --- /dev/null +++ b/drivers/hwmon/f71805f.c | |||
@@ -0,0 +1,908 @@ | |||
1 | /* | ||
2 | * f71805f.c - driver for the Fintek F71805F/FG Super-I/O chip integrated | ||
3 | * hardware monitoring features | ||
4 | * Copyright (C) 2005 Jean Delvare <khali@linux-fr.org> | ||
5 | * | ||
6 | * The F71805F/FG is a LPC Super-I/O chip made by Fintek. It integrates | ||
7 | * complete hardware monitoring features: voltage, fan and temperature | ||
8 | * sensors, and manual and automatic fan speed control. | ||
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 | * You should have received a copy of the GNU General Public License | ||
21 | * along with this program; if not, write to the Free Software | ||
22 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
23 | */ | ||
24 | |||
25 | #include <linux/module.h> | ||
26 | #include <linux/init.h> | ||
27 | #include <linux/slab.h> | ||
28 | #include <linux/jiffies.h> | ||
29 | #include <linux/platform_device.h> | ||
30 | #include <linux/hwmon.h> | ||
31 | #include <linux/hwmon-sysfs.h> | ||
32 | #include <linux/err.h> | ||
33 | #include <asm/io.h> | ||
34 | |||
35 | static struct platform_device *pdev; | ||
36 | |||
37 | #define DRVNAME "f71805f" | ||
38 | |||
39 | /* | ||
40 | * Super-I/O constants and functions | ||
41 | */ | ||
42 | |||
43 | #define F71805F_LD_HWM 0x04 | ||
44 | |||
45 | #define SIO_REG_LDSEL 0x07 /* Logical device select */ | ||
46 | #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ | ||
47 | #define SIO_REG_DEVREV 0x22 /* Device revision */ | ||
48 | #define SIO_REG_MANID 0x23 /* Fintek ID (2 bytes) */ | ||
49 | #define SIO_REG_ENABLE 0x30 /* Logical device enable */ | ||
50 | #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ | ||
51 | |||
52 | #define SIO_FINTEK_ID 0x1934 | ||
53 | #define SIO_F71805F_ID 0x0406 | ||
54 | |||
55 | static inline int | ||
56 | superio_inb(int base, int reg) | ||
57 | { | ||
58 | outb(reg, base); | ||
59 | return inb(base + 1); | ||
60 | } | ||
61 | |||
62 | static int | ||
63 | superio_inw(int base, int reg) | ||
64 | { | ||
65 | int val; | ||
66 | outb(reg++, base); | ||
67 | val = inb(base + 1) << 8; | ||
68 | outb(reg, base); | ||
69 | val |= inb(base + 1); | ||
70 | return val; | ||
71 | } | ||
72 | |||
73 | static inline void | ||
74 | superio_select(int base, int ld) | ||
75 | { | ||
76 | outb(SIO_REG_LDSEL, base); | ||
77 | outb(ld, base + 1); | ||
78 | } | ||
79 | |||
80 | static inline void | ||
81 | superio_enter(int base) | ||
82 | { | ||
83 | outb(0x87, base); | ||
84 | outb(0x87, base); | ||
85 | } | ||
86 | |||
87 | static inline void | ||
88 | superio_exit(int base) | ||
89 | { | ||
90 | outb(0xaa, base); | ||
91 | } | ||
92 | |||
93 | /* | ||
94 | * ISA constants | ||
95 | */ | ||
96 | |||
97 | #define REGION_LENGTH 2 | ||
98 | #define ADDR_REG_OFFSET 0 | ||
99 | #define DATA_REG_OFFSET 1 | ||
100 | |||
101 | static struct resource f71805f_resource __initdata = { | ||
102 | .flags = IORESOURCE_IO, | ||
103 | }; | ||
104 | |||
105 | /* | ||
106 | * Registers | ||
107 | */ | ||
108 | |||
109 | /* in nr from 0 to 8 (8-bit values) */ | ||
110 | #define F71805F_REG_IN(nr) (0x10 + (nr)) | ||
111 | #define F71805F_REG_IN_HIGH(nr) (0x40 + 2 * (nr)) | ||
112 | #define F71805F_REG_IN_LOW(nr) (0x41 + 2 * (nr)) | ||
113 | /* fan nr from 0 to 2 (12-bit values, two registers) */ | ||
114 | #define F71805F_REG_FAN(nr) (0x20 + 2 * (nr)) | ||
115 | #define F71805F_REG_FAN_LOW(nr) (0x28 + 2 * (nr)) | ||
116 | #define F71805F_REG_FAN_CTRL(nr) (0x60 + 16 * (nr)) | ||
117 | /* temp nr from 0 to 2 (8-bit values) */ | ||
118 | #define F71805F_REG_TEMP(nr) (0x1B + (nr)) | ||
119 | #define F71805F_REG_TEMP_HIGH(nr) (0x54 + 2 * (nr)) | ||
120 | #define F71805F_REG_TEMP_HYST(nr) (0x55 + 2 * (nr)) | ||
121 | #define F71805F_REG_TEMP_MODE 0x01 | ||
122 | |||
123 | #define F71805F_REG_START 0x00 | ||
124 | /* status nr from 0 to 2 */ | ||
125 | #define F71805F_REG_STATUS(nr) (0x36 + (nr)) | ||
126 | |||
127 | /* | ||
128 | * Data structures and manipulation thereof | ||
129 | */ | ||
130 | |||
131 | struct f71805f_data { | ||
132 | unsigned short addr; | ||
133 | const char *name; | ||
134 | struct semaphore lock; | ||
135 | struct class_device *class_dev; | ||
136 | |||
137 | struct semaphore update_lock; | ||
138 | char valid; /* !=0 if following fields are valid */ | ||
139 | unsigned long last_updated; /* In jiffies */ | ||
140 | unsigned long last_limits; /* In jiffies */ | ||
141 | |||
142 | /* Register values */ | ||
143 | u8 in[9]; | ||
144 | u8 in_high[9]; | ||
145 | u8 in_low[9]; | ||
146 | u16 fan[3]; | ||
147 | u16 fan_low[3]; | ||
148 | u8 fan_enabled; /* Read once at init time */ | ||
149 | u8 temp[3]; | ||
150 | u8 temp_high[3]; | ||
151 | u8 temp_hyst[3]; | ||
152 | u8 temp_mode; | ||
153 | u8 alarms[3]; | ||
154 | }; | ||
155 | |||
156 | static inline long in_from_reg(u8 reg) | ||
157 | { | ||
158 | return (reg * 8); | ||
159 | } | ||
160 | |||
161 | /* The 2 least significant bits are not used */ | ||
162 | static inline u8 in_to_reg(long val) | ||
163 | { | ||
164 | if (val <= 0) | ||
165 | return 0; | ||
166 | if (val >= 2016) | ||
167 | return 0xfc; | ||
168 | return (((val + 16) / 32) << 2); | ||
169 | } | ||
170 | |||
171 | /* in0 is downscaled by a factor 2 internally */ | ||
172 | static inline long in0_from_reg(u8 reg) | ||
173 | { | ||
174 | return (reg * 16); | ||
175 | } | ||
176 | |||
177 | static inline u8 in0_to_reg(long val) | ||
178 | { | ||
179 | if (val <= 0) | ||
180 | return 0; | ||
181 | if (val >= 4032) | ||
182 | return 0xfc; | ||
183 | return (((val + 32) / 64) << 2); | ||
184 | } | ||
185 | |||
186 | /* The 4 most significant bits are not used */ | ||
187 | static inline long fan_from_reg(u16 reg) | ||
188 | { | ||
189 | reg &= 0xfff; | ||
190 | if (!reg || reg == 0xfff) | ||
191 | return 0; | ||
192 | return (1500000 / reg); | ||
193 | } | ||
194 | |||
195 | static inline u16 fan_to_reg(long rpm) | ||
196 | { | ||
197 | /* If the low limit is set below what the chip can measure, | ||
198 | store the largest possible 12-bit value in the registers, | ||
199 | so that no alarm will ever trigger. */ | ||
200 | if (rpm < 367) | ||
201 | return 0xfff; | ||
202 | return (1500000 / rpm); | ||
203 | } | ||
204 | |||
205 | static inline long temp_from_reg(u8 reg) | ||
206 | { | ||
207 | return (reg * 1000); | ||
208 | } | ||
209 | |||
210 | static inline u8 temp_to_reg(long val) | ||
211 | { | ||
212 | if (val < 0) | ||
213 | val = 0; | ||
214 | else if (val > 1000 * 0xff) | ||
215 | val = 0xff; | ||
216 | return ((val + 500) / 1000); | ||
217 | } | ||
218 | |||
219 | /* | ||
220 | * Device I/O access | ||
221 | */ | ||
222 | |||
223 | static u8 f71805f_read8(struct f71805f_data *data, u8 reg) | ||
224 | { | ||
225 | u8 val; | ||
226 | |||
227 | down(&data->lock); | ||
228 | outb(reg, data->addr + ADDR_REG_OFFSET); | ||
229 | val = inb(data->addr + DATA_REG_OFFSET); | ||
230 | up(&data->lock); | ||
231 | |||
232 | return val; | ||
233 | } | ||
234 | |||
235 | static void f71805f_write8(struct f71805f_data *data, u8 reg, u8 val) | ||
236 | { | ||
237 | down(&data->lock); | ||
238 | outb(reg, data->addr + ADDR_REG_OFFSET); | ||
239 | outb(val, data->addr + DATA_REG_OFFSET); | ||
240 | up(&data->lock); | ||
241 | } | ||
242 | |||
243 | /* It is important to read the MSB first, because doing so latches the | ||
244 | value of the LSB, so we are sure both bytes belong to the same value. */ | ||
245 | static u16 f71805f_read16(struct f71805f_data *data, u8 reg) | ||
246 | { | ||
247 | u16 val; | ||
248 | |||
249 | down(&data->lock); | ||
250 | outb(reg, data->addr + ADDR_REG_OFFSET); | ||
251 | val = inb(data->addr + DATA_REG_OFFSET) << 8; | ||
252 | outb(++reg, data->addr + ADDR_REG_OFFSET); | ||
253 | val |= inb(data->addr + DATA_REG_OFFSET); | ||
254 | up(&data->lock); | ||
255 | |||
256 | return val; | ||
257 | } | ||
258 | |||
259 | static void f71805f_write16(struct f71805f_data *data, u8 reg, u16 val) | ||
260 | { | ||
261 | down(&data->lock); | ||
262 | outb(reg, data->addr + ADDR_REG_OFFSET); | ||
263 | outb(val >> 8, data->addr + DATA_REG_OFFSET); | ||
264 | outb(++reg, data->addr + ADDR_REG_OFFSET); | ||
265 | outb(val & 0xff, data->addr + DATA_REG_OFFSET); | ||
266 | up(&data->lock); | ||
267 | } | ||
268 | |||
269 | static struct f71805f_data *f71805f_update_device(struct device *dev) | ||
270 | { | ||
271 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
272 | int nr; | ||
273 | |||
274 | down(&data->update_lock); | ||
275 | |||
276 | /* Limit registers cache is refreshed after 60 seconds */ | ||
277 | if (time_after(jiffies, data->last_updated + 60 * HZ) | ||
278 | || !data->valid) { | ||
279 | for (nr = 0; nr < 9; nr++) { | ||
280 | data->in_high[nr] = f71805f_read8(data, | ||
281 | F71805F_REG_IN_HIGH(nr)); | ||
282 | data->in_low[nr] = f71805f_read8(data, | ||
283 | F71805F_REG_IN_LOW(nr)); | ||
284 | } | ||
285 | for (nr = 0; nr < 3; nr++) { | ||
286 | if (data->fan_enabled & (1 << nr)) | ||
287 | data->fan_low[nr] = f71805f_read16(data, | ||
288 | F71805F_REG_FAN_LOW(nr)); | ||
289 | } | ||
290 | for (nr = 0; nr < 3; nr++) { | ||
291 | data->temp_high[nr] = f71805f_read8(data, | ||
292 | F71805F_REG_TEMP_HIGH(nr)); | ||
293 | data->temp_hyst[nr] = f71805f_read8(data, | ||
294 | F71805F_REG_TEMP_HYST(nr)); | ||
295 | } | ||
296 | data->temp_mode = f71805f_read8(data, F71805F_REG_TEMP_MODE); | ||
297 | |||
298 | data->last_limits = jiffies; | ||
299 | } | ||
300 | |||
301 | /* Measurement registers cache is refreshed after 1 second */ | ||
302 | if (time_after(jiffies, data->last_updated + HZ) | ||
303 | || !data->valid) { | ||
304 | for (nr = 0; nr < 9; nr++) { | ||
305 | data->in[nr] = f71805f_read8(data, | ||
306 | F71805F_REG_IN(nr)); | ||
307 | } | ||
308 | for (nr = 0; nr < 3; nr++) { | ||
309 | if (data->fan_enabled & (1 << nr)) | ||
310 | data->fan[nr] = f71805f_read16(data, | ||
311 | F71805F_REG_FAN(nr)); | ||
312 | } | ||
313 | for (nr = 0; nr < 3; nr++) { | ||
314 | data->temp[nr] = f71805f_read8(data, | ||
315 | F71805F_REG_TEMP(nr)); | ||
316 | } | ||
317 | for (nr = 0; nr < 3; nr++) { | ||
318 | data->alarms[nr] = f71805f_read8(data, | ||
319 | F71805F_REG_STATUS(nr)); | ||
320 | } | ||
321 | |||
322 | data->last_updated = jiffies; | ||
323 | data->valid = 1; | ||
324 | } | ||
325 | |||
326 | up(&data->update_lock); | ||
327 | |||
328 | return data; | ||
329 | } | ||
330 | |||
331 | /* | ||
332 | * Sysfs interface | ||
333 | */ | ||
334 | |||
335 | static ssize_t show_in0(struct device *dev, struct device_attribute *devattr, | ||
336 | char *buf) | ||
337 | { | ||
338 | struct f71805f_data *data = f71805f_update_device(dev); | ||
339 | |||
340 | return sprintf(buf, "%ld\n", in0_from_reg(data->in[0])); | ||
341 | } | ||
342 | |||
343 | static ssize_t show_in0_max(struct device *dev, struct device_attribute | ||
344 | *devattr, char *buf) | ||
345 | { | ||
346 | struct f71805f_data *data = f71805f_update_device(dev); | ||
347 | |||
348 | return sprintf(buf, "%ld\n", in0_from_reg(data->in_high[0])); | ||
349 | } | ||
350 | |||
351 | static ssize_t show_in0_min(struct device *dev, struct device_attribute | ||
352 | *devattr, char *buf) | ||
353 | { | ||
354 | struct f71805f_data *data = f71805f_update_device(dev); | ||
355 | |||
356 | return sprintf(buf, "%ld\n", in0_from_reg(data->in_low[0])); | ||
357 | } | ||
358 | |||
359 | static ssize_t set_in0_max(struct device *dev, struct device_attribute | ||
360 | *devattr, const char *buf, size_t count) | ||
361 | { | ||
362 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
363 | long val = simple_strtol(buf, NULL, 10); | ||
364 | |||
365 | down(&data->update_lock); | ||
366 | data->in_high[0] = in0_to_reg(val); | ||
367 | f71805f_write8(data, F71805F_REG_IN_HIGH(0), data->in_high[0]); | ||
368 | up(&data->update_lock); | ||
369 | |||
370 | return count; | ||
371 | } | ||
372 | |||
373 | static ssize_t set_in0_min(struct device *dev, struct device_attribute | ||
374 | *devattr, const char *buf, size_t count) | ||
375 | { | ||
376 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
377 | long val = simple_strtol(buf, NULL, 10); | ||
378 | |||
379 | down(&data->update_lock); | ||
380 | data->in_low[0] = in0_to_reg(val); | ||
381 | f71805f_write8(data, F71805F_REG_IN_LOW(0), data->in_low[0]); | ||
382 | up(&data->update_lock); | ||
383 | |||
384 | return count; | ||
385 | } | ||
386 | |||
387 | static DEVICE_ATTR(in0_input, S_IRUGO, show_in0, NULL); | ||
388 | static DEVICE_ATTR(in0_max, S_IRUGO| S_IWUSR, show_in0_max, set_in0_max); | ||
389 | static DEVICE_ATTR(in0_min, S_IRUGO| S_IWUSR, show_in0_min, set_in0_min); | ||
390 | |||
391 | static ssize_t show_in(struct device *dev, struct device_attribute *devattr, | ||
392 | char *buf) | ||
393 | { | ||
394 | struct f71805f_data *data = f71805f_update_device(dev); | ||
395 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
396 | int nr = attr->index; | ||
397 | |||
398 | return sprintf(buf, "%ld\n", in_from_reg(data->in[nr])); | ||
399 | } | ||
400 | |||
401 | static ssize_t show_in_max(struct device *dev, struct device_attribute | ||
402 | *devattr, char *buf) | ||
403 | { | ||
404 | struct f71805f_data *data = f71805f_update_device(dev); | ||
405 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
406 | int nr = attr->index; | ||
407 | |||
408 | return sprintf(buf, "%ld\n", in_from_reg(data->in_high[nr])); | ||
409 | } | ||
410 | |||
411 | static ssize_t show_in_min(struct device *dev, struct device_attribute | ||
412 | *devattr, char *buf) | ||
413 | { | ||
414 | struct f71805f_data *data = f71805f_update_device(dev); | ||
415 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
416 | int nr = attr->index; | ||
417 | |||
418 | return sprintf(buf, "%ld\n", in_from_reg(data->in_low[nr])); | ||
419 | } | ||
420 | |||
421 | static ssize_t set_in_max(struct device *dev, struct device_attribute | ||
422 | *devattr, const char *buf, size_t count) | ||
423 | { | ||
424 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
425 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
426 | int nr = attr->index; | ||
427 | long val = simple_strtol(buf, NULL, 10); | ||
428 | |||
429 | down(&data->update_lock); | ||
430 | data->in_high[nr] = in_to_reg(val); | ||
431 | f71805f_write8(data, F71805F_REG_IN_HIGH(nr), data->in_high[nr]); | ||
432 | up(&data->update_lock); | ||
433 | |||
434 | return count; | ||
435 | } | ||
436 | |||
437 | static ssize_t set_in_min(struct device *dev, struct device_attribute | ||
438 | *devattr, const char *buf, size_t count) | ||
439 | { | ||
440 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
441 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
442 | int nr = attr->index; | ||
443 | long val = simple_strtol(buf, NULL, 10); | ||
444 | |||
445 | down(&data->update_lock); | ||
446 | data->in_low[nr] = in_to_reg(val); | ||
447 | f71805f_write8(data, F71805F_REG_IN_LOW(nr), data->in_low[nr]); | ||
448 | up(&data->update_lock); | ||
449 | |||
450 | return count; | ||
451 | } | ||
452 | |||
453 | #define sysfs_in(offset) \ | ||
454 | static SENSOR_DEVICE_ATTR(in##offset##_input, S_IRUGO, \ | ||
455 | show_in, NULL, offset); \ | ||
456 | static SENSOR_DEVICE_ATTR(in##offset##_max, S_IRUGO | S_IWUSR, \ | ||
457 | show_in_max, set_in_max, offset); \ | ||
458 | static SENSOR_DEVICE_ATTR(in##offset##_min, S_IRUGO | S_IWUSR, \ | ||
459 | show_in_min, set_in_min, offset) | ||
460 | |||
461 | sysfs_in(1); | ||
462 | sysfs_in(2); | ||
463 | sysfs_in(3); | ||
464 | sysfs_in(4); | ||
465 | sysfs_in(5); | ||
466 | sysfs_in(6); | ||
467 | sysfs_in(7); | ||
468 | sysfs_in(8); | ||
469 | |||
470 | static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, | ||
471 | char *buf) | ||
472 | { | ||
473 | struct f71805f_data *data = f71805f_update_device(dev); | ||
474 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
475 | int nr = attr->index; | ||
476 | |||
477 | return sprintf(buf, "%ld\n", fan_from_reg(data->fan[nr])); | ||
478 | } | ||
479 | |||
480 | static ssize_t show_fan_min(struct device *dev, struct device_attribute | ||
481 | *devattr, char *buf) | ||
482 | { | ||
483 | struct f71805f_data *data = f71805f_update_device(dev); | ||
484 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
485 | int nr = attr->index; | ||
486 | |||
487 | return sprintf(buf, "%ld\n", fan_from_reg(data->fan_low[nr])); | ||
488 | } | ||
489 | |||
490 | static ssize_t set_fan_min(struct device *dev, struct device_attribute | ||
491 | *devattr, const char *buf, size_t count) | ||
492 | { | ||
493 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
494 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
495 | int nr = attr->index; | ||
496 | long val = simple_strtol(buf, NULL, 10); | ||
497 | |||
498 | down(&data->update_lock); | ||
499 | data->fan_low[nr] = fan_to_reg(val); | ||
500 | f71805f_write16(data, F71805F_REG_FAN_LOW(nr), data->fan_low[nr]); | ||
501 | up(&data->update_lock); | ||
502 | |||
503 | return count; | ||
504 | } | ||
505 | |||
506 | #define sysfs_fan(offset) \ | ||
507 | static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ | ||
508 | show_fan, NULL, offset - 1); \ | ||
509 | static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR, \ | ||
510 | show_fan_min, set_fan_min, offset - 1) | ||
511 | |||
512 | sysfs_fan(1); | ||
513 | sysfs_fan(2); | ||
514 | sysfs_fan(3); | ||
515 | |||
516 | static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, | ||
517 | char *buf) | ||
518 | { | ||
519 | struct f71805f_data *data = f71805f_update_device(dev); | ||
520 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
521 | int nr = attr->index; | ||
522 | |||
523 | return sprintf(buf, "%ld\n", temp_from_reg(data->temp[nr])); | ||
524 | } | ||
525 | |||
526 | static ssize_t show_temp_max(struct device *dev, struct device_attribute | ||
527 | *devattr, char *buf) | ||
528 | { | ||
529 | struct f71805f_data *data = f71805f_update_device(dev); | ||
530 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
531 | int nr = attr->index; | ||
532 | |||
533 | return sprintf(buf, "%ld\n", temp_from_reg(data->temp_high[nr])); | ||
534 | } | ||
535 | |||
536 | static ssize_t show_temp_hyst(struct device *dev, struct device_attribute | ||
537 | *devattr, char *buf) | ||
538 | { | ||
539 | struct f71805f_data *data = f71805f_update_device(dev); | ||
540 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
541 | int nr = attr->index; | ||
542 | |||
543 | return sprintf(buf, "%ld\n", temp_from_reg(data->temp_hyst[nr])); | ||
544 | } | ||
545 | |||
546 | static ssize_t show_temp_type(struct device *dev, struct device_attribute | ||
547 | *devattr, char *buf) | ||
548 | { | ||
549 | struct f71805f_data *data = f71805f_update_device(dev); | ||
550 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
551 | int nr = attr->index; | ||
552 | |||
553 | /* 3 is diode, 4 is thermistor */ | ||
554 | return sprintf(buf, "%u\n", (data->temp_mode & (1 << nr)) ? 3 : 4); | ||
555 | } | ||
556 | |||
557 | static ssize_t set_temp_max(struct device *dev, struct device_attribute | ||
558 | *devattr, const char *buf, size_t count) | ||
559 | { | ||
560 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
561 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
562 | int nr = attr->index; | ||
563 | long val = simple_strtol(buf, NULL, 10); | ||
564 | |||
565 | down(&data->update_lock); | ||
566 | data->temp_high[nr] = temp_to_reg(val); | ||
567 | f71805f_write8(data, F71805F_REG_TEMP_HIGH(nr), data->temp_high[nr]); | ||
568 | up(&data->update_lock); | ||
569 | |||
570 | return count; | ||
571 | } | ||
572 | |||
573 | static ssize_t set_temp_hyst(struct device *dev, struct device_attribute | ||
574 | *devattr, const char *buf, size_t count) | ||
575 | { | ||
576 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
577 | struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); | ||
578 | int nr = attr->index; | ||
579 | long val = simple_strtol(buf, NULL, 10); | ||
580 | |||
581 | down(&data->update_lock); | ||
582 | data->temp_hyst[nr] = temp_to_reg(val); | ||
583 | f71805f_write8(data, F71805F_REG_TEMP_HYST(nr), data->temp_hyst[nr]); | ||
584 | up(&data->update_lock); | ||
585 | |||
586 | return count; | ||
587 | } | ||
588 | |||
589 | #define sysfs_temp(offset) \ | ||
590 | static SENSOR_DEVICE_ATTR(temp##offset##_input, S_IRUGO, \ | ||
591 | show_temp, NULL, offset - 1); \ | ||
592 | static SENSOR_DEVICE_ATTR(temp##offset##_max, S_IRUGO | S_IWUSR, \ | ||
593 | show_temp_max, set_temp_max, offset - 1); \ | ||
594 | static SENSOR_DEVICE_ATTR(temp##offset##_max_hyst, S_IRUGO | S_IWUSR, \ | ||
595 | show_temp_hyst, set_temp_hyst, offset - 1); \ | ||
596 | static SENSOR_DEVICE_ATTR(temp##offset##_type, S_IRUGO, \ | ||
597 | show_temp_type, NULL, offset - 1) | ||
598 | |||
599 | sysfs_temp(1); | ||
600 | sysfs_temp(2); | ||
601 | sysfs_temp(3); | ||
602 | |||
603 | static ssize_t show_alarms_in(struct device *dev, struct device_attribute | ||
604 | *devattr, char *buf) | ||
605 | { | ||
606 | struct f71805f_data *data = f71805f_update_device(dev); | ||
607 | |||
608 | return sprintf(buf, "%d\n", data->alarms[0] | | ||
609 | ((data->alarms[1] & 0x01) << 8)); | ||
610 | } | ||
611 | |||
612 | static ssize_t show_alarms_fan(struct device *dev, struct device_attribute | ||
613 | *devattr, char *buf) | ||
614 | { | ||
615 | struct f71805f_data *data = f71805f_update_device(dev); | ||
616 | |||
617 | return sprintf(buf, "%d\n", data->alarms[2] & 0x07); | ||
618 | } | ||
619 | |||
620 | static ssize_t show_alarms_temp(struct device *dev, struct device_attribute | ||
621 | *devattr, char *buf) | ||
622 | { | ||
623 | struct f71805f_data *data = f71805f_update_device(dev); | ||
624 | |||
625 | return sprintf(buf, "%d\n", (data->alarms[1] >> 3) & 0x07); | ||
626 | } | ||
627 | |||
628 | static DEVICE_ATTR(alarms_in, S_IRUGO, show_alarms_in, NULL); | ||
629 | static DEVICE_ATTR(alarms_fan, S_IRUGO, show_alarms_fan, NULL); | ||
630 | static DEVICE_ATTR(alarms_temp, S_IRUGO, show_alarms_temp, NULL); | ||
631 | |||
632 | static ssize_t show_name(struct device *dev, struct device_attribute | ||
633 | *devattr, char *buf) | ||
634 | { | ||
635 | struct f71805f_data *data = dev_get_drvdata(dev); | ||
636 | |||
637 | return sprintf(buf, "%s\n", data->name); | ||
638 | } | ||
639 | |||
640 | static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); | ||
641 | |||
642 | /* | ||
643 | * Device registration and initialization | ||
644 | */ | ||
645 | |||
646 | static void __devinit f71805f_init_device(struct f71805f_data *data) | ||
647 | { | ||
648 | u8 reg; | ||
649 | int i; | ||
650 | |||
651 | reg = f71805f_read8(data, F71805F_REG_START); | ||
652 | if ((reg & 0x41) != 0x01) { | ||
653 | printk(KERN_DEBUG DRVNAME ": Starting monitoring " | ||
654 | "operations\n"); | ||
655 | f71805f_write8(data, F71805F_REG_START, (reg | 0x01) & ~0x40); | ||
656 | } | ||
657 | |||
658 | /* Fan monitoring can be disabled. If it is, we won't be polling | ||
659 | the register values, and won't create the related sysfs files. */ | ||
660 | for (i = 0; i < 3; i++) { | ||
661 | reg = f71805f_read8(data, F71805F_REG_FAN_CTRL(i)); | ||
662 | if (!(reg & 0x80)) | ||
663 | data->fan_enabled |= (1 << i); | ||
664 | } | ||
665 | } | ||
666 | |||
667 | static int __devinit f71805f_probe(struct platform_device *pdev) | ||
668 | { | ||
669 | struct f71805f_data *data; | ||
670 | struct resource *res; | ||
671 | int err; | ||
672 | |||
673 | if (!(data = kzalloc(sizeof(struct f71805f_data), GFP_KERNEL))) { | ||
674 | err = -ENOMEM; | ||
675 | printk(KERN_ERR DRVNAME ": Out of memory\n"); | ||
676 | goto exit; | ||
677 | } | ||
678 | |||
679 | res = platform_get_resource(pdev, IORESOURCE_IO, 0); | ||
680 | data->addr = res->start; | ||
681 | init_MUTEX(&data->lock); | ||
682 | data->name = "f71805f"; | ||
683 | init_MUTEX(&data->update_lock); | ||
684 | |||
685 | platform_set_drvdata(pdev, data); | ||
686 | |||
687 | data->class_dev = hwmon_device_register(&pdev->dev); | ||
688 | if (IS_ERR(data->class_dev)) { | ||
689 | err = PTR_ERR(data->class_dev); | ||
690 | dev_err(&pdev->dev, "Class registration failed (%d)\n", err); | ||
691 | goto exit_free; | ||
692 | } | ||
693 | |||
694 | /* Initialize the F71805F chip */ | ||
695 | f71805f_init_device(data); | ||
696 | |||
697 | /* Register sysfs interface files */ | ||
698 | device_create_file(&pdev->dev, &dev_attr_in0_input); | ||
699 | device_create_file(&pdev->dev, &dev_attr_in0_max); | ||
700 | device_create_file(&pdev->dev, &dev_attr_in0_min); | ||
701 | device_create_file(&pdev->dev, &sensor_dev_attr_in1_input.dev_attr); | ||
702 | device_create_file(&pdev->dev, &sensor_dev_attr_in2_input.dev_attr); | ||
703 | device_create_file(&pdev->dev, &sensor_dev_attr_in3_input.dev_attr); | ||
704 | device_create_file(&pdev->dev, &sensor_dev_attr_in4_input.dev_attr); | ||
705 | device_create_file(&pdev->dev, &sensor_dev_attr_in5_input.dev_attr); | ||
706 | device_create_file(&pdev->dev, &sensor_dev_attr_in6_input.dev_attr); | ||
707 | device_create_file(&pdev->dev, &sensor_dev_attr_in7_input.dev_attr); | ||
708 | device_create_file(&pdev->dev, &sensor_dev_attr_in8_input.dev_attr); | ||
709 | device_create_file(&pdev->dev, &sensor_dev_attr_in1_max.dev_attr); | ||
710 | device_create_file(&pdev->dev, &sensor_dev_attr_in2_max.dev_attr); | ||
711 | device_create_file(&pdev->dev, &sensor_dev_attr_in3_max.dev_attr); | ||
712 | device_create_file(&pdev->dev, &sensor_dev_attr_in4_max.dev_attr); | ||
713 | device_create_file(&pdev->dev, &sensor_dev_attr_in5_max.dev_attr); | ||
714 | device_create_file(&pdev->dev, &sensor_dev_attr_in6_max.dev_attr); | ||
715 | device_create_file(&pdev->dev, &sensor_dev_attr_in7_max.dev_attr); | ||
716 | device_create_file(&pdev->dev, &sensor_dev_attr_in8_max.dev_attr); | ||
717 | device_create_file(&pdev->dev, &sensor_dev_attr_in1_min.dev_attr); | ||
718 | device_create_file(&pdev->dev, &sensor_dev_attr_in2_min.dev_attr); | ||
719 | device_create_file(&pdev->dev, &sensor_dev_attr_in3_min.dev_attr); | ||
720 | device_create_file(&pdev->dev, &sensor_dev_attr_in4_min.dev_attr); | ||
721 | device_create_file(&pdev->dev, &sensor_dev_attr_in5_min.dev_attr); | ||
722 | device_create_file(&pdev->dev, &sensor_dev_attr_in6_min.dev_attr); | ||
723 | device_create_file(&pdev->dev, &sensor_dev_attr_in7_min.dev_attr); | ||
724 | device_create_file(&pdev->dev, &sensor_dev_attr_in8_min.dev_attr); | ||
725 | if (data->fan_enabled & (1 << 0)) { | ||
726 | device_create_file(&pdev->dev, | ||
727 | &sensor_dev_attr_fan1_input.dev_attr); | ||
728 | device_create_file(&pdev->dev, | ||
729 | &sensor_dev_attr_fan1_min.dev_attr); | ||
730 | } | ||
731 | if (data->fan_enabled & (1 << 1)) { | ||
732 | device_create_file(&pdev->dev, | ||
733 | &sensor_dev_attr_fan2_input.dev_attr); | ||
734 | device_create_file(&pdev->dev, | ||
735 | &sensor_dev_attr_fan2_min.dev_attr); | ||
736 | } | ||
737 | if (data->fan_enabled & (1 << 2)) { | ||
738 | device_create_file(&pdev->dev, | ||
739 | &sensor_dev_attr_fan3_input.dev_attr); | ||
740 | device_create_file(&pdev->dev, | ||
741 | &sensor_dev_attr_fan3_min.dev_attr); | ||
742 | } | ||
743 | device_create_file(&pdev->dev, | ||
744 | &sensor_dev_attr_temp1_input.dev_attr); | ||
745 | device_create_file(&pdev->dev, | ||
746 | &sensor_dev_attr_temp2_input.dev_attr); | ||
747 | device_create_file(&pdev->dev, | ||
748 | &sensor_dev_attr_temp3_input.dev_attr); | ||
749 | device_create_file(&pdev->dev, &sensor_dev_attr_temp1_max.dev_attr); | ||
750 | device_create_file(&pdev->dev, &sensor_dev_attr_temp2_max.dev_attr); | ||
751 | device_create_file(&pdev->dev, &sensor_dev_attr_temp3_max.dev_attr); | ||
752 | device_create_file(&pdev->dev, | ||
753 | &sensor_dev_attr_temp1_max_hyst.dev_attr); | ||
754 | device_create_file(&pdev->dev, | ||
755 | &sensor_dev_attr_temp2_max_hyst.dev_attr); | ||
756 | device_create_file(&pdev->dev, | ||
757 | &sensor_dev_attr_temp3_max_hyst.dev_attr); | ||
758 | device_create_file(&pdev->dev, &sensor_dev_attr_temp1_type.dev_attr); | ||
759 | device_create_file(&pdev->dev, &sensor_dev_attr_temp2_type.dev_attr); | ||
760 | device_create_file(&pdev->dev, &sensor_dev_attr_temp3_type.dev_attr); | ||
761 | device_create_file(&pdev->dev, &dev_attr_alarms_in); | ||
762 | device_create_file(&pdev->dev, &dev_attr_alarms_fan); | ||
763 | device_create_file(&pdev->dev, &dev_attr_alarms_temp); | ||
764 | device_create_file(&pdev->dev, &dev_attr_name); | ||
765 | |||
766 | return 0; | ||
767 | |||
768 | exit_free: | ||
769 | kfree(data); | ||
770 | exit: | ||
771 | return err; | ||
772 | } | ||
773 | |||
774 | static int __devexit f71805f_remove(struct platform_device *pdev) | ||
775 | { | ||
776 | struct f71805f_data *data = platform_get_drvdata(pdev); | ||
777 | |||
778 | platform_set_drvdata(pdev, NULL); | ||
779 | hwmon_device_unregister(data->class_dev); | ||
780 | kfree(data); | ||
781 | |||
782 | return 0; | ||
783 | } | ||
784 | |||
785 | static struct platform_driver f71805f_driver = { | ||
786 | .driver = { | ||
787 | .owner = THIS_MODULE, | ||
788 | .name = DRVNAME, | ||
789 | }, | ||
790 | .probe = f71805f_probe, | ||
791 | .remove = __devexit_p(f71805f_remove), | ||
792 | }; | ||
793 | |||
794 | static int __init f71805f_device_add(unsigned short address) | ||
795 | { | ||
796 | int err; | ||
797 | |||
798 | pdev = platform_device_alloc(DRVNAME, address); | ||
799 | if (!pdev) { | ||
800 | err = -ENOMEM; | ||
801 | printk(KERN_ERR DRVNAME ": Device allocation failed\n"); | ||
802 | goto exit; | ||
803 | } | ||
804 | |||
805 | f71805f_resource.start = address; | ||
806 | f71805f_resource.end = address + REGION_LENGTH - 1; | ||
807 | f71805f_resource.name = pdev->name; | ||
808 | err = platform_device_add_resources(pdev, &f71805f_resource, 1); | ||
809 | if (err) { | ||
810 | printk(KERN_ERR DRVNAME ": Device resource addition failed " | ||
811 | "(%d)\n", err); | ||
812 | goto exit_device_put; | ||
813 | } | ||
814 | |||
815 | err = platform_device_add(pdev); | ||
816 | if (err) { | ||
817 | printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", | ||
818 | err); | ||
819 | goto exit_device_put; | ||
820 | } | ||
821 | |||
822 | return 0; | ||
823 | |||
824 | exit_device_put: | ||
825 | platform_device_put(pdev); | ||
826 | exit: | ||
827 | return err; | ||
828 | } | ||
829 | |||
830 | static int __init f71805f_find(int sioaddr, unsigned short *address) | ||
831 | { | ||
832 | int err = -ENODEV; | ||
833 | u16 devid; | ||
834 | |||
835 | superio_enter(sioaddr); | ||
836 | |||
837 | devid = superio_inw(sioaddr, SIO_REG_MANID); | ||
838 | if (devid != SIO_FINTEK_ID) | ||
839 | goto exit; | ||
840 | |||
841 | devid = superio_inw(sioaddr, SIO_REG_DEVID); | ||
842 | if (devid != SIO_F71805F_ID) { | ||
843 | printk(KERN_INFO DRVNAME ": Unsupported Fintek device, " | ||
844 | "skipping\n"); | ||
845 | goto exit; | ||
846 | } | ||
847 | |||
848 | superio_select(sioaddr, F71805F_LD_HWM); | ||
849 | if (!(superio_inb(sioaddr, SIO_REG_ENABLE) & 0x01)) { | ||
850 | printk(KERN_WARNING DRVNAME ": Device not activated, " | ||
851 | "skipping\n"); | ||
852 | goto exit; | ||
853 | } | ||
854 | |||
855 | *address = superio_inw(sioaddr, SIO_REG_ADDR); | ||
856 | if (*address == 0) { | ||
857 | printk(KERN_WARNING DRVNAME ": Base address not set, " | ||
858 | "skipping\n"); | ||
859 | goto exit; | ||
860 | } | ||
861 | |||
862 | err = 0; | ||
863 | printk(KERN_INFO DRVNAME ": Found F71805F chip at %#x, revision %u\n", | ||
864 | *address, superio_inb(sioaddr, SIO_REG_DEVREV)); | ||
865 | |||
866 | exit: | ||
867 | superio_exit(sioaddr); | ||
868 | return err; | ||
869 | } | ||
870 | |||
871 | static int __init f71805f_init(void) | ||
872 | { | ||
873 | int err; | ||
874 | unsigned short address; | ||
875 | |||
876 | if (f71805f_find(0x2e, &address) | ||
877 | && f71805f_find(0x4e, &address)) | ||
878 | return -ENODEV; | ||
879 | |||
880 | err = platform_driver_register(&f71805f_driver); | ||
881 | if (err) | ||
882 | goto exit; | ||
883 | |||
884 | /* Sets global pdev as a side effect */ | ||
885 | err = f71805f_device_add(address); | ||
886 | if (err) | ||
887 | goto exit_driver; | ||
888 | |||
889 | return 0; | ||
890 | |||
891 | exit_driver: | ||
892 | platform_driver_unregister(&f71805f_driver); | ||
893 | exit: | ||
894 | return err; | ||
895 | } | ||
896 | |||
897 | static void __exit f71805f_exit(void) | ||
898 | { | ||
899 | platform_device_unregister(pdev); | ||
900 | platform_driver_unregister(&f71805f_driver); | ||
901 | } | ||
902 | |||
903 | MODULE_AUTHOR("Jean Delvare <khali@linux-fr>"); | ||
904 | MODULE_LICENSE("GPL"); | ||
905 | MODULE_DESCRIPTION("F71805F hardware monitoring driver"); | ||
906 | |||
907 | module_init(f71805f_init); | ||
908 | module_exit(f71805f_exit); | ||