diff options
author | Hans de Goede <j.w.r.degoede@hhs.nl> | 2007-10-11 08:06:29 -0400 |
---|---|---|
committer | Mark M. Hoffman <mhoffman@lightlink.com> | 2007-10-11 08:11:24 -0400 |
commit | 569ff1022662a88591b0a89ea316a3c44306d199 (patch) | |
tree | a0420271733bae76afce1a13c8a3942a41607635 /drivers/hwmon | |
parent | 4cfdbe7f6cfdc07c27ae8ac490cd3453440dd9d8 (diff) |
hwmon: Add new combined driver for FSC chips
This patch adds a new merged driver for FSC sensor chips, it merges the fscher
and fscpos drivers and adds support for the FSC Scylla, Heracles and Heimdall
chips.
Signed-off-by: Hans de Goede <j.w.r.degoede@hhs.nl>
Acked-by: Jean Delvare <khali@linux-fr.org>
Signed-off-by: Mark M. Hoffman <mhoffman@lightlink.com>
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 14 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 1 | ||||
-rw-r--r-- | drivers/hwmon/fschmd.c | 778 |
3 files changed, 793 insertions, 0 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c69de6c56d97..e47f88170806 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig | |||
@@ -266,6 +266,20 @@ config SENSORS_FSCPOS | |||
266 | This driver can also be built as a module. If so, the module | 266 | This driver can also be built as a module. If so, the module |
267 | will be called fscpos. | 267 | will be called fscpos. |
268 | 268 | ||
269 | config SENSORS_FSCHMD | ||
270 | tristate "FSC Poseidon, Scylla, Hermes, Heimdall and Heracles" | ||
271 | depends on X86 && I2C && EXPERIMENTAL | ||
272 | help | ||
273 | If you say yes here you get support for various Fujitsu Siemens | ||
274 | Computers sensor chips. | ||
275 | |||
276 | This is a new merged driver for FSC sensor chips which is intended | ||
277 | as a replacment for the fscpos, fscscy and fscher drivers and adds | ||
278 | support for several other FCS sensor chips. | ||
279 | |||
280 | This driver can also be built as a module. If so, the module | ||
281 | will be called fschmd. | ||
282 | |||
269 | config SENSORS_GL518SM | 283 | config SENSORS_GL518SM |
270 | tristate "Genesys Logic GL518SM" | 284 | tristate "Genesys Logic GL518SM" |
271 | depends on I2C | 285 | depends on I2C |
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 2f592e2e01b3..6da3eef94306 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile | |||
@@ -33,6 +33,7 @@ obj-$(CONFIG_SENSORS_F71805F) += f71805f.o | |||
33 | obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o | 33 | obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o |
34 | obj-$(CONFIG_SENSORS_F75375S) += f75375s.o | 34 | obj-$(CONFIG_SENSORS_F75375S) += f75375s.o |
35 | obj-$(CONFIG_SENSORS_FSCHER) += fscher.o | 35 | obj-$(CONFIG_SENSORS_FSCHER) += fscher.o |
36 | obj-$(CONFIG_SENSORS_FSCHMD) += fschmd.o | ||
36 | obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o | 37 | obj-$(CONFIG_SENSORS_FSCPOS) += fscpos.o |
37 | obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o | 38 | obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o |
38 | obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o | 39 | obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o |
diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c new file mode 100644 index 000000000000..63a4df0580db --- /dev/null +++ b/drivers/hwmon/fschmd.c | |||
@@ -0,0 +1,778 @@ | |||
1 | /* fschmd.c | ||
2 | * | ||
3 | * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify | ||
6 | * it under the terms of the GNU General Public License as published by | ||
7 | * the Free Software Foundation; either version 2 of the License, or | ||
8 | * (at your option) any later version. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, | ||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | * GNU General Public License for more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License | ||
16 | * along with this program; if not, write to the Free Software | ||
17 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
18 | */ | ||
19 | |||
20 | /* | ||
21 | * Merged Fujitsu Siemens hwmon driver, supporting the Poseidon, Hermes, | ||
22 | * Scylla, Heracles and Heimdall chips | ||
23 | * | ||
24 | * Based on the original 2.4 fscscy, 2.6 fscpos, 2.6 fscher and 2.6 | ||
25 | * (candidate) fschmd drivers: | ||
26 | * Copyright (C) 2006 Thilo Cestonaro | ||
27 | * <thilo.cestonaro.external@fujitsu-siemens.com> | ||
28 | * Copyright (C) 2004, 2005 Stefan Ott <stefan@desire.ch> | ||
29 | * Copyright (C) 2003, 2004 Reinhard Nissl <rnissl@gmx.de> | ||
30 | * Copyright (c) 2001 Martin Knoblauch <mkn@teraport.de, knobi@knobisoft.de> | ||
31 | * Copyright (C) 2000 Hermann Jung <hej@odn.de> | ||
32 | */ | ||
33 | |||
34 | #include <linux/module.h> | ||
35 | #include <linux/init.h> | ||
36 | #include <linux/slab.h> | ||
37 | #include <linux/jiffies.h> | ||
38 | #include <linux/i2c.h> | ||
39 | #include <linux/hwmon.h> | ||
40 | #include <linux/hwmon-sysfs.h> | ||
41 | #include <linux/err.h> | ||
42 | #include <linux/mutex.h> | ||
43 | #include <linux/sysfs.h> | ||
44 | |||
45 | /* Addresses to scan */ | ||
46 | static unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; | ||
47 | |||
48 | /* Insmod parameters */ | ||
49 | I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); | ||
50 | |||
51 | /* | ||
52 | * The FSCHMD registers and other defines | ||
53 | */ | ||
54 | |||
55 | /* chip identification */ | ||
56 | #define FSCHMD_REG_IDENT_0 0x00 | ||
57 | #define FSCHMD_REG_IDENT_1 0x01 | ||
58 | #define FSCHMD_REG_IDENT_2 0x02 | ||
59 | #define FSCHMD_REG_REVISION 0x03 | ||
60 | |||
61 | /* global control and status */ | ||
62 | #define FSCHMD_REG_EVENT_STATE 0x04 | ||
63 | #define FSCHMD_REG_CONTROL 0x05 | ||
64 | |||
65 | #define FSCHMD_CONTROL_ALERT_LED_MASK 0x01 | ||
66 | |||
67 | /* watchdog (support to be implemented) */ | ||
68 | #define FSCHMD_REG_WDOG_PRESET 0x28 | ||
69 | #define FSCHMD_REG_WDOG_STATE 0x23 | ||
70 | #define FSCHMD_REG_WDOG_CONTROL 0x21 | ||
71 | |||
72 | /* voltages, weird order is to keep the same order as the old drivers */ | ||
73 | static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; | ||
74 | |||
75 | /* minimum pwm at which the fan is driven (pwm can by increased depending on | ||
76 | the temp. Notice that for the scy some fans share there minimum speed. | ||
77 | Also notice that with the scy the sensor order is different then with the | ||
78 | other chips, this order was in the 2.4 driver and kept for consistency. */ | ||
79 | static const u8 FSCHMD_REG_FAN_MIN[5][6] = { | ||
80 | { 0x55, 0x65 }, /* pos */ | ||
81 | { 0x55, 0x65, 0xb5 }, /* her */ | ||
82 | { 0x65, 0x65, 0x55, 0xa5, 0x55, 0xa5 }, /* scy */ | ||
83 | { 0x55, 0x65, 0xa5, 0xb5 }, /* hrc */ | ||
84 | { 0x55, 0x65, 0xa5, 0xb5, 0xc5 }, /* hmd */ | ||
85 | }; | ||
86 | |||
87 | /* actual fan speed */ | ||
88 | static const u8 FSCHMD_REG_FAN_ACT[5][6] = { | ||
89 | { 0x0e, 0x6b, 0xab }, /* pos */ | ||
90 | { 0x0e, 0x6b, 0xbb }, /* her */ | ||
91 | { 0x6b, 0x6c, 0x0e, 0xab, 0x5c, 0xbb }, /* scy */ | ||
92 | { 0x0e, 0x6b, 0xab, 0xbb }, /* hrc */ | ||
93 | { 0x5b, 0x6b, 0xab, 0xbb, 0xcb }, /* hmd */ | ||
94 | }; | ||
95 | |||
96 | /* fan status registers */ | ||
97 | static const u8 FSCHMD_REG_FAN_STATE[5][6] = { | ||
98 | { 0x0d, 0x62, 0xa2 }, /* pos */ | ||
99 | { 0x0d, 0x62, 0xb2 }, /* her */ | ||
100 | { 0x62, 0x61, 0x0d, 0xa2, 0x52, 0xb2 }, /* scy */ | ||
101 | { 0x0d, 0x62, 0xa2, 0xb2 }, /* hrc */ | ||
102 | { 0x52, 0x62, 0xa2, 0xb2, 0xc2 }, /* hmd */ | ||
103 | }; | ||
104 | |||
105 | /* fan ripple / divider registers */ | ||
106 | static const u8 FSCHMD_REG_FAN_RIPPLE[5][6] = { | ||
107 | { 0x0f, 0x6f, 0xaf }, /* pos */ | ||
108 | { 0x0f, 0x6f, 0xbf }, /* her */ | ||
109 | { 0x6f, 0x6f, 0x0f, 0xaf, 0x0f, 0xbf }, /* scy */ | ||
110 | { 0x0f, 0x6f, 0xaf, 0xbf }, /* hrc */ | ||
111 | { 0x5f, 0x6f, 0xaf, 0xbf, 0xcf }, /* hmd */ | ||
112 | }; | ||
113 | |||
114 | static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 }; | ||
115 | |||
116 | /* Fan status register bitmasks */ | ||
117 | #define FSCHMD_FAN_ALARM_MASK 0x04 /* called fault by FSC! */ | ||
118 | #define FSCHMD_FAN_NOT_PRESENT_MASK 0x08 /* not documented */ | ||
119 | |||
120 | |||
121 | /* actual temperature registers */ | ||
122 | static const u8 FSCHMD_REG_TEMP_ACT[5][5] = { | ||
123 | { 0x64, 0x32, 0x35 }, /* pos */ | ||
124 | { 0x64, 0x32, 0x35 }, /* her */ | ||
125 | { 0x64, 0xD0, 0x32, 0x35 }, /* scy */ | ||
126 | { 0x64, 0x32, 0x35 }, /* hrc */ | ||
127 | { 0x70, 0x80, 0x90, 0xd0, 0xe0 }, /* hmd */ | ||
128 | }; | ||
129 | |||
130 | /* temperature state registers */ | ||
131 | static const u8 FSCHMD_REG_TEMP_STATE[5][5] = { | ||
132 | { 0x71, 0x81, 0x91 }, /* pos */ | ||
133 | { 0x71, 0x81, 0x91 }, /* her */ | ||
134 | { 0x71, 0xd1, 0x81, 0x91 }, /* scy */ | ||
135 | { 0x71, 0x81, 0x91 }, /* hrc */ | ||
136 | { 0x71, 0x81, 0x91, 0xd1, 0xe1 }, /* hmd */ | ||
137 | }; | ||
138 | |||
139 | /* temperature high limit registers, FSC does not document these. Proven to be | ||
140 | there with field testing on the fscher and fschrc, already supported / used | ||
141 | in the fscscy 2.4 driver. FSC has confirmed that the fschmd has registers | ||
142 | at these addresses, but doesn't want to confirm they are the same as with | ||
143 | the fscher?? */ | ||
144 | static const u8 FSCHMD_REG_TEMP_LIMIT[5][5] = { | ||
145 | { 0, 0, 0 }, /* pos */ | ||
146 | { 0x76, 0x86, 0x96 }, /* her */ | ||
147 | { 0x76, 0xd6, 0x86, 0x96 }, /* scy */ | ||
148 | { 0x76, 0x86, 0x96 }, /* hrc */ | ||
149 | { 0x76, 0x86, 0x96, 0xd6, 0xe6 }, /* hmd */ | ||
150 | }; | ||
151 | |||
152 | /* These were found through experimenting with an fscher, currently they are | ||
153 | not used, but we keep them around for future reference. | ||
154 | static const u8 FSCHER_REG_TEMP_AUTOP1[] = { 0x73, 0x83, 0x93 }; | ||
155 | static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; */ | ||
156 | |||
157 | static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 }; | ||
158 | |||
159 | /* temp status register bitmasks */ | ||
160 | #define FSCHMD_TEMP_WORKING_MASK 0x01 | ||
161 | #define FSCHMD_TEMP_ALERT_MASK 0x02 | ||
162 | /* there only really is an alarm if the sensor is working and alert == 1 */ | ||
163 | #define FSCHMD_TEMP_ALARM_MASK \ | ||
164 | (FSCHMD_TEMP_WORKING_MASK | FSCHMD_TEMP_ALERT_MASK) | ||
165 | |||
166 | /* our driver name */ | ||
167 | #define FSCHMD_NAME "fschmd" | ||
168 | |||
169 | /* | ||
170 | * Functions declarations | ||
171 | */ | ||
172 | |||
173 | static int fschmd_attach_adapter(struct i2c_adapter *adapter); | ||
174 | static int fschmd_detach_client(struct i2c_client *client); | ||
175 | static struct fschmd_data *fschmd_update_device(struct device *dev); | ||
176 | |||
177 | /* | ||
178 | * Driver data (common to all clients) | ||
179 | */ | ||
180 | |||
181 | static struct i2c_driver fschmd_driver = { | ||
182 | .driver = { | ||
183 | .name = FSCHMD_NAME, | ||
184 | }, | ||
185 | .attach_adapter = fschmd_attach_adapter, | ||
186 | .detach_client = fschmd_detach_client, | ||
187 | }; | ||
188 | |||
189 | /* | ||
190 | * Client data (each client gets its own) | ||
191 | */ | ||
192 | |||
193 | struct fschmd_data { | ||
194 | struct i2c_client client; | ||
195 | struct device *hwmon_dev; | ||
196 | struct mutex update_lock; | ||
197 | int kind; | ||
198 | char valid; /* zero until following fields are valid */ | ||
199 | unsigned long last_updated; /* in jiffies */ | ||
200 | |||
201 | /* register values */ | ||
202 | u8 global_control; /* global control register */ | ||
203 | u8 volt[3]; /* 12, 5, battery voltage */ | ||
204 | u8 temp_act[5]; /* temperature */ | ||
205 | u8 temp_status[5]; /* status of sensor */ | ||
206 | u8 temp_max[5]; /* high temp limit, notice: undocumented! */ | ||
207 | u8 fan_act[6]; /* fans revolutions per second */ | ||
208 | u8 fan_status[6]; /* fan status */ | ||
209 | u8 fan_min[6]; /* fan min value for rps */ | ||
210 | u8 fan_ripple[6]; /* divider for rps */ | ||
211 | }; | ||
212 | |||
213 | /* | ||
214 | * Sysfs attr show / store functions | ||
215 | */ | ||
216 | |||
217 | static ssize_t show_in_value(struct device *dev, | ||
218 | struct device_attribute *devattr, char *buf) | ||
219 | { | ||
220 | const int max_reading[3] = { 14200, 6600, 3300 }; | ||
221 | int index = to_sensor_dev_attr(devattr)->index; | ||
222 | struct fschmd_data *data = fschmd_update_device(dev); | ||
223 | |||
224 | return sprintf(buf, "%d\n", (data->volt[index] * | ||
225 | max_reading[index] + 128) / 255); | ||
226 | } | ||
227 | |||
228 | |||
229 | #define TEMP_FROM_REG(val) (((val) - 128) * 1000) | ||
230 | |||
231 | static ssize_t show_temp_value(struct device *dev, | ||
232 | struct device_attribute *devattr, char *buf) | ||
233 | { | ||
234 | int index = to_sensor_dev_attr(devattr)->index; | ||
235 | struct fschmd_data *data = fschmd_update_device(dev); | ||
236 | |||
237 | return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_act[index])); | ||
238 | } | ||
239 | |||
240 | static ssize_t show_temp_max(struct device *dev, | ||
241 | struct device_attribute *devattr, char *buf) | ||
242 | { | ||
243 | int index = to_sensor_dev_attr(devattr)->index; | ||
244 | struct fschmd_data *data = fschmd_update_device(dev); | ||
245 | |||
246 | return sprintf(buf, "%d\n", TEMP_FROM_REG(data->temp_max[index])); | ||
247 | } | ||
248 | |||
249 | static ssize_t store_temp_max(struct device *dev, struct device_attribute | ||
250 | *devattr, const char *buf, size_t count) | ||
251 | { | ||
252 | int index = to_sensor_dev_attr(devattr)->index; | ||
253 | struct fschmd_data *data = dev_get_drvdata(dev); | ||
254 | long v = simple_strtol(buf, NULL, 10) / 1000; | ||
255 | |||
256 | v = SENSORS_LIMIT(v, -128, 127) + 128; | ||
257 | |||
258 | mutex_lock(&data->update_lock); | ||
259 | i2c_smbus_write_byte_data(&data->client, | ||
260 | FSCHMD_REG_TEMP_LIMIT[data->kind][index], v); | ||
261 | data->temp_max[index] = v; | ||
262 | mutex_unlock(&data->update_lock); | ||
263 | |||
264 | return count; | ||
265 | } | ||
266 | |||
267 | static ssize_t show_temp_fault(struct device *dev, | ||
268 | struct device_attribute *devattr, char *buf) | ||
269 | { | ||
270 | int index = to_sensor_dev_attr(devattr)->index; | ||
271 | struct fschmd_data *data = fschmd_update_device(dev); | ||
272 | |||
273 | /* bit 0 set means sensor working ok, so no fault! */ | ||
274 | if (data->temp_status[index] & FSCHMD_TEMP_WORKING_MASK) | ||
275 | return sprintf(buf, "0\n"); | ||
276 | else | ||
277 | return sprintf(buf, "1\n"); | ||
278 | } | ||
279 | |||
280 | static ssize_t show_temp_alarm(struct device *dev, | ||
281 | struct device_attribute *devattr, char *buf) | ||
282 | { | ||
283 | int index = to_sensor_dev_attr(devattr)->index; | ||
284 | struct fschmd_data *data = fschmd_update_device(dev); | ||
285 | |||
286 | if ((data->temp_status[index] & FSCHMD_TEMP_ALARM_MASK) == | ||
287 | FSCHMD_TEMP_ALARM_MASK) | ||
288 | return sprintf(buf, "1\n"); | ||
289 | else | ||
290 | return sprintf(buf, "0\n"); | ||
291 | } | ||
292 | |||
293 | |||
294 | #define RPM_FROM_REG(val) ((val) * 60) | ||
295 | |||
296 | static ssize_t show_fan_value(struct device *dev, | ||
297 | struct device_attribute *devattr, char *buf) | ||
298 | { | ||
299 | int index = to_sensor_dev_attr(devattr)->index; | ||
300 | struct fschmd_data *data = fschmd_update_device(dev); | ||
301 | |||
302 | return sprintf(buf, "%u\n", RPM_FROM_REG(data->fan_act[index])); | ||
303 | } | ||
304 | |||
305 | static ssize_t show_fan_div(struct device *dev, | ||
306 | struct device_attribute *devattr, char *buf) | ||
307 | { | ||
308 | int index = to_sensor_dev_attr(devattr)->index; | ||
309 | struct fschmd_data *data = fschmd_update_device(dev); | ||
310 | |||
311 | /* bits 2..7 reserved => mask with 3 */ | ||
312 | return sprintf(buf, "%d\n", 1 << (data->fan_ripple[index] & 3)); | ||
313 | } | ||
314 | |||
315 | static ssize_t store_fan_div(struct device *dev, struct device_attribute | ||
316 | *devattr, const char *buf, size_t count) | ||
317 | { | ||
318 | u8 reg; | ||
319 | int index = to_sensor_dev_attr(devattr)->index; | ||
320 | struct fschmd_data *data = dev_get_drvdata(dev); | ||
321 | /* supported values: 2, 4, 8 */ | ||
322 | unsigned long v = simple_strtoul(buf, NULL, 10); | ||
323 | |||
324 | switch (v) { | ||
325 | case 2: v = 1; break; | ||
326 | case 4: v = 2; break; | ||
327 | case 8: v = 3; break; | ||
328 | default: | ||
329 | dev_err(dev, "fan_div value %lu not supported. " | ||
330 | "Choose one of 2, 4 or 8!\n", v); | ||
331 | return -EINVAL; | ||
332 | } | ||
333 | |||
334 | mutex_lock(&data->update_lock); | ||
335 | |||
336 | reg = i2c_smbus_read_byte_data(&data->client, | ||
337 | FSCHMD_REG_FAN_RIPPLE[data->kind][index]); | ||
338 | |||
339 | /* bits 2..7 reserved => mask with 0x03 */ | ||
340 | reg &= ~0x03; | ||
341 | reg |= v; | ||
342 | |||
343 | i2c_smbus_write_byte_data(&data->client, | ||
344 | FSCHMD_REG_FAN_RIPPLE[data->kind][index], reg); | ||
345 | |||
346 | data->fan_ripple[index] = reg; | ||
347 | |||
348 | mutex_unlock(&data->update_lock); | ||
349 | |||
350 | return count; | ||
351 | } | ||
352 | |||
353 | static ssize_t show_fan_alarm(struct device *dev, | ||
354 | struct device_attribute *devattr, char *buf) | ||
355 | { | ||
356 | int index = to_sensor_dev_attr(devattr)->index; | ||
357 | struct fschmd_data *data = fschmd_update_device(dev); | ||
358 | |||
359 | if (data->fan_status[index] & FSCHMD_FAN_ALARM_MASK) | ||
360 | return sprintf(buf, "1\n"); | ||
361 | else | ||
362 | return sprintf(buf, "0\n"); | ||
363 | } | ||
364 | |||
365 | static ssize_t show_fan_fault(struct device *dev, | ||
366 | struct device_attribute *devattr, char *buf) | ||
367 | { | ||
368 | int index = to_sensor_dev_attr(devattr)->index; | ||
369 | struct fschmd_data *data = fschmd_update_device(dev); | ||
370 | |||
371 | if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT_MASK) | ||
372 | return sprintf(buf, "1\n"); | ||
373 | else | ||
374 | return sprintf(buf, "0\n"); | ||
375 | } | ||
376 | |||
377 | |||
378 | static ssize_t show_pwm_auto_point1_pwm(struct device *dev, | ||
379 | struct device_attribute *devattr, char *buf) | ||
380 | { | ||
381 | int index = to_sensor_dev_attr(devattr)->index; | ||
382 | int val = fschmd_update_device(dev)->fan_min[index]; | ||
383 | |||
384 | /* 0 = allow turning off, 1-255 = 50-100% */ | ||
385 | if (val) | ||
386 | val = val / 2 + 128; | ||
387 | |||
388 | return sprintf(buf, "%d\n", val); | ||
389 | } | ||
390 | |||
391 | static ssize_t store_pwm_auto_point1_pwm(struct device *dev, | ||
392 | struct device_attribute *devattr, const char *buf, size_t count) | ||
393 | { | ||
394 | int index = to_sensor_dev_attr(devattr)->index; | ||
395 | struct fschmd_data *data = dev_get_drvdata(dev); | ||
396 | unsigned long v = simple_strtoul(buf, NULL, 10); | ||
397 | |||
398 | /* register: 0 = allow turning off, 1-255 = 50-100% */ | ||
399 | if (v) { | ||
400 | v = SENSORS_LIMIT(v, 128, 255); | ||
401 | v = (v - 128) * 2 + 1; | ||
402 | } | ||
403 | |||
404 | mutex_lock(&data->update_lock); | ||
405 | |||
406 | i2c_smbus_write_byte_data(&data->client, | ||
407 | FSCHMD_REG_FAN_MIN[data->kind][index], v); | ||
408 | data->fan_min[index] = v; | ||
409 | |||
410 | mutex_unlock(&data->update_lock); | ||
411 | |||
412 | return count; | ||
413 | } | ||
414 | |||
415 | |||
416 | /* The FSC hwmon family has the ability to force an attached alert led to flash | ||
417 | from software, we export this as an alert_led sysfs attr */ | ||
418 | static ssize_t show_alert_led(struct device *dev, | ||
419 | struct device_attribute *devattr, char *buf) | ||
420 | { | ||
421 | struct fschmd_data *data = fschmd_update_device(dev); | ||
422 | |||
423 | if (data->global_control & FSCHMD_CONTROL_ALERT_LED_MASK) | ||
424 | return sprintf(buf, "1\n"); | ||
425 | else | ||
426 | return sprintf(buf, "0\n"); | ||
427 | } | ||
428 | |||
429 | static ssize_t store_alert_led(struct device *dev, | ||
430 | struct device_attribute *devattr, const char *buf, size_t count) | ||
431 | { | ||
432 | u8 reg; | ||
433 | struct fschmd_data *data = dev_get_drvdata(dev); | ||
434 | unsigned long v = simple_strtoul(buf, NULL, 10); | ||
435 | |||
436 | mutex_lock(&data->update_lock); | ||
437 | |||
438 | reg = i2c_smbus_read_byte_data(&data->client, FSCHMD_REG_CONTROL); | ||
439 | |||
440 | if (v) | ||
441 | reg |= FSCHMD_CONTROL_ALERT_LED_MASK; | ||
442 | else | ||
443 | reg &= ~FSCHMD_CONTROL_ALERT_LED_MASK; | ||
444 | |||
445 | i2c_smbus_write_byte_data(&data->client, FSCHMD_REG_CONTROL, reg); | ||
446 | |||
447 | data->global_control = reg; | ||
448 | |||
449 | mutex_unlock(&data->update_lock); | ||
450 | |||
451 | return count; | ||
452 | } | ||
453 | |||
454 | static struct sensor_device_attribute fschmd_attr[] = { | ||
455 | SENSOR_ATTR(in0_input, 0444, show_in_value, NULL, 0), | ||
456 | SENSOR_ATTR(in1_input, 0444, show_in_value, NULL, 1), | ||
457 | SENSOR_ATTR(in2_input, 0444, show_in_value, NULL, 2), | ||
458 | SENSOR_ATTR(alert_led, 0644, show_alert_led, store_alert_led, 0), | ||
459 | }; | ||
460 | |||
461 | static struct sensor_device_attribute fschmd_temp_attr[] = { | ||
462 | SENSOR_ATTR(temp1_input, 0444, show_temp_value, NULL, 0), | ||
463 | SENSOR_ATTR(temp1_max, 0644, show_temp_max, store_temp_max, 0), | ||
464 | SENSOR_ATTR(temp1_fault, 0444, show_temp_fault, NULL, 0), | ||
465 | SENSOR_ATTR(temp1_alarm, 0444, show_temp_alarm, NULL, 0), | ||
466 | SENSOR_ATTR(temp2_input, 0444, show_temp_value, NULL, 1), | ||
467 | SENSOR_ATTR(temp2_max, 0644, show_temp_max, store_temp_max, 1), | ||
468 | SENSOR_ATTR(temp2_fault, 0444, show_temp_fault, NULL, 1), | ||
469 | SENSOR_ATTR(temp2_alarm, 0444, show_temp_alarm, NULL, 1), | ||
470 | SENSOR_ATTR(temp3_input, 0444, show_temp_value, NULL, 2), | ||
471 | SENSOR_ATTR(temp3_max, 0644, show_temp_max, store_temp_max, 2), | ||
472 | SENSOR_ATTR(temp3_fault, 0444, show_temp_fault, NULL, 2), | ||
473 | SENSOR_ATTR(temp3_alarm, 0444, show_temp_alarm, NULL, 2), | ||
474 | SENSOR_ATTR(temp4_input, 0444, show_temp_value, NULL, 3), | ||
475 | SENSOR_ATTR(temp4_max, 0644, show_temp_max, store_temp_max, 3), | ||
476 | SENSOR_ATTR(temp4_fault, 0444, show_temp_fault, NULL, 3), | ||
477 | SENSOR_ATTR(temp4_alarm, 0444, show_temp_alarm, NULL, 3), | ||
478 | SENSOR_ATTR(temp5_input, 0444, show_temp_value, NULL, 4), | ||
479 | SENSOR_ATTR(temp5_max, 0644, show_temp_max, store_temp_max, 4), | ||
480 | SENSOR_ATTR(temp5_fault, 0444, show_temp_fault, NULL, 4), | ||
481 | SENSOR_ATTR(temp5_alarm, 0444, show_temp_alarm, NULL, 4), | ||
482 | }; | ||
483 | |||
484 | static struct sensor_device_attribute fschmd_fan_attr[] = { | ||
485 | SENSOR_ATTR(fan1_input, 0444, show_fan_value, NULL, 0), | ||
486 | SENSOR_ATTR(fan1_div, 0644, show_fan_div, store_fan_div, 0), | ||
487 | SENSOR_ATTR(fan1_alarm, 0444, show_fan_alarm, NULL, 0), | ||
488 | SENSOR_ATTR(fan1_fault, 0444, show_fan_fault, NULL, 0), | ||
489 | SENSOR_ATTR(pwm1_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
490 | store_pwm_auto_point1_pwm, 0), | ||
491 | SENSOR_ATTR(fan2_input, 0444, show_fan_value, NULL, 1), | ||
492 | SENSOR_ATTR(fan2_div, 0644, show_fan_div, store_fan_div, 1), | ||
493 | SENSOR_ATTR(fan2_alarm, 0444, show_fan_alarm, NULL, 1), | ||
494 | SENSOR_ATTR(fan2_fault, 0444, show_fan_fault, NULL, 1), | ||
495 | SENSOR_ATTR(pwm2_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
496 | store_pwm_auto_point1_pwm, 1), | ||
497 | SENSOR_ATTR(fan3_input, 0444, show_fan_value, NULL, 2), | ||
498 | SENSOR_ATTR(fan3_div, 0644, show_fan_div, store_fan_div, 2), | ||
499 | SENSOR_ATTR(fan3_alarm, 0444, show_fan_alarm, NULL, 2), | ||
500 | SENSOR_ATTR(fan3_fault, 0444, show_fan_fault, NULL, 2), | ||
501 | SENSOR_ATTR(pwm3_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
502 | store_pwm_auto_point1_pwm, 2), | ||
503 | SENSOR_ATTR(fan4_input, 0444, show_fan_value, NULL, 3), | ||
504 | SENSOR_ATTR(fan4_div, 0644, show_fan_div, store_fan_div, 3), | ||
505 | SENSOR_ATTR(fan4_alarm, 0444, show_fan_alarm, NULL, 3), | ||
506 | SENSOR_ATTR(fan4_fault, 0444, show_fan_fault, NULL, 3), | ||
507 | SENSOR_ATTR(pwm4_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
508 | store_pwm_auto_point1_pwm, 3), | ||
509 | SENSOR_ATTR(fan5_input, 0444, show_fan_value, NULL, 4), | ||
510 | SENSOR_ATTR(fan5_div, 0644, show_fan_div, store_fan_div, 4), | ||
511 | SENSOR_ATTR(fan5_alarm, 0444, show_fan_alarm, NULL, 4), | ||
512 | SENSOR_ATTR(fan5_fault, 0444, show_fan_fault, NULL, 4), | ||
513 | SENSOR_ATTR(pwm5_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
514 | store_pwm_auto_point1_pwm, 4), | ||
515 | SENSOR_ATTR(fan6_input, 0444, show_fan_value, NULL, 5), | ||
516 | SENSOR_ATTR(fan6_div, 0644, show_fan_div, store_fan_div, 5), | ||
517 | SENSOR_ATTR(fan6_alarm, 0444, show_fan_alarm, NULL, 5), | ||
518 | SENSOR_ATTR(fan6_fault, 0444, show_fan_fault, NULL, 5), | ||
519 | SENSOR_ATTR(pwm6_auto_point1_pwm, 0644, show_pwm_auto_point1_pwm, | ||
520 | store_pwm_auto_point1_pwm, 5), | ||
521 | }; | ||
522 | |||
523 | |||
524 | /* | ||
525 | * Real code | ||
526 | */ | ||
527 | |||
528 | static int fschmd_detect(struct i2c_adapter *adapter, int address, int kind) | ||
529 | { | ||
530 | struct i2c_client *client; | ||
531 | struct fschmd_data *data; | ||
532 | u8 revision; | ||
533 | const char * const names[5] = { "Poseidon", "Hermes", "Scylla", | ||
534 | "Heracles", "Heimdall" }; | ||
535 | const char * const client_names[5] = { "fscpos", "fscher", "fscscy", | ||
536 | "fschrc", "fschmd" }; | ||
537 | int i, err = 0; | ||
538 | |||
539 | if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
540 | return 0; | ||
541 | |||
542 | /* OK. For now, we presume we have a valid client. We now create the | ||
543 | * client structure, even though we cannot fill it completely yet. | ||
544 | * But it allows us to access i2c_smbus_read_byte_data. */ | ||
545 | if (!(data = kzalloc(sizeof(struct fschmd_data), GFP_KERNEL))) | ||
546 | return -ENOMEM; | ||
547 | |||
548 | client = &data->client; | ||
549 | i2c_set_clientdata(client, data); | ||
550 | client->addr = address; | ||
551 | client->adapter = adapter; | ||
552 | client->driver = &fschmd_driver; | ||
553 | mutex_init(&data->update_lock); | ||
554 | |||
555 | /* Detect & Identify the chip */ | ||
556 | if (kind <= 0) { | ||
557 | char id[4]; | ||
558 | |||
559 | id[0] = i2c_smbus_read_byte_data(client, | ||
560 | FSCHMD_REG_IDENT_0); | ||
561 | id[1] = i2c_smbus_read_byte_data(client, | ||
562 | FSCHMD_REG_IDENT_1); | ||
563 | id[2] = i2c_smbus_read_byte_data(client, | ||
564 | FSCHMD_REG_IDENT_2); | ||
565 | id[3] = '\0'; | ||
566 | |||
567 | if (!strcmp(id, "PEG")) | ||
568 | kind = fscpos; | ||
569 | else if (!strcmp(id, "HER")) | ||
570 | kind = fscher; | ||
571 | else if (!strcmp(id, "SCY")) | ||
572 | kind = fscscy; | ||
573 | else if (!strcmp(id, "HRC")) | ||
574 | kind = fschrc; | ||
575 | else if (!strcmp(id, "HMD")) | ||
576 | kind = fschmd; | ||
577 | else | ||
578 | goto exit_free; | ||
579 | } | ||
580 | |||
581 | if (kind == fscpos) { | ||
582 | /* The Poseidon has hardwired temp limits, fill these | ||
583 | in for the alarm resetting code */ | ||
584 | data->temp_max[0] = 70 + 128; | ||
585 | data->temp_max[1] = 50 + 128; | ||
586 | data->temp_max[2] = 50 + 128; | ||
587 | } | ||
588 | |||
589 | /* i2c kind goes from 1-5, we want from 0-4 to address arrays */ | ||
590 | data->kind = kind - 1; | ||
591 | strlcpy(client->name, client_names[data->kind], I2C_NAME_SIZE); | ||
592 | |||
593 | /* Tell the I2C layer a new client has arrived */ | ||
594 | if ((err = i2c_attach_client(client))) | ||
595 | goto exit_free; | ||
596 | |||
597 | for (i = 0; i < ARRAY_SIZE(fschmd_attr); i++) { | ||
598 | err = device_create_file(&client->dev, | ||
599 | &fschmd_attr[i].dev_attr); | ||
600 | if (err) | ||
601 | goto exit_detach; | ||
602 | } | ||
603 | |||
604 | for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++) { | ||
605 | /* Poseidon doesn't have TEMP_LIMIT registers */ | ||
606 | if (kind == fscpos && fschmd_temp_attr[i].dev_attr.show == | ||
607 | show_temp_max) | ||
608 | continue; | ||
609 | |||
610 | err = device_create_file(&client->dev, | ||
611 | &fschmd_temp_attr[i].dev_attr); | ||
612 | if (err) | ||
613 | goto exit_detach; | ||
614 | } | ||
615 | |||
616 | for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++) { | ||
617 | /* Poseidon doesn't have a FAN_MIN register for its 3rd fan */ | ||
618 | if (kind == fscpos && | ||
619 | !strcmp(fschmd_fan_attr[i].dev_attr.attr.name, | ||
620 | "pwm3_auto_point1_pwm")) | ||
621 | continue; | ||
622 | |||
623 | err = device_create_file(&client->dev, | ||
624 | &fschmd_fan_attr[i].dev_attr); | ||
625 | if (err) | ||
626 | goto exit_detach; | ||
627 | } | ||
628 | |||
629 | data->hwmon_dev = hwmon_device_register(&client->dev); | ||
630 | if (IS_ERR(data->hwmon_dev)) { | ||
631 | err = PTR_ERR(data->hwmon_dev); | ||
632 | data->hwmon_dev = NULL; | ||
633 | goto exit_detach; | ||
634 | } | ||
635 | |||
636 | revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); | ||
637 | printk(KERN_INFO FSCHMD_NAME ": Detected FSC %s chip, revision: %d\n", | ||
638 | names[data->kind], (int) revision); | ||
639 | |||
640 | return 0; | ||
641 | |||
642 | exit_detach: | ||
643 | fschmd_detach_client(client); /* will also free data for us */ | ||
644 | return err; | ||
645 | |||
646 | exit_free: | ||
647 | kfree(data); | ||
648 | return err; | ||
649 | } | ||
650 | |||
651 | static int fschmd_attach_adapter(struct i2c_adapter *adapter) | ||
652 | { | ||
653 | if (!(adapter->class & I2C_CLASS_HWMON)) | ||
654 | return 0; | ||
655 | return i2c_probe(adapter, &addr_data, fschmd_detect); | ||
656 | } | ||
657 | |||
658 | static int fschmd_detach_client(struct i2c_client *client) | ||
659 | { | ||
660 | struct fschmd_data *data = i2c_get_clientdata(client); | ||
661 | int i, err; | ||
662 | |||
663 | /* Check if registered in case we're called from fschmd_detect | ||
664 | to cleanup after an error */ | ||
665 | if (data->hwmon_dev) | ||
666 | hwmon_device_unregister(data->hwmon_dev); | ||
667 | |||
668 | for (i = 0; i < ARRAY_SIZE(fschmd_attr); i++) | ||
669 | device_remove_file(&client->dev, &fschmd_attr[i].dev_attr); | ||
670 | for (i = 0; i < (FSCHMD_NO_TEMP_SENSORS[data->kind] * 4); i++) | ||
671 | device_remove_file(&client->dev, | ||
672 | &fschmd_temp_attr[i].dev_attr); | ||
673 | for (i = 0; i < (FSCHMD_NO_FAN_SENSORS[data->kind] * 5); i++) | ||
674 | device_remove_file(&client->dev, | ||
675 | &fschmd_fan_attr[i].dev_attr); | ||
676 | |||
677 | if ((err = i2c_detach_client(client))) | ||
678 | return err; | ||
679 | |||
680 | kfree(data); | ||
681 | return 0; | ||
682 | } | ||
683 | |||
684 | static struct fschmd_data *fschmd_update_device(struct device *dev) | ||
685 | { | ||
686 | struct i2c_client *client = to_i2c_client(dev); | ||
687 | struct fschmd_data *data = i2c_get_clientdata(client); | ||
688 | int i; | ||
689 | |||
690 | mutex_lock(&data->update_lock); | ||
691 | |||
692 | if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { | ||
693 | |||
694 | for (i = 0; i < FSCHMD_NO_TEMP_SENSORS[data->kind]; i++) { | ||
695 | data->temp_act[i] = i2c_smbus_read_byte_data(client, | ||
696 | FSCHMD_REG_TEMP_ACT[data->kind][i]); | ||
697 | data->temp_status[i] = i2c_smbus_read_byte_data(client, | ||
698 | FSCHMD_REG_TEMP_STATE[data->kind][i]); | ||
699 | |||
700 | /* The fscpos doesn't have TEMP_LIMIT registers */ | ||
701 | if (FSCHMD_REG_TEMP_LIMIT[data->kind][i]) | ||
702 | data->temp_max[i] = i2c_smbus_read_byte_data( | ||
703 | client, | ||
704 | FSCHMD_REG_TEMP_LIMIT[data->kind][i]); | ||
705 | |||
706 | /* reset alarm if the alarm condition is gone, | ||
707 | the chip doesn't do this itself */ | ||
708 | if ((data->temp_status[i] & FSCHMD_TEMP_ALARM_MASK) == | ||
709 | FSCHMD_TEMP_ALARM_MASK && | ||
710 | data->temp_act[i] < data->temp_max[i]) | ||
711 | i2c_smbus_write_byte_data(client, | ||
712 | FSCHMD_REG_TEMP_STATE[data->kind][i], | ||
713 | FSCHMD_TEMP_ALERT_MASK); | ||
714 | } | ||
715 | |||
716 | for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { | ||
717 | data->fan_act[i] = i2c_smbus_read_byte_data(client, | ||
718 | FSCHMD_REG_FAN_ACT[data->kind][i]); | ||
719 | data->fan_status[i] = i2c_smbus_read_byte_data(client, | ||
720 | FSCHMD_REG_FAN_STATE[data->kind][i]); | ||
721 | data->fan_ripple[i] = i2c_smbus_read_byte_data(client, | ||
722 | FSCHMD_REG_FAN_RIPPLE[data->kind][i]); | ||
723 | |||
724 | /* The fscpos third fan doesn't have a fan_min */ | ||
725 | if (FSCHMD_REG_FAN_MIN[data->kind][i]) | ||
726 | data->fan_min[i] = i2c_smbus_read_byte_data( | ||
727 | client, | ||
728 | FSCHMD_REG_FAN_MIN[data->kind][i]); | ||
729 | |||
730 | /* reset fan status if speed is back to > 0 */ | ||
731 | if ((data->fan_status[i] & FSCHMD_FAN_ALARM_MASK) && | ||
732 | data->fan_act[i]) | ||
733 | i2c_smbus_write_byte_data(client, | ||
734 | FSCHMD_REG_FAN_STATE[data->kind][i], | ||
735 | FSCHMD_FAN_ALARM_MASK); | ||
736 | } | ||
737 | |||
738 | for (i = 0; i < 3; i++) | ||
739 | data->volt[i] = i2c_smbus_read_byte_data(client, | ||
740 | FSCHMD_REG_VOLT[i]); | ||
741 | |||
742 | data->global_control = i2c_smbus_read_byte_data(client, | ||
743 | FSCHMD_REG_CONTROL); | ||
744 | |||
745 | /* To be implemented in the future | ||
746 | data->watchdog[0] = i2c_smbus_read_byte_data(client, | ||
747 | FSCHMD_REG_WDOG_PRESET); | ||
748 | data->watchdog[1] = i2c_smbus_read_byte_data(client, | ||
749 | FSCHMD_REG_WDOG_STATE); | ||
750 | data->watchdog[2] = i2c_smbus_read_byte_data(client, | ||
751 | FSCHMD_REG_WDOG_CONTROL); */ | ||
752 | |||
753 | data->last_updated = jiffies; | ||
754 | data->valid = 1; | ||
755 | } | ||
756 | |||
757 | mutex_unlock(&data->update_lock); | ||
758 | |||
759 | return data; | ||
760 | } | ||
761 | |||
762 | static int __init fschmd_init(void) | ||
763 | { | ||
764 | return i2c_add_driver(&fschmd_driver); | ||
765 | } | ||
766 | |||
767 | static void __exit fschmd_exit(void) | ||
768 | { | ||
769 | i2c_del_driver(&fschmd_driver); | ||
770 | } | ||
771 | |||
772 | MODULE_AUTHOR("Hans de Goede <j.w.r.degoede@hhs.nl>"); | ||
773 | MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and " | ||
774 | "Heimdall driver"); | ||
775 | MODULE_LICENSE("GPL"); | ||
776 | |||
777 | module_init(fschmd_init); | ||
778 | module_exit(fschmd_exit); | ||