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