Commit | Line | Data |
---|---|---|
c942fddf | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
08426eda TC |
2 | /* |
3 | * Support for the FTS Systemmonitoring Chip "Teutates" | |
4 | * | |
5 | * Copyright (C) 2016 Fujitsu Technology Solutions GmbH, | |
6 | * Thilo Cestonaro <thilo.cestonaro@ts.fujitsu.com> | |
08426eda TC |
7 | */ |
8 | #include <linux/err.h> | |
08426eda | 9 | #include <linux/hwmon.h> |
08426eda TC |
10 | #include <linux/i2c.h> |
11 | #include <linux/init.h> | |
12 | #include <linux/jiffies.h> | |
ca8fd8c1 | 13 | #include <linux/math.h> |
08426eda TC |
14 | #include <linux/module.h> |
15 | #include <linux/mutex.h> | |
16 | #include <linux/slab.h> | |
08426eda TC |
17 | #include <linux/watchdog.h> |
18 | ||
19 | #define FTS_DEVICE_ID_REG 0x0000 | |
20 | #define FTS_DEVICE_REVISION_REG 0x0001 | |
21 | #define FTS_DEVICE_STATUS_REG 0x0004 | |
22 | #define FTS_SATELLITE_STATUS_REG 0x0005 | |
23 | #define FTS_EVENT_STATUS_REG 0x0006 | |
24 | #define FTS_GLOBAL_CONTROL_REG 0x0007 | |
25 | ||
2d5aee43 TC |
26 | #define FTS_DEVICE_DETECT_REG_1 0x0C |
27 | #define FTS_DEVICE_DETECT_REG_2 0x0D | |
28 | #define FTS_DEVICE_DETECT_REG_3 0x0E | |
29 | ||
08426eda TC |
30 | #define FTS_SENSOR_EVENT_REG 0x0010 |
31 | ||
32 | #define FTS_FAN_EVENT_REG 0x0014 | |
33 | #define FTS_FAN_PRESENT_REG 0x0015 | |
34 | ||
35 | #define FTS_POWER_ON_TIME_COUNTER_A 0x007A | |
36 | #define FTS_POWER_ON_TIME_COUNTER_B 0x007B | |
37 | #define FTS_POWER_ON_TIME_COUNTER_C 0x007C | |
38 | ||
39 | #define FTS_PAGE_SELECT_REG 0x007F | |
40 | ||
41 | #define FTS_WATCHDOG_TIME_PRESET 0x000B | |
42 | #define FTS_WATCHDOG_CONTROL 0x5081 | |
43 | ||
44 | #define FTS_NO_FAN_SENSORS 0x08 | |
45 | #define FTS_NO_TEMP_SENSORS 0x10 | |
46 | #define FTS_NO_VOLT_SENSORS 0x04 | |
47 | ||
1c5759d8 AW |
48 | #define FTS_FAN_SOURCE_INVALID 0xff |
49 | ||
2d5aee43 TC |
50 | static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; |
51 | ||
1dad2e95 | 52 | static const struct i2c_device_id fts_id[] = { |
08426eda TC |
53 | { "ftsteutates", 0 }, |
54 | { } | |
55 | }; | |
56 | MODULE_DEVICE_TABLE(i2c, fts_id); | |
57 | ||
58 | enum WATCHDOG_RESOLUTION { | |
59 | seconds = 1, | |
60 | minutes = 60 | |
61 | }; | |
62 | ||
63 | struct fts_data { | |
64 | struct i2c_client *client; | |
65 | /* update sensor data lock */ | |
66 | struct mutex update_lock; | |
67 | /* read/write register lock */ | |
68 | struct mutex access_lock; | |
69 | unsigned long last_updated; /* in jiffies */ | |
70 | struct watchdog_device wdd; | |
71 | enum WATCHDOG_RESOLUTION resolution; | |
72 | bool valid; /* false until following fields are valid */ | |
73 | ||
74 | u8 volt[FTS_NO_VOLT_SENSORS]; | |
75 | ||
76 | u8 temp_input[FTS_NO_TEMP_SENSORS]; | |
77 | u8 temp_alarm; | |
78 | ||
79 | u8 fan_present; | |
80 | u8 fan_input[FTS_NO_FAN_SENSORS]; /* in rps */ | |
81 | u8 fan_source[FTS_NO_FAN_SENSORS]; | |
82 | u8 fan_alarm; | |
83 | }; | |
84 | ||
85 | #define FTS_REG_FAN_INPUT(idx) ((idx) + 0x20) | |
86 | #define FTS_REG_FAN_SOURCE(idx) ((idx) + 0x30) | |
87 | #define FTS_REG_FAN_CONTROL(idx) (((idx) << 16) + 0x4881) | |
88 | ||
89 | #define FTS_REG_TEMP_INPUT(idx) ((idx) + 0x40) | |
90 | #define FTS_REG_TEMP_CONTROL(idx) (((idx) << 16) + 0x0681) | |
91 | ||
92 | #define FTS_REG_VOLT(idx) ((idx) + 0x18) | |
93 | ||
94 | /*****************************************************************************/ | |
95 | /* I2C Helper functions */ | |
96 | /*****************************************************************************/ | |
97 | static int fts_read_byte(struct i2c_client *client, unsigned short reg) | |
98 | { | |
99 | int ret; | |
100 | unsigned char page = reg >> 8; | |
101 | struct fts_data *data = dev_get_drvdata(&client->dev); | |
102 | ||
103 | mutex_lock(&data->access_lock); | |
104 | ||
105 | dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); | |
106 | ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); | |
107 | if (ret < 0) | |
108 | goto error; | |
109 | ||
110 | reg &= 0xFF; | |
111 | ret = i2c_smbus_read_byte_data(client, reg); | |
112 | dev_dbg(&client->dev, "read - reg: 0x%.02x: val: 0x%.02x\n", reg, ret); | |
113 | ||
114 | error: | |
115 | mutex_unlock(&data->access_lock); | |
116 | return ret; | |
117 | } | |
118 | ||
119 | static int fts_write_byte(struct i2c_client *client, unsigned short reg, | |
120 | unsigned char value) | |
121 | { | |
122 | int ret; | |
123 | unsigned char page = reg >> 8; | |
124 | struct fts_data *data = dev_get_drvdata(&client->dev); | |
125 | ||
126 | mutex_lock(&data->access_lock); | |
127 | ||
128 | dev_dbg(&client->dev, "page select - page: 0x%.02x\n", page); | |
129 | ret = i2c_smbus_write_byte_data(client, FTS_PAGE_SELECT_REG, page); | |
130 | if (ret < 0) | |
131 | goto error; | |
132 | ||
133 | reg &= 0xFF; | |
134 | dev_dbg(&client->dev, | |
135 | "write - reg: 0x%.02x: val: 0x%.02x\n", reg, value); | |
136 | ret = i2c_smbus_write_byte_data(client, reg, value); | |
137 | ||
138 | error: | |
139 | mutex_unlock(&data->access_lock); | |
140 | return ret; | |
141 | } | |
142 | ||
143 | /*****************************************************************************/ | |
144 | /* Data Updater Helper function */ | |
145 | /*****************************************************************************/ | |
146 | static int fts_update_device(struct fts_data *data) | |
147 | { | |
148 | int i; | |
149 | int err = 0; | |
150 | ||
151 | mutex_lock(&data->update_lock); | |
152 | if (!time_after(jiffies, data->last_updated + 2 * HZ) && data->valid) | |
153 | goto exit; | |
154 | ||
155 | err = fts_read_byte(data->client, FTS_DEVICE_STATUS_REG); | |
156 | if (err < 0) | |
157 | goto exit; | |
158 | ||
159 | data->valid = !!(err & 0x02); /* Data not ready yet */ | |
160 | if (unlikely(!data->valid)) { | |
161 | err = -EAGAIN; | |
162 | goto exit; | |
163 | } | |
164 | ||
165 | err = fts_read_byte(data->client, FTS_FAN_PRESENT_REG); | |
166 | if (err < 0) | |
167 | goto exit; | |
168 | data->fan_present = err; | |
169 | ||
170 | err = fts_read_byte(data->client, FTS_FAN_EVENT_REG); | |
171 | if (err < 0) | |
172 | goto exit; | |
173 | data->fan_alarm = err; | |
174 | ||
175 | for (i = 0; i < FTS_NO_FAN_SENSORS; i++) { | |
176 | if (data->fan_present & BIT(i)) { | |
177 | err = fts_read_byte(data->client, FTS_REG_FAN_INPUT(i)); | |
178 | if (err < 0) | |
179 | goto exit; | |
180 | data->fan_input[i] = err; | |
181 | ||
182 | err = fts_read_byte(data->client, | |
183 | FTS_REG_FAN_SOURCE(i)); | |
184 | if (err < 0) | |
185 | goto exit; | |
186 | data->fan_source[i] = err; | |
187 | } else { | |
188 | data->fan_input[i] = 0; | |
1c5759d8 | 189 | data->fan_source[i] = FTS_FAN_SOURCE_INVALID; |
08426eda TC |
190 | } |
191 | } | |
192 | ||
193 | err = fts_read_byte(data->client, FTS_SENSOR_EVENT_REG); | |
194 | if (err < 0) | |
195 | goto exit; | |
196 | data->temp_alarm = err; | |
197 | ||
198 | for (i = 0; i < FTS_NO_TEMP_SENSORS; i++) { | |
199 | err = fts_read_byte(data->client, FTS_REG_TEMP_INPUT(i)); | |
200 | if (err < 0) | |
201 | goto exit; | |
202 | data->temp_input[i] = err; | |
203 | } | |
204 | ||
205 | for (i = 0; i < FTS_NO_VOLT_SENSORS; i++) { | |
206 | err = fts_read_byte(data->client, FTS_REG_VOLT(i)); | |
207 | if (err < 0) | |
208 | goto exit; | |
209 | data->volt[i] = err; | |
210 | } | |
211 | data->last_updated = jiffies; | |
212 | err = 0; | |
213 | exit: | |
214 | mutex_unlock(&data->update_lock); | |
215 | return err; | |
216 | } | |
217 | ||
218 | /*****************************************************************************/ | |
219 | /* Watchdog functions */ | |
220 | /*****************************************************************************/ | |
221 | static int fts_wd_set_resolution(struct fts_data *data, | |
222 | enum WATCHDOG_RESOLUTION resolution) | |
223 | { | |
224 | int ret; | |
225 | ||
226 | if (data->resolution == resolution) | |
227 | return 0; | |
228 | ||
229 | ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); | |
230 | if (ret < 0) | |
231 | return ret; | |
232 | ||
233 | if ((resolution == seconds && ret & BIT(1)) || | |
234 | (resolution == minutes && (ret & BIT(1)) == 0)) { | |
235 | data->resolution = resolution; | |
236 | return 0; | |
237 | } | |
238 | ||
239 | if (resolution == seconds) | |
4c8702b3 | 240 | ret |= BIT(1); |
08426eda TC |
241 | else |
242 | ret &= ~BIT(1); | |
243 | ||
244 | ret = fts_write_byte(data->client, FTS_WATCHDOG_CONTROL, ret); | |
245 | if (ret < 0) | |
246 | return ret; | |
247 | ||
248 | data->resolution = resolution; | |
249 | return ret; | |
250 | } | |
251 | ||
252 | static int fts_wd_set_timeout(struct watchdog_device *wdd, unsigned int timeout) | |
253 | { | |
254 | struct fts_data *data; | |
255 | enum WATCHDOG_RESOLUTION resolution = seconds; | |
256 | int ret; | |
257 | ||
258 | data = watchdog_get_drvdata(wdd); | |
259 | /* switch watchdog resolution to minutes if timeout does not fit | |
260 | * into a byte | |
261 | */ | |
262 | if (timeout > 0xFF) { | |
263 | timeout = DIV_ROUND_UP(timeout, 60) * 60; | |
264 | resolution = minutes; | |
265 | } | |
266 | ||
267 | ret = fts_wd_set_resolution(data, resolution); | |
268 | if (ret < 0) | |
269 | return ret; | |
270 | ||
271 | wdd->timeout = timeout; | |
272 | return 0; | |
273 | } | |
274 | ||
275 | static int fts_wd_start(struct watchdog_device *wdd) | |
276 | { | |
277 | struct fts_data *data = watchdog_get_drvdata(wdd); | |
278 | ||
279 | return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, | |
280 | wdd->timeout / (u8)data->resolution); | |
281 | } | |
282 | ||
283 | static int fts_wd_stop(struct watchdog_device *wdd) | |
284 | { | |
285 | struct fts_data *data; | |
286 | ||
287 | data = watchdog_get_drvdata(wdd); | |
288 | return fts_write_byte(data->client, FTS_WATCHDOG_TIME_PRESET, 0); | |
289 | } | |
290 | ||
291 | static const struct watchdog_info fts_wd_info = { | |
292 | .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, | |
293 | .identity = "FTS Teutates Hardware Watchdog", | |
294 | }; | |
295 | ||
296 | static const struct watchdog_ops fts_wd_ops = { | |
297 | .owner = THIS_MODULE, | |
298 | .start = fts_wd_start, | |
299 | .stop = fts_wd_stop, | |
300 | .set_timeout = fts_wd_set_timeout, | |
301 | }; | |
302 | ||
303 | static int fts_watchdog_init(struct fts_data *data) | |
304 | { | |
305 | int timeout, ret; | |
306 | ||
307 | watchdog_set_drvdata(&data->wdd, data); | |
308 | ||
309 | timeout = fts_read_byte(data->client, FTS_WATCHDOG_TIME_PRESET); | |
310 | if (timeout < 0) | |
311 | return timeout; | |
312 | ||
313 | /* watchdog not running, set timeout to a default of 60 sec. */ | |
314 | if (timeout == 0) { | |
315 | ret = fts_wd_set_resolution(data, seconds); | |
316 | if (ret < 0) | |
317 | return ret; | |
318 | data->wdd.timeout = 60; | |
319 | } else { | |
320 | ret = fts_read_byte(data->client, FTS_WATCHDOG_CONTROL); | |
321 | if (ret < 0) | |
322 | return ret; | |
323 | ||
324 | data->resolution = ret & BIT(1) ? seconds : minutes; | |
325 | data->wdd.timeout = timeout * (u8)data->resolution; | |
326 | set_bit(WDOG_HW_RUNNING, &data->wdd.status); | |
327 | } | |
328 | ||
329 | /* Register our watchdog part */ | |
330 | data->wdd.info = &fts_wd_info; | |
331 | data->wdd.ops = &fts_wd_ops; | |
332 | data->wdd.parent = &data->client->dev; | |
333 | data->wdd.min_timeout = 1; | |
334 | ||
335 | /* max timeout 255 minutes. */ | |
336 | data->wdd.max_hw_heartbeat_ms = 0xFF * 60 * MSEC_PER_SEC; | |
337 | ||
e1515a74 | 338 | return devm_watchdog_register_device(&data->client->dev, &data->wdd); |
08426eda TC |
339 | } |
340 | ||
dea60ff0 AW |
341 | static umode_t fts_is_visible(const void *devdata, enum hwmon_sensor_types type, u32 attr, |
342 | int channel) | |
343 | { | |
344 | switch (type) { | |
345 | case hwmon_temp: | |
346 | switch (attr) { | |
347 | case hwmon_temp_input: | |
348 | case hwmon_temp_fault: | |
349 | return 0444; | |
350 | case hwmon_temp_alarm: | |
351 | return 0644; | |
352 | default: | |
353 | break; | |
354 | } | |
355 | break; | |
356 | case hwmon_fan: | |
357 | switch (attr) { | |
358 | case hwmon_fan_input: | |
c184f377 | 359 | case hwmon_fan_fault: |
dea60ff0 AW |
360 | return 0444; |
361 | case hwmon_fan_alarm: | |
362 | return 0644; | |
363 | default: | |
364 | break; | |
365 | } | |
366 | break; | |
1c5759d8 | 367 | case hwmon_pwm: |
dea60ff0 AW |
368 | case hwmon_in: |
369 | return 0444; | |
370 | default: | |
371 | break; | |
372 | } | |
08426eda | 373 | |
dea60ff0 | 374 | return 0; |
08426eda TC |
375 | } |
376 | ||
dea60ff0 AW |
377 | static int fts_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, |
378 | long *val) | |
08426eda TC |
379 | { |
380 | struct fts_data *data = dev_get_drvdata(dev); | |
dea60ff0 | 381 | int ret = fts_update_device(data); |
08426eda | 382 | |
08426eda TC |
383 | if (ret < 0) |
384 | return ret; | |
385 | ||
dea60ff0 AW |
386 | switch (type) { |
387 | case hwmon_temp: | |
388 | switch (attr) { | |
389 | case hwmon_temp_input: | |
390 | *val = (data->temp_input[channel] - 64) * 1000; | |
ca8fd8c1 | 391 | |
dea60ff0 AW |
392 | return 0; |
393 | case hwmon_temp_alarm: | |
394 | *val = !!(data->temp_alarm & BIT(channel)); | |
08426eda | 395 | |
dea60ff0 AW |
396 | return 0; |
397 | case hwmon_temp_fault: | |
398 | /* 00h Temperature = Sensor Error */; | |
399 | *val = (data->temp_input[channel] == 0); | |
08426eda | 400 | |
dea60ff0 AW |
401 | return 0; |
402 | default: | |
403 | break; | |
404 | } | |
405 | break; | |
406 | case hwmon_fan: | |
407 | switch (attr) { | |
408 | case hwmon_fan_input: | |
409 | *val = data->fan_input[channel] * 60; | |
08426eda | 410 | |
dea60ff0 AW |
411 | return 0; |
412 | case hwmon_fan_alarm: | |
413 | *val = !!(data->fan_alarm & BIT(channel)); | |
08426eda | 414 | |
c184f377 AW |
415 | return 0; |
416 | case hwmon_fan_fault: | |
417 | *val = !(data->fan_present & BIT(channel)); | |
418 | ||
dea60ff0 AW |
419 | return 0; |
420 | default: | |
421 | break; | |
422 | } | |
423 | break; | |
1c5759d8 AW |
424 | case hwmon_pwm: |
425 | switch (attr) { | |
426 | case hwmon_pwm_auto_channels_temp: | |
427 | if (data->fan_source[channel] == FTS_FAN_SOURCE_INVALID) | |
428 | *val = 0; | |
429 | else | |
430 | *val = BIT(data->fan_source[channel]); | |
431 | ||
432 | return 0; | |
433 | default: | |
434 | break; | |
435 | } | |
436 | break; | |
dea60ff0 AW |
437 | case hwmon_in: |
438 | switch (attr) { | |
439 | case hwmon_in_input: | |
440 | *val = DIV_ROUND_CLOSEST(data->volt[channel] * 3300, 255); | |
08426eda | 441 | |
dea60ff0 AW |
442 | return 0; |
443 | default: | |
444 | break; | |
445 | } | |
446 | break; | |
447 | default: | |
448 | break; | |
449 | } | |
08426eda | 450 | |
dea60ff0 | 451 | return -EOPNOTSUPP; |
08426eda TC |
452 | } |
453 | ||
dea60ff0 AW |
454 | static int fts_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, |
455 | long val) | |
08426eda TC |
456 | { |
457 | struct fts_data *data = dev_get_drvdata(dev); | |
dea60ff0 | 458 | int ret = fts_update_device(data); |
08426eda | 459 | |
08426eda TC |
460 | if (ret < 0) |
461 | return ret; | |
462 | ||
dea60ff0 AW |
463 | switch (type) { |
464 | case hwmon_temp: | |
465 | switch (attr) { | |
466 | case hwmon_temp_alarm: | |
467 | if (val) | |
468 | return -EINVAL; | |
469 | ||
470 | mutex_lock(&data->update_lock); | |
471 | ret = fts_read_byte(data->client, FTS_REG_TEMP_CONTROL(channel)); | |
472 | if (ret >= 0) | |
473 | ret = fts_write_byte(data->client, FTS_REG_TEMP_CONTROL(channel), | |
474 | ret | 0x1); | |
475 | if (ret >= 0) | |
476 | data->valid = false; | |
477 | ||
478 | mutex_unlock(&data->update_lock); | |
479 | if (ret < 0) | |
480 | return ret; | |
481 | ||
482 | return 0; | |
483 | default: | |
484 | break; | |
485 | } | |
486 | break; | |
487 | case hwmon_fan: | |
488 | switch (attr) { | |
489 | case hwmon_fan_alarm: | |
490 | if (val) | |
491 | return -EINVAL; | |
492 | ||
493 | mutex_lock(&data->update_lock); | |
494 | ret = fts_read_byte(data->client, FTS_REG_FAN_CONTROL(channel)); | |
495 | if (ret >= 0) | |
496 | ret = fts_write_byte(data->client, FTS_REG_FAN_CONTROL(channel), | |
497 | ret | 0x1); | |
498 | if (ret >= 0) | |
499 | data->valid = false; | |
500 | ||
501 | mutex_unlock(&data->update_lock); | |
502 | if (ret < 0) | |
503 | return ret; | |
504 | ||
505 | return 0; | |
506 | default: | |
507 | break; | |
508 | } | |
509 | break; | |
510 | default: | |
511 | break; | |
512 | } | |
08426eda | 513 | |
dea60ff0 | 514 | return -EOPNOTSUPP; |
08426eda TC |
515 | } |
516 | ||
dea60ff0 AW |
517 | static const struct hwmon_ops fts_ops = { |
518 | .is_visible = fts_is_visible, | |
519 | .read = fts_read, | |
520 | .write = fts_write, | |
08426eda TC |
521 | }; |
522 | ||
06c37796 | 523 | static const struct hwmon_channel_info * const fts_info[] = { |
dea60ff0 AW |
524 | HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), |
525 | HWMON_CHANNEL_INFO(temp, | |
526 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
527 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
528 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
529 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
530 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
531 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
532 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
533 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
534 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
535 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
536 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
537 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
538 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
539 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
540 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT, | |
541 | HWMON_T_INPUT | HWMON_T_ALARM | HWMON_T_FAULT | |
542 | ), | |
543 | HWMON_CHANNEL_INFO(fan, | |
c184f377 AW |
544 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, |
545 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
546 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
547 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
548 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
549 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
550 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT, | |
551 | HWMON_F_INPUT | HWMON_F_ALARM | HWMON_F_FAULT | |
dea60ff0 | 552 | ), |
1c5759d8 AW |
553 | HWMON_CHANNEL_INFO(pwm, |
554 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
555 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
556 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
557 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
558 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
559 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
560 | HWMON_PWM_AUTO_CHANNELS_TEMP, | |
561 | HWMON_PWM_AUTO_CHANNELS_TEMP | |
562 | ), | |
dea60ff0 AW |
563 | HWMON_CHANNEL_INFO(in, |
564 | HWMON_I_INPUT, | |
565 | HWMON_I_INPUT, | |
566 | HWMON_I_INPUT, | |
567 | HWMON_I_INPUT | |
568 | ), | |
08426eda TC |
569 | NULL |
570 | }; | |
571 | ||
dea60ff0 AW |
572 | static const struct hwmon_chip_info fts_chip_info = { |
573 | .ops = &fts_ops, | |
574 | .info = fts_info, | |
08426eda TC |
575 | }; |
576 | ||
577 | /*****************************************************************************/ | |
578 | /* Module initialization / remove functions */ | |
579 | /*****************************************************************************/ | |
2d5aee43 TC |
580 | static int fts_detect(struct i2c_client *client, |
581 | struct i2c_board_info *info) | |
582 | { | |
583 | int val; | |
584 | ||
23bc3caf | 585 | /* detection works with revision greater or equal to 0x2b */ |
2d5aee43 TC |
586 | val = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG); |
587 | if (val < 0x2b) | |
588 | return -ENODEV; | |
589 | ||
590 | /* Device Detect Regs must have 0x17 0x34 and 0x54 */ | |
591 | val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_1); | |
592 | if (val != 0x17) | |
593 | return -ENODEV; | |
594 | ||
595 | val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_2); | |
596 | if (val != 0x34) | |
597 | return -ENODEV; | |
598 | ||
599 | val = i2c_smbus_read_byte_data(client, FTS_DEVICE_DETECT_REG_3); | |
600 | if (val != 0x54) | |
601 | return -ENODEV; | |
602 | ||
603 | /* | |
604 | * 0x10 == Baseboard Management Controller, 0x01 == Teutates | |
605 | * Device ID Reg needs to be 0x11 | |
606 | */ | |
607 | val = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG); | |
608 | if (val != 0x11) | |
609 | return -ENODEV; | |
610 | ||
f2f394db | 611 | strscpy(info->type, fts_id[0].name, I2C_NAME_SIZE); |
2d5aee43 TC |
612 | info->flags = 0; |
613 | return 0; | |
614 | } | |
615 | ||
67487038 | 616 | static int fts_probe(struct i2c_client *client) |
08426eda TC |
617 | { |
618 | u8 revision; | |
619 | struct fts_data *data; | |
620 | int err; | |
621 | s8 deviceid; | |
622 | struct device *hwmon_dev; | |
623 | ||
624 | if (client->addr != 0x73) | |
625 | return -ENODEV; | |
626 | ||
627 | /* Baseboard Management Controller check */ | |
628 | deviceid = i2c_smbus_read_byte_data(client, FTS_DEVICE_ID_REG); | |
629 | if (deviceid > 0 && (deviceid & 0xF0) == 0x10) { | |
630 | switch (deviceid & 0x0F) { | |
631 | case 0x01: | |
632 | break; | |
633 | default: | |
634 | dev_dbg(&client->dev, | |
635 | "No Baseboard Management Controller\n"); | |
636 | return -ENODEV; | |
637 | } | |
638 | } else { | |
639 | dev_dbg(&client->dev, "No fujitsu board\n"); | |
640 | return -ENODEV; | |
641 | } | |
642 | ||
643 | data = devm_kzalloc(&client->dev, sizeof(struct fts_data), | |
644 | GFP_KERNEL); | |
645 | if (!data) | |
646 | return -ENOMEM; | |
647 | ||
648 | mutex_init(&data->update_lock); | |
649 | mutex_init(&data->access_lock); | |
650 | data->client = client; | |
651 | dev_set_drvdata(&client->dev, data); | |
652 | ||
653 | err = i2c_smbus_read_byte_data(client, FTS_DEVICE_REVISION_REG); | |
654 | if (err < 0) | |
655 | return err; | |
656 | revision = err; | |
657 | ||
dea60ff0 | 658 | hwmon_dev = devm_hwmon_device_register_with_info(&client->dev, "ftsteutates", data, |
1c5759d8 | 659 | &fts_chip_info, NULL); |
08426eda TC |
660 | if (IS_ERR(hwmon_dev)) |
661 | return PTR_ERR(hwmon_dev); | |
662 | ||
663 | err = fts_watchdog_init(data); | |
664 | if (err) | |
665 | return err; | |
666 | ||
667 | dev_info(&client->dev, "Detected FTS Teutates chip, revision: %d.%d\n", | |
668 | (revision & 0xF0) >> 4, revision & 0x0F); | |
669 | return 0; | |
670 | } | |
671 | ||
672 | /*****************************************************************************/ | |
673 | /* Module Details */ | |
674 | /*****************************************************************************/ | |
675 | static struct i2c_driver fts_driver = { | |
2d5aee43 | 676 | .class = I2C_CLASS_HWMON, |
08426eda TC |
677 | .driver = { |
678 | .name = "ftsteutates", | |
679 | }, | |
680 | .id_table = fts_id, | |
1975d167 | 681 | .probe = fts_probe, |
2d5aee43 TC |
682 | .detect = fts_detect, |
683 | .address_list = normal_i2c, | |
08426eda TC |
684 | }; |
685 | ||
686 | module_i2c_driver(fts_driver); | |
687 | ||
688 | MODULE_AUTHOR("Thilo Cestonaro <thilo.cestonaro@ts.fujitsu.com>"); | |
689 | MODULE_DESCRIPTION("FTS Teutates driver"); | |
690 | MODULE_LICENSE("GPL"); |