1 // SPDX-License-Identifier: GPL-2.0+
3 * HWMON driver for Lenovo ThinkStation based workstations
4 * via the embedded controller registers
6 * Copyright (C) 2024 David Ober (Lenovo) <dober@lenovo.com>
11 * - Chassis zone temperatures
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
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>
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>
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
45 get_ec_reg(unsigned char page
, unsigned char index
)
48 unsigned short m_index
;
49 unsigned short phy_index
= page
* 256 + index
;
51 outb_p(0x01, MCHP_EMI0_APPLICATION_ID
);
53 m_index
= phy_index
& GENMASK(14, 2);
54 outw_p(m_index
, MCHP_EMI0_EC_ADDRESS
);
56 onebyte
= inb_p(MCHP_EMI0_EC_DATA_BYTE0
+ (phy_index
& GENMASK(1, 0)));
58 outb_p(0x01, MCHP_EMI0_APPLICATION_ID
); /* write 0x01 again to clean */
69 static int px_temp_map
[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
71 static const char * const lenovo_px_ec_temp_label
[] = {
89 static int gen_temp_map
[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
91 static const char * const lenovo_gen_ec_temp_label
[] = {
106 static int px_fan_map
[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
108 static const char * const px_ec_fan_label
[] = {
127 static int p7_fan_map
[] = {0, 2, 3, 4, 5, 6, 7, 8, 10, 11, 14};
129 static const char * const p7_ec_fan_label
[] = {
143 static int p5_fan_map
[] = {0, 5, 6, 7, 8, 10, 11, 14};
145 static const char * const p5_ec_fan_label
[] = {
156 static int p8_fan_map
[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14};
158 static const char * const p8_ec_fan_label
[] = {
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
;
184 lenovo_ec_do_read_temp(struct ec_sensors_data
*data
, u32 attr
, int channel
, long *val
)
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
);
195 *val
= (lsb
- 0x40) * 1000;
203 lenovo_ec_do_read_fan(struct ec_sensors_data
*data
, u32 attr
, int channel
, long *val
)
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
;
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
;
229 lenovo_ec_hwmon_read_string(struct device
*dev
, enum hwmon_sensor_types type
,
230 u32 attr
, int channel
, const char **str
)
232 struct ec_sensors_data
*state
= dev_get_drvdata(dev
);
236 *str
= state
->temp_labels
[channel
];
239 *str
= state
->fan_labels
[channel
];
247 lenovo_ec_hwmon_read(struct device
*dev
, enum hwmon_sensor_types type
,
248 u32 attr
, int channel
, long *val
)
250 struct ec_sensors_data
*data
= dev_get_drvdata(dev
);
254 return lenovo_ec_do_read_temp(data
, attr
, data
->temp_map
[channel
], val
);
256 return lenovo_ec_do_read_fan(data
, attr
, data
->fan_map
[channel
], val
);
263 lenovo_ec_hwmon_is_visible(const void *data
, enum hwmon_sensor_types type
,
264 u32 attr
, int channel
)
268 if (attr
== hwmon_temp_input
|| attr
== hwmon_temp_label
)
272 if (attr
== hwmon_fan_input
|| attr
== hwmon_fan_max
|| attr
== hwmon_fan_label
)
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
),
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
),
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
),
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
),
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
,
410 static struct hwmon_chip_info lenovo_ec_chip_info
= {
411 .ops
= &lenovo_ec_hwmon_ops
,
414 static const struct dmi_system_id thinkstation_dmi_table
[] = {
416 .ident
= "LENOVO_PX",
417 .driver_data
= (void *)(long)LENOVO_PX
,
419 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
420 DMI_MATCH(DMI_PRODUCT_NAME
, "30EU"),
424 .ident
= "LENOVO_PX",
425 .driver_data
= (void *)(long)LENOVO_PX
,
427 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
428 DMI_MATCH(DMI_PRODUCT_NAME
, "30EV"),
432 .ident
= "LENOVO_P7",
433 .driver_data
= (void *)(long)LENOVO_P7
,
435 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
436 DMI_MATCH(DMI_PRODUCT_NAME
, "30F2"),
440 .ident
= "LENOVO_P7",
441 .driver_data
= (void *)(long)LENOVO_P7
,
443 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
444 DMI_MATCH(DMI_PRODUCT_NAME
, "30F3"),
448 .ident
= "LENOVO_P5",
449 .driver_data
= (void *)(long)LENOVO_P5
,
451 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
452 DMI_MATCH(DMI_PRODUCT_NAME
, "30G9"),
456 .ident
= "LENOVO_P5",
457 .driver_data
= (void *)(long)LENOVO_P5
,
459 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
460 DMI_MATCH(DMI_PRODUCT_NAME
, "30GA"),
464 .ident
= "LENOVO_P8",
465 .driver_data
= (void *)(long)LENOVO_P8
,
467 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
468 DMI_MATCH(DMI_PRODUCT_NAME
, "30HH"),
472 .ident
= "LENOVO_P8",
473 .driver_data
= (void *)(long)LENOVO_P8
,
475 DMI_MATCH(DMI_SYS_VENDOR
, "LENOVO"),
476 DMI_MATCH(DMI_PRODUCT_NAME
, "30HJ"),
481 MODULE_DEVICE_TABLE(dmi
, thinkstation_dmi_table
);
483 static int lenovo_ec_probe(struct platform_device
*pdev
)
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
;
492 ec_data
= devm_kzalloc(dev
, sizeof(struct ec_sensors_data
), GFP_KERNEL
);
496 if (!request_region(IO_REGION_START
, IO_REGION_LENGTH
, "LNV-WKS")) {
497 pr_err(":request fail\n");
501 dev_set_drvdata(dev
, ec_data
);
503 chip_info
= &lenovo_ec_chip_info
;
505 mutex_init(&ec_data
->mec_mutex
);
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
);
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
);
522 dmi_id
= dmi_first_match(thinkstation_dmi_table
);
524 switch ((long)dmi_id
->driver_data
) {
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
;
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
;
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
;
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
;
554 release_region(IO_REGION_START
, IO_REGION_LENGTH
);
558 hwdev
= devm_hwmon_device_register_with_info(dev
, "lenovo_ec",
562 return PTR_ERR_OR_ZERO(hwdev
);
565 static struct platform_driver lenovo_ec_sensors_platform_driver
= {
567 .name
= "lenovo-ec-sensors",
569 .probe
= lenovo_ec_probe
,
572 static struct platform_device
*lenovo_ec_sensors_platform_device
;
574 static int __init
lenovo_ec_init(void)
576 if (!dmi_check_system(thinkstation_dmi_table
))
579 lenovo_ec_sensors_platform_device
=
580 platform_create_bundle(&lenovo_ec_sensors_platform_driver
,
581 lenovo_ec_probe
, NULL
, 0, NULL
, 0);
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
);
590 module_init(lenovo_ec_init
);
592 static void __exit
lenovo_ec_exit(void)
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
);
598 module_exit(lenovo_ec_exit
);
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");