1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * HWMON driver for Lenovo ThinkStation based workstations 4 * via the embedded controller registers 5 * 6 * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com> 7 * 8 * EC provides: 9 * - CPU temperature 10 * - DIMM temperature 11 * - Chassis zone temperatures 12 * - CPU fan RPM 13 * - DIMM fan RPM 14 * - Chassis fans RPM 15 */ 16 17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 18 19 #include <linux/acpi.h> 20 #include <linux/bits.h> 21 #include <linux/delay.h> 22 #include <linux/device.h> 23 #include <linux/dmi.h> 24 #include <linux/err.h> 25 #include <linux/hwmon.h> 26 #include <linux/io.h> 27 #include <linux/ioport.h> 28 #include <linux/module.h> 29 #include <linux/mutex.h> 30 #include <linux/platform_device.h> 31 #include <linux/types.h> 32 #include <linux/units.h> 33 34 #define MCHP_SING_IDX 0x0000 35 #define MCHP_EMI0_APPLICATION_ID 0x090C 36 #define MCHP_EMI0_EC_ADDRESS 0x0902 37 #define MCHP_EMI0_EC_DATA_BYTE0 0x0904 38 #define MCHP_EMI0_EC_DATA_BYTE1 0x0905 39 #define MCHP_EMI0_EC_DATA_BYTE2 0x0906 40 #define MCHP_EMI0_EC_DATA_BYTE3 0x0907 41 #define IO_REGION_START 0x0900 42 #define IO_REGION_LENGTH 0xD 43 44 static inline u8 45 get_ec_reg(unsigned char page, unsigned char index) 46 { 47 u8 onebyte; 48 unsigned short m_index; 49 unsigned short phy_index = page * 256 + index; 50 51 outb_p(0x01, MCHP_EMI0_APPLICATION_ID); 52 53 m_index = phy_index & GENMASK(14, 2); 54 outw_p(m_index, MCHP_EMI0_EC_ADDRESS); 55 56 onebyte = inb_p(MCHP_EMI0_EC_DATA_BYTE0 + (phy_index & GENMASK(1, 0))); 57 58 outb_p(0x01, MCHP_EMI0_APPLICATION_ID); /* write 0x01 again to clean */ 59 return onebyte; 60 } 61 62 enum systems { 63 LENOVO_PX, 64 LENOVO_P7, 65 LENOVO_P5, 66 LENOVO_P8, 67 }; 68 69 static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; 70 71 static const char * const lenovo_px_ec_temp_label[] = { 72 "CPU1", 73 "CPU2", 74 "R_DIMM1", 75 "L_DIMM1", 76 "R_DIMM2", 77 "L_DIMM2", 78 "PCH", 79 "M2_R", 80 "M2_Z1R", 81 "M2_Z2R", 82 "PCI_Z1", 83 "PCI_Z2", 84 "PCI_Z3", 85 "PCI_Z4", 86 "AMB", 87 }; 88 89 static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; 90 91 static const char * const lenovo_gen_ec_temp_label[] = { 92 "CPU1", 93 "R_DIMM", 94 "L_DIMM", 95 "PCH", 96 "M2_R", 97 "M2_Z1R", 98 "M2_Z2R", 99 "PCI_Z1", 100 "PCI_Z2", 101 "PCI_Z3", 102 "PCI_Z4", 103 "AMB", 104 }; 105 106 static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; 107 108 static const char * const px_ec_fan_label[] = { 109 "CPU1_Fan", 110 "CPU2_Fan", 111 "Front_Fan1-1", 112 "Front_Fan1-2", 113 "Front_Fan2", 114 "Front_Fan3", 115 "MEM_Fan1", 116 "MEM_Fan2", 117 "Rear_Fan1", 118 "Rear_Fan2", 119 "Flex_Bay_Fan1", 120 "Flex_Bay_Fan2", 121 "Flex_Bay_Fan2", 122 "PSU_HDD_Fan", 123 "PSU1_Fan", 124 "PSU2_Fan", 125 }; 126 127 static int p7_fan_map[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14}; 128 129 static const char * const p7_ec_fan_label[] = { 130 "CPU1_Fan", 131 "HP_CPU_Fan1", 132 "HP_CPU_Fan2", 133 "PCIE1_4_Fan", 134 "PCIE5_7_Fan", 135 "MEM_Fan1", 136 "MEM_Fan2", 137 "Rear_Fan1", 138 "BCB_Fan", 139 "Flex_Bay_Fan", 140 "PSU_Fan", 141 }; 142 143 static int p5_fan_map[] = {0, 5, 6, 7, 8, 10, 11, 14}; 144 145 static const char * const p5_ec_fan_label[] = { 146 "CPU_Fan", 147 "HDD_Fan", 148 "Duct_Fan1", 149 "MEM_Fan", 150 "Rear_Fan", 151 "Front_Fan", 152 "Flex_Bay_Fan", 153 "PSU_Fan", 154 }; 155 156 static int p8_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14}; 157 158 static const char * const p8_ec_fan_label[] = { 159 "CPU1_Fan", 160 "CPU2_Fan", 161 "HP_CPU_Fan1", 162 "HP_CPU_Fan2", 163 "PCIE1_4_Fan", 164 "PCIE5_7_Fan", 165 "DIMM1_Fan1", 166 "DIMM1_Fan2", 167 "DIMM2_Fan1", 168 "DIMM2_Fan2", 169 "Rear_Fan", 170 "HDD_Bay_Fan", 171 "Flex_Bay_Fan", 172 "PSU_Fan", 173 }; 174 175 struct ec_sensors_data { 176 struct mutex mec_mutex; /* lock for sensor data access */ 177 const char *const *fan_labels; 178 const char *const *temp_labels; 179 const int *fan_map; 180 const int *temp_map; 181 }; 182 183 static int 184 lenovo_ec_do_read_temp(struct ec_sensors_data *data, u32 attr, int channel, long *val) 185 { 186 u8 lsb; 187 188 switch (attr) { 189 case hwmon_temp_input: 190 mutex_lock(&data->mec_mutex); 191 lsb = get_ec_reg(2, 0x81 + channel); 192 mutex_unlock(&data->mec_mutex); 193 if (lsb <= 0x40) 194 return -ENODATA; 195 *val = (lsb - 0x40) * 1000; 196 return 0; 197 default: 198 return -EOPNOTSUPP; 199 } 200 } 201 202 static int 203 lenovo_ec_do_read_fan(struct ec_sensors_data *data, u32 attr, int channel, long *val) 204 { 205 u8 lsb, msb; 206 207 channel *= 2; 208 switch (attr) { 209 case hwmon_fan_input: 210 mutex_lock(&data->mec_mutex); 211 lsb = get_ec_reg(4, 0x20 + channel); 212 msb = get_ec_reg(4, 0x21 + channel); 213 mutex_unlock(&data->mec_mutex); 214 *val = (msb << 8) + lsb; 215 return 0; 216 case hwmon_fan_max: 217 mutex_lock(&data->mec_mutex); 218 lsb = get_ec_reg(4, 0x40 + channel); 219 msb = get_ec_reg(4, 0x41 + channel); 220 mutex_unlock(&data->mec_mutex); 221 *val = (msb << 8) + lsb; 222 return 0; 223 default: 224 return -EOPNOTSUPP; 225 } 226 } 227 228 static int 229 lenovo_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, 230 u32 attr, int channel, const char **str) 231 { 232 struct ec_sensors_data *state = dev_get_drvdata(dev); 233 234 switch (type) { 235 case hwmon_temp: 236 *str = state->temp_labels[channel]; 237 return 0; 238 case hwmon_fan: 239 *str = state->fan_labels[channel]; 240 return 0; 241 default: 242 return -EOPNOTSUPP; 243 } 244 } 245 246 static int 247 lenovo_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 248 u32 attr, int channel, long *val) 249 { 250 struct ec_sensors_data *data = dev_get_drvdata(dev); 251 252 switch (type) { 253 case hwmon_temp: 254 return lenovo_ec_do_read_temp(data, attr, data->temp_map[channel], val); 255 case hwmon_fan: 256 return lenovo_ec_do_read_fan(data, attr, data->fan_map[channel], val); 257 default: 258 return -EOPNOTSUPP; 259 } 260 } 261 262 static umode_t 263 lenovo_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, 264 u32 attr, int channel) 265 { 266 switch (type) { 267 case hwmon_temp: 268 if (attr == hwmon_temp_input || attr == hwmon_temp_label) 269 return 0444; 270 return 0; 271 case hwmon_fan: 272 if (attr == hwmon_fan_input || attr == hwmon_fan_max || attr == hwmon_fan_label) 273 return 0444; 274 return 0; 275 default: 276 return 0; 277 } 278 } 279 280 static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = { 281 HWMON_CHANNEL_INFO(temp, 282 HWMON_T_INPUT | HWMON_T_LABEL, 283 HWMON_T_INPUT | HWMON_T_LABEL, 284 HWMON_T_INPUT | HWMON_T_LABEL, 285 HWMON_T_INPUT | HWMON_T_LABEL, 286 HWMON_T_INPUT | HWMON_T_LABEL, 287 HWMON_T_INPUT | HWMON_T_LABEL, 288 HWMON_T_INPUT | HWMON_T_LABEL, 289 HWMON_T_INPUT | HWMON_T_LABEL, 290 HWMON_T_INPUT | HWMON_T_LABEL, 291 HWMON_T_INPUT | HWMON_T_LABEL, 292 HWMON_T_INPUT | HWMON_T_LABEL, 293 HWMON_T_INPUT | HWMON_T_LABEL, 294 HWMON_T_INPUT | HWMON_T_LABEL, 295 HWMON_T_INPUT | HWMON_T_LABEL, 296 HWMON_T_INPUT | HWMON_T_LABEL), 297 HWMON_CHANNEL_INFO(fan, 298 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 299 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 300 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 301 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 302 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 303 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 304 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 305 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 306 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 307 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 308 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 309 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 310 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 311 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 312 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 313 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), 314 NULL 315 }; 316 317 static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = { 318 HWMON_CHANNEL_INFO(temp, 319 HWMON_T_INPUT | HWMON_T_LABEL, 320 HWMON_T_INPUT | HWMON_T_LABEL, 321 HWMON_T_INPUT | HWMON_T_LABEL, 322 HWMON_T_INPUT | HWMON_T_LABEL, 323 HWMON_T_INPUT | HWMON_T_LABEL, 324 HWMON_T_INPUT | HWMON_T_LABEL, 325 HWMON_T_INPUT | HWMON_T_LABEL, 326 HWMON_T_INPUT | HWMON_T_LABEL, 327 HWMON_T_INPUT | HWMON_T_LABEL, 328 HWMON_T_INPUT | HWMON_T_LABEL, 329 HWMON_T_INPUT | HWMON_T_LABEL, 330 HWMON_T_INPUT | HWMON_T_LABEL), 331 HWMON_CHANNEL_INFO(fan, 332 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 333 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 334 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 335 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 336 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 337 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 338 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 339 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 340 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 341 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 342 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 343 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 344 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 345 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), 346 NULL 347 }; 348 349 static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = { 350 HWMON_CHANNEL_INFO(temp, 351 HWMON_T_INPUT | HWMON_T_LABEL, 352 HWMON_T_INPUT | HWMON_T_LABEL, 353 HWMON_T_INPUT | HWMON_T_LABEL, 354 HWMON_T_INPUT | HWMON_T_LABEL, 355 HWMON_T_INPUT | HWMON_T_LABEL, 356 HWMON_T_INPUT | HWMON_T_LABEL, 357 HWMON_T_INPUT | HWMON_T_LABEL, 358 HWMON_T_INPUT | HWMON_T_LABEL, 359 HWMON_T_INPUT | HWMON_T_LABEL, 360 HWMON_T_INPUT | HWMON_T_LABEL, 361 HWMON_T_INPUT | HWMON_T_LABEL, 362 HWMON_T_INPUT | HWMON_T_LABEL), 363 HWMON_CHANNEL_INFO(fan, 364 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 365 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 366 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 367 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 368 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 369 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 370 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 371 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 372 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 373 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 374 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), 375 NULL 376 }; 377 378 static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = { 379 HWMON_CHANNEL_INFO(temp, 380 HWMON_T_INPUT | HWMON_T_LABEL, 381 HWMON_T_INPUT | HWMON_T_LABEL, 382 HWMON_T_INPUT | HWMON_T_LABEL, 383 HWMON_T_INPUT | HWMON_T_LABEL, 384 HWMON_T_INPUT | HWMON_T_LABEL, 385 HWMON_T_INPUT | HWMON_T_LABEL, 386 HWMON_T_INPUT | HWMON_T_LABEL, 387 HWMON_T_INPUT | HWMON_T_LABEL, 388 HWMON_T_INPUT | HWMON_T_LABEL, 389 HWMON_T_INPUT | HWMON_T_LABEL, 390 HWMON_T_INPUT | HWMON_T_LABEL, 391 HWMON_T_INPUT | HWMON_T_LABEL), 392 HWMON_CHANNEL_INFO(fan, 393 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 394 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 395 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 396 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 397 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 398 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 399 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, 400 HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX), 401 NULL 402 }; 403 404 static const struct hwmon_ops lenovo_ec_hwmon_ops = { 405 .is_visible = lenovo_ec_hwmon_is_visible, 406 .read = lenovo_ec_hwmon_read, 407 .read_string = lenovo_ec_hwmon_read_string, 408 }; 409 410 static struct hwmon_chip_info lenovo_ec_chip_info = { 411 .ops = &lenovo_ec_hwmon_ops, 412 }; 413 414 static const struct dmi_system_id thinkstation_dmi_table[] = { 415 { 416 .ident = "LENOVO_PX", 417 .driver_data = (void *)(long)LENOVO_PX, 418 .matches = { 419 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 420 DMI_MATCH(DMI_PRODUCT_NAME, "30EU"), 421 }, 422 }, 423 { 424 .ident = "LENOVO_PX", 425 .driver_data = (void *)(long)LENOVO_PX, 426 .matches = { 427 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 428 DMI_MATCH(DMI_PRODUCT_NAME, "30EV"), 429 }, 430 }, 431 { 432 .ident = "LENOVO_P7", 433 .driver_data = (void *)(long)LENOVO_P7, 434 .matches = { 435 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 436 DMI_MATCH(DMI_PRODUCT_NAME, "30F2"), 437 }, 438 }, 439 { 440 .ident = "LENOVO_P7", 441 .driver_data = (void *)(long)LENOVO_P7, 442 .matches = { 443 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 444 DMI_MATCH(DMI_PRODUCT_NAME, "30F3"), 445 }, 446 }, 447 { 448 .ident = "LENOVO_P5", 449 .driver_data = (void *)(long)LENOVO_P5, 450 .matches = { 451 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 452 DMI_MATCH(DMI_PRODUCT_NAME, "30G9"), 453 }, 454 }, 455 { 456 .ident = "LENOVO_P5", 457 .driver_data = (void *)(long)LENOVO_P5, 458 .matches = { 459 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 460 DMI_MATCH(DMI_PRODUCT_NAME, "30GA"), 461 }, 462 }, 463 { 464 .ident = "LENOVO_P8", 465 .driver_data = (void *)(long)LENOVO_P8, 466 .matches = { 467 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 468 DMI_MATCH(DMI_PRODUCT_NAME, "30HH"), 469 }, 470 }, 471 { 472 .ident = "LENOVO_P8", 473 .driver_data = (void *)(long)LENOVO_P8, 474 .matches = { 475 DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), 476 DMI_MATCH(DMI_PRODUCT_NAME, "30HJ"), 477 }, 478 }, 479 {} 480 }; 481 MODULE_DEVICE_TABLE(dmi, thinkstation_dmi_table); 482 483 static int lenovo_ec_probe(struct platform_device *pdev) 484 { 485 struct device *hwdev; 486 struct ec_sensors_data *ec_data; 487 const struct hwmon_chip_info *chip_info; 488 struct device *dev = &pdev->dev; 489 const struct dmi_system_id *dmi_id; 490 int app_id; 491 492 ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), GFP_KERNEL); 493 if (!ec_data) 494 return -ENOMEM; 495 496 if (!request_region(IO_REGION_START, IO_REGION_LENGTH, "LNV-WKS")) { 497 pr_err(":request fail\n"); 498 return -EIO; 499 } 500 501 dev_set_drvdata(dev, ec_data); 502 503 chip_info = &lenovo_ec_chip_info; 504 505 mutex_init(&ec_data->mec_mutex); 506 507 mutex_lock(&ec_data->mec_mutex); 508 app_id = inb_p(MCHP_EMI0_APPLICATION_ID); 509 if (app_id) /* check EMI Application ID Value */ 510 outb_p(app_id, MCHP_EMI0_APPLICATION_ID); /* set EMI Application ID to 0 */ 511 outw_p(MCHP_SING_IDX, MCHP_EMI0_EC_ADDRESS); 512 mutex_unlock(&ec_data->mec_mutex); 513 514 if ((inb_p(MCHP_EMI0_EC_DATA_BYTE0) != 'M') && 515 (inb_p(MCHP_EMI0_EC_DATA_BYTE1) != 'C') && 516 (inb_p(MCHP_EMI0_EC_DATA_BYTE2) != 'H') && 517 (inb_p(MCHP_EMI0_EC_DATA_BYTE3) != 'P')) { 518 release_region(IO_REGION_START, IO_REGION_LENGTH); 519 return -ENODEV; 520 } 521 522 dmi_id = dmi_first_match(thinkstation_dmi_table); 523 524 switch ((long)dmi_id->driver_data) { 525 case 0: 526 ec_data->fan_labels = px_ec_fan_label; 527 ec_data->temp_labels = lenovo_px_ec_temp_label; 528 ec_data->fan_map = px_fan_map; 529 ec_data->temp_map = px_temp_map; 530 lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_px; 531 break; 532 case 1: 533 ec_data->fan_labels = p7_ec_fan_label; 534 ec_data->temp_labels = lenovo_gen_ec_temp_label; 535 ec_data->fan_map = p7_fan_map; 536 ec_data->temp_map = gen_temp_map; 537 lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p7; 538 break; 539 case 2: 540 ec_data->fan_labels = p5_ec_fan_label; 541 ec_data->temp_labels = lenovo_gen_ec_temp_label; 542 ec_data->fan_map = p5_fan_map; 543 ec_data->temp_map = gen_temp_map; 544 lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p5; 545 break; 546 case 3: 547 ec_data->fan_labels = p8_ec_fan_label; 548 ec_data->temp_labels = lenovo_gen_ec_temp_label; 549 ec_data->fan_map = p8_fan_map; 550 ec_data->temp_map = gen_temp_map; 551 lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8; 552 break; 553 default: 554 release_region(IO_REGION_START, IO_REGION_LENGTH); 555 return -ENODEV; 556 } 557 558 hwdev = devm_hwmon_device_register_with_info(dev, "lenovo_ec", 559 ec_data, 560 chip_info, NULL); 561 562 return PTR_ERR_OR_ZERO(hwdev); 563 } 564 565 static struct platform_driver lenovo_ec_sensors_platform_driver = { 566 .driver = { 567 .name = "lenovo-ec-sensors", 568 }, 569 .probe = lenovo_ec_probe, 570 }; 571 572 static struct platform_device *lenovo_ec_sensors_platform_device; 573 574 static int __init lenovo_ec_init(void) 575 { 576 if (!dmi_check_system(thinkstation_dmi_table)) 577 return -ENODEV; 578 579 lenovo_ec_sensors_platform_device = 580 platform_create_bundle(&lenovo_ec_sensors_platform_driver, 581 lenovo_ec_probe, NULL, 0, NULL, 0); 582 583 if (IS_ERR(lenovo_ec_sensors_platform_device)) { 584 release_region(IO_REGION_START, IO_REGION_LENGTH); 585 return PTR_ERR(lenovo_ec_sensors_platform_device); 586 } 587 588 return 0; 589 } 590 module_init(lenovo_ec_init); 591 592 static void __exit lenovo_ec_exit(void) 593 { 594 release_region(IO_REGION_START, IO_REGION_LENGTH); 595 platform_device_unregister(lenovo_ec_sensors_platform_device); 596 platform_driver_unregister(&lenovo_ec_sensors_platform_driver); 597 } 598 module_exit(lenovo_ec_exit); 599 600 MODULE_AUTHOR("David Ober <dober@lenovo.com>"); 601 MODULE_DESCRIPTION("HWMON driver for sensors accessible via EC in LENOVO motherboards"); 602 MODULE_LICENSE("GPL"); 603