Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

hwmon: Add EC Chip driver for Lenovo ThinkStation motherboards

This addition adds in the ability for the system to scan
the EC chip in the Lenovo ThinkStation systems to get the
current fan RPM speeds the Maximum speed value for each
fan also provides the CPU, DIMM other thermal statuses

Signed-off-by: David Ober <dober6023@gmail.com>
Link: https://lore.kernel.org/r/20240328121250.331146-1-dober6023@gmail.com
[groeck: Dropped pointless case statements]
[Colin King: Fixed spelling error accesssible -> accessible]
Signed-off-by: Guenter Roeck <linux@roeck-us.net>

authored by

David Ober and committed by
Guenter Roeck
70118f85 91630090

+613
+10
drivers/hwmon/Kconfig
··· 917 917 This driver can also be built as a module. If so, the module 918 918 will be called lan966x-hwmon. 919 919 920 + config SENSORS_LENOVO_EC 921 + tristate "Sensor reader for Lenovo ThinkStations" 922 + depends on X86 923 + help 924 + If you say yes here you get support for LENOVO 925 + EC Sensor data on newer ThinkStation systems 926 + 927 + This driver can also be built as a module. If so, the module 928 + will be called lenovo_ec_sensors. 929 + 920 930 config SENSORS_LINEAGE 921 931 tristate "Lineage Compact Power Line Power Entry Module" 922 932 depends on I2C
+1
drivers/hwmon/Makefile
··· 108 108 obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o 109 109 obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o 110 110 obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o 111 + obj-$(CONFIG_SENSORS_LENOVO_EC) += lenovo-ec-sensors.o 111 112 obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o 112 113 obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o 113 114 obj-$(CONFIG_SENSORS_LM63) += lm63.o
+602
drivers/hwmon/lenovo-ec-sensors.c
··· 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");