603 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * HWMON driver for Lenovo ThinkStation based workstations4 * via the embedded controller registers5 *6 * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com>7 *8 * EC provides:9 * - CPU temperature10 * - DIMM temperature11 * - Chassis zone temperatures12 * - CPU fan RPM13 * - DIMM fan RPM14 * - Chassis fans RPM15 */16 17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt18 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 0x000035#define MCHP_EMI0_APPLICATION_ID 0x090C36#define MCHP_EMI0_EC_ADDRESS 0x090237#define MCHP_EMI0_EC_DATA_BYTE0 0x090438#define MCHP_EMI0_EC_DATA_BYTE1 0x090539#define MCHP_EMI0_EC_DATA_BYTE2 0x090640#define MCHP_EMI0_EC_DATA_BYTE3 0x090741#define IO_REGION_START 0x090042#define IO_REGION_LENGTH 0xD43 44static inline u845get_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 62enum systems {63 LENOVO_PX,64 LENOVO_P7,65 LENOVO_P5,66 LENOVO_P8,67};68 69static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};70 71static 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 89static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};90 91static 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 106static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};107 108static 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 127static int p7_fan_map[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14};128 129static 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 143static int p5_fan_map[] = {0, 5, 6, 7, 8, 10, 11, 14};144 145static 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 156static int p8_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14};157 158static 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 175struct 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 183static int184lenovo_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 202static int203lenovo_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 228static int229lenovo_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 246static int247lenovo_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 262static umode_t263lenovo_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 280static 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 NULL315};316 317static 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 NULL347};348 349static 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 NULL376};377 378static 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 NULL402};403 404static 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 410static struct hwmon_chip_info lenovo_ec_chip_info = {411 .ops = &lenovo_ec_hwmon_ops,412};413 414static 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};481MODULE_DEVICE_TABLE(dmi, thinkstation_dmi_table);482 483static 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 565static struct platform_driver lenovo_ec_sensors_platform_driver = {566 .driver = {567 .name = "lenovo-ec-sensors",568 },569 .probe = lenovo_ec_probe,570};571 572static struct platform_device *lenovo_ec_sensors_platform_device;573 574static 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}590module_init(lenovo_ec_init);591 592static 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}598module_exit(lenovo_ec_exit);599 600MODULE_AUTHOR("David Ober <dober@lenovo.com>");601MODULE_DESCRIPTION("HWMON driver for sensors accessible via EC in LENOVO motherboards");602MODULE_LICENSE("GPL");603