1 // SPDX-License-Identifier: GPL-2.0-or-later
4 * msi-ec: MSI laptops' embedded controller driver.
6 * This driver allows various MSI laptops' functionalities to be
7 * controlled from userspace.
9 * It contains EC memory configurations for different firmware versions
10 * and exports battery charge thresholds to userspace.
12 * Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es>
13 * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
14 * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
21 #include <acpi/battery.h>
22 #include <linux/acpi.h>
23 #include <linux/init.h>
24 #include <linux/kernel.h>
25 #include <linux/module.h>
26 #include <linux/platform_device.h>
27 #include <linux/seq_file.h>
28 #include <linux/string.h>
30 #define SM_ECO_NAME "eco"
31 #define SM_COMFORT_NAME "comfort"
32 #define SM_SPORT_NAME "sport"
33 #define SM_TURBO_NAME "turbo"
35 #define FM_AUTO_NAME "auto"
36 #define FM_SILENT_NAME "silent"
37 #define FM_BASIC_NAME "basic"
38 #define FM_ADVANCED_NAME "advanced"
40 static const char * const ALLOWED_FW_0[] __initconst = {
47 static struct msi_ec_conf CONF0 __initdata = {
48 .allowed_fw = ALLOWED_FW_0,
58 .block_address = 0x2f,
72 { SM_ECO_NAME, 0xc2 },
73 { SM_COMFORT_NAME, 0xc1 },
74 { SM_SPORT_NAME, 0xc0 },
79 .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing
84 { FM_AUTO_NAME, 0x0d },
85 { FM_SILENT_NAME, 0x1d },
86 { FM_BASIC_NAME, 0x4d },
87 { FM_ADVANCED_NAME, 0x8d },
92 .rt_temp_address = 0x68,
93 .rt_fan_speed_address = 0x71,
94 .rt_fan_speed_base_min = 0x19,
95 .rt_fan_speed_base_max = 0x37,
96 .bs_fan_speed_address = 0x89,
97 .bs_fan_speed_base_min = 0x00,
98 .bs_fan_speed_base_max = 0x0f,
101 .rt_temp_address = 0x80,
102 .rt_fan_speed_address = 0x89,
105 .micmute_led_address = 0x2b,
106 .mute_led_address = 0x2c,
110 .bl_mode_address = 0x2c, // ?
111 .bl_modes = { 0x00, 0x08 }, // ?
113 .bl_state_address = 0xf3,
114 .state_base_value = 0x80,
119 static const char * const ALLOWED_FW_1[] __initconst = {
127 static struct msi_ec_conf CONF1 __initdata = {
128 .allowed_fw = ALLOWED_FW_1,
131 .offset_start = 0x8a,
138 .block_address = 0x2f,
152 { SM_ECO_NAME, 0xc2 },
153 { SM_COMFORT_NAME, 0xc1 },
154 { SM_SPORT_NAME, 0xc0 },
155 { SM_TURBO_NAME, 0xc4 },
160 .address = MSI_EC_ADDR_UNKNOWN,
165 { FM_AUTO_NAME, 0x0d },
166 { FM_BASIC_NAME, 0x4d },
167 { FM_ADVANCED_NAME, 0x8d },
172 .rt_temp_address = 0x68,
173 .rt_fan_speed_address = 0x71,
174 .rt_fan_speed_base_min = 0x19,
175 .rt_fan_speed_base_max = 0x37,
176 .bs_fan_speed_address = 0x89,
177 .bs_fan_speed_base_min = 0x00,
178 .bs_fan_speed_base_max = 0x0f,
181 .rt_temp_address = 0x80,
182 .rt_fan_speed_address = 0x89,
185 .micmute_led_address = 0x2b,
186 .mute_led_address = 0x2c,
190 .bl_mode_address = 0x2c, // ?
191 .bl_modes = { 0x00, 0x08 }, // ?
193 .bl_state_address = 0xf3,
194 .state_base_value = 0x80,
199 static const char * const ALLOWED_FW_2[] __initconst = {
204 static struct msi_ec_conf CONF2 __initdata = {
205 .allowed_fw = ALLOWED_FW_2,
208 .offset_start = 0x8a,
215 .block_address = 0x2f,
229 { SM_ECO_NAME, 0xc2 },
230 { SM_COMFORT_NAME, 0xc1 },
231 { SM_SPORT_NAME, 0xc0 },
242 { FM_AUTO_NAME, 0x0d },
243 { FM_SILENT_NAME, 0x1d },
244 { FM_BASIC_NAME, 0x4d },
245 { FM_ADVANCED_NAME, 0x8d },
250 .rt_temp_address = 0x68,
251 .rt_fan_speed_address = 0x71,
252 .rt_fan_speed_base_min = 0x19,
253 .rt_fan_speed_base_max = 0x37,
254 .bs_fan_speed_address = 0x89,
255 .bs_fan_speed_base_min = 0x00,
256 .bs_fan_speed_base_max = 0x0f,
259 .rt_temp_address = 0x80,
260 .rt_fan_speed_address = 0x89,
263 .micmute_led_address = 0x2c,
264 .mute_led_address = 0x2d,
268 .bl_mode_address = 0x2c, // ?
269 .bl_modes = { 0x00, 0x08 }, // ?
271 .bl_state_address = 0xd3,
272 .state_base_value = 0x80,
277 static const char * const ALLOWED_FW_3[] __initconst = {
282 static struct msi_ec_conf CONF3 __initdata = {
283 .allowed_fw = ALLOWED_FW_3,
286 .offset_start = 0x8a,
293 .block_address = 0x2f,
307 { SM_ECO_NAME, 0xc2 },
308 { SM_COMFORT_NAME, 0xc1 },
309 { SM_SPORT_NAME, 0xc0 },
320 { FM_AUTO_NAME, 0x0d },
321 { FM_SILENT_NAME, 0x1d },
322 { FM_BASIC_NAME, 0x4d },
323 { FM_ADVANCED_NAME, 0x8d },
328 .rt_temp_address = 0x68,
329 .rt_fan_speed_address = 0xc9,
330 .rt_fan_speed_base_min = 0x19,
331 .rt_fan_speed_base_max = 0x37,
332 .bs_fan_speed_address = 0x89, // ?
333 .bs_fan_speed_base_min = 0x00,
334 .bs_fan_speed_base_max = 0x0f,
337 .rt_temp_address = 0x80,
338 .rt_fan_speed_address = 0x89,
341 .micmute_led_address = 0x2b,
342 .mute_led_address = 0x2c,
346 .bl_mode_address = 0x2c, // ?
347 .bl_modes = { 0x00, 0x08 }, // ?
349 .bl_state_address = 0xd3,
350 .state_base_value = 0x80,
355 static const char * const ALLOWED_FW_4[] __initconst = {
360 static struct msi_ec_conf CONF4 __initdata = {
361 .allowed_fw = ALLOWED_FW_4,
364 .offset_start = 0x8a,
371 .block_address = 0x2f,
375 .address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
385 { SM_ECO_NAME, 0xc2 },
386 { SM_COMFORT_NAME, 0xc1 },
387 { SM_SPORT_NAME, 0xc0 },
391 .super_battery = { // may be supported, but address is unknown
392 .address = MSI_EC_ADDR_UNKNOWN,
398 { FM_AUTO_NAME, 0x0d },
399 { FM_SILENT_NAME, 0x1d },
400 { FM_ADVANCED_NAME, 0x8d },
405 .rt_temp_address = 0x68, // needs testing
406 .rt_fan_speed_address = 0x71, // needs testing
407 .rt_fan_speed_base_min = 0x19,
408 .rt_fan_speed_base_max = 0x37,
409 .bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
410 .bs_fan_speed_base_min = 0x00,
411 .bs_fan_speed_base_max = 0x0f,
414 .rt_temp_address = 0x80,
415 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
418 .micmute_led_address = MSI_EC_ADDR_UNKNOWN,
419 .mute_led_address = MSI_EC_ADDR_UNKNOWN,
423 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
424 .bl_modes = { 0x00, 0x08 }, // ?
426 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
427 .state_base_value = 0x80,
432 static const char * const ALLOWED_FW_5[] __initconst = {
439 static struct msi_ec_conf CONF5 __initdata = {
440 .allowed_fw = ALLOWED_FW_5,
443 .offset_start = 0x8a,
450 .block_address = 0x2f,
453 .fn_super_swap = { // todo: reverse
464 { SM_ECO_NAME, 0xc2 },
465 { SM_COMFORT_NAME, 0xc1 },
466 { SM_TURBO_NAME, 0xc4 },
470 .super_battery = { // unsupported?
471 .address = MSI_EC_ADDR_UNKNOWN,
477 { FM_AUTO_NAME, 0x0d },
478 { FM_SILENT_NAME, 0x1d },
479 { FM_ADVANCED_NAME, 0x8d },
484 .rt_temp_address = 0x68, // needs testing
485 .rt_fan_speed_address = 0x71, // needs testing
486 .rt_fan_speed_base_min = 0x19,
487 .rt_fan_speed_base_max = 0x37,
488 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
489 .bs_fan_speed_base_min = 0x00,
490 .bs_fan_speed_base_max = 0x0f,
493 .rt_temp_address = MSI_EC_ADDR_UNKNOWN,
494 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
497 .micmute_led_address = 0x2b,
498 .mute_led_address = 0x2c,
502 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
503 .bl_modes = { 0x00, 0x08 }, // ?
505 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
506 .state_base_value = 0x80,
511 static const char * const ALLOWED_FW_6[] __initconst = {
517 static struct msi_ec_conf CONF6 __initdata = {
518 .allowed_fw = ALLOWED_FW_6,
521 .offset_start = 0x8a,
528 .block_address = MSI_EC_ADDR_UNSUPP,
532 .address = 0xbf, // todo: reverse
542 { SM_ECO_NAME, 0xc2 },
543 { SM_COMFORT_NAME, 0xc1 },
544 { SM_SPORT_NAME, 0xc0 },
545 { SM_TURBO_NAME, 0xc4 },
556 { FM_AUTO_NAME, 0x0d },
557 { FM_SILENT_NAME, 0x1d },
558 { FM_ADVANCED_NAME, 0x8d },
563 .rt_temp_address = 0x68,
564 .rt_fan_speed_address = 0xc9,
565 .rt_fan_speed_base_min = 0x19,
566 .rt_fan_speed_base_max = 0x37,
567 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
568 .bs_fan_speed_base_min = 0x00,
569 .bs_fan_speed_base_max = 0x0f,
572 .rt_temp_address = 0x80,
573 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
576 .micmute_led_address = MSI_EC_ADDR_UNSUPP,
577 .mute_led_address = MSI_EC_ADDR_UNSUPP,
581 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
582 .bl_modes = { 0x00, 0x08 }, // ?
584 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
585 .state_base_value = 0x80,
590 static const char * const ALLOWED_FW_7[] __initconst = {
597 static struct msi_ec_conf CONF7 __initdata = {
598 .allowed_fw = ALLOWED_FW_7,
601 .offset_start = 0x8a,
608 .block_address = MSI_EC_ADDR_UNSUPP,
612 .address = 0xbf, // needs testing
622 { SM_ECO_NAME, 0xc2 },
623 { SM_COMFORT_NAME, 0xc1 },
624 { SM_SPORT_NAME, 0xc0 },
625 { SM_TURBO_NAME, 0xc4 },
630 .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
636 { FM_AUTO_NAME, 0x0d }, // d may not be relevant
637 { FM_SILENT_NAME, 0x1d },
638 { FM_ADVANCED_NAME, 0x8d },
643 .rt_temp_address = 0x68,
644 .rt_fan_speed_address = 0xc9, // needs testing
645 .rt_fan_speed_base_min = 0x19,
646 .rt_fan_speed_base_max = 0x37,
647 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
648 .bs_fan_speed_base_min = 0x00,
649 .bs_fan_speed_base_max = 0x0f,
652 .rt_temp_address = MSI_EC_ADDR_UNKNOWN,
653 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
656 .micmute_led_address = MSI_EC_ADDR_UNSUPP,
657 .mute_led_address = 0x2c,
661 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
662 .bl_modes = { 0x00, 0x08 }, // ?
664 .bl_state_address = 0xf3,
665 .state_base_value = 0x80,
670 static struct msi_ec_conf *CONFIGS[] __initdata = {
682 static struct msi_ec_conf conf; // current configuration
688 static int ec_read_seq(u8 addr, u8 *buf, u8 len)
692 for (u8 i = 0; i < len; i++) {
693 result = ec_read(addr + i, buf + i);
701 static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
705 memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
706 result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
708 MSI_EC_FW_VERSION_LENGTH);
712 return MSI_EC_FW_VERSION_LENGTH + 1;
716 * Sysfs power_supply subsystem
719 static ssize_t charge_control_threshold_show(u8 offset,
720 struct device *device,
721 struct device_attribute *attr,
727 result = ec_read(conf.charge_control.address, &rdata);
731 return sysfs_emit(buf, "%i\n", rdata - offset);
734 static ssize_t charge_control_threshold_store(u8 offset,
736 struct device_attribute *attr,
737 const char *buf, size_t count)
742 result = kstrtou8(buf, 10, &wdata);
747 if (wdata < conf.charge_control.range_min ||
748 wdata > conf.charge_control.range_max)
751 result = ec_write(conf.charge_control.address, wdata);
758 static ssize_t charge_control_start_threshold_show(struct device *device,
759 struct device_attribute *attr,
762 return charge_control_threshold_show(conf.charge_control.offset_start,
766 static ssize_t charge_control_start_threshold_store(struct device *dev,
767 struct device_attribute *attr,
768 const char *buf, size_t count)
770 return charge_control_threshold_store(conf.charge_control.offset_start,
771 dev, attr, buf, count);
774 static ssize_t charge_control_end_threshold_show(struct device *device,
775 struct device_attribute *attr,
778 return charge_control_threshold_show(conf.charge_control.offset_end,
782 static ssize_t charge_control_end_threshold_store(struct device *dev,
783 struct device_attribute *attr,
784 const char *buf, size_t count)
786 return charge_control_threshold_store(conf.charge_control.offset_end,
787 dev, attr, buf, count);
790 static DEVICE_ATTR_RW(charge_control_start_threshold);
791 static DEVICE_ATTR_RW(charge_control_end_threshold);
793 static struct attribute *msi_battery_attrs[] = {
794 &dev_attr_charge_control_start_threshold.attr,
795 &dev_attr_charge_control_end_threshold.attr,
799 ATTRIBUTE_GROUPS(msi_battery);
801 static int msi_battery_add(struct power_supply *battery,
802 struct acpi_battery_hook *hook)
804 return device_add_groups(&battery->dev, msi_battery_groups);
807 static int msi_battery_remove(struct power_supply *battery,
808 struct acpi_battery_hook *hook)
810 device_remove_groups(&battery->dev, msi_battery_groups);
814 static struct acpi_battery_hook battery_hook = {
815 .add_battery = msi_battery_add,
816 .remove_battery = msi_battery_remove,
817 .name = MSI_EC_DRIVER_NAME,
824 static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
827 DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
832 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
837 MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
839 static int __init load_configuration(void)
843 u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
845 /* get firmware version */
846 result = ec_get_firmware_version(fw_version);
850 /* load the suitable configuration, if exists */
851 for (int i = 0; CONFIGS[i]; i++) {
852 if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
854 conf.allowed_fw = NULL;
859 /* config not found */
861 for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
862 if (!isgraph(fw_version[i])) {
863 pr_warn("Unable to find a valid firmware version!\n");
868 pr_warn("Firmware version is not supported: '%s'\n", fw_version);
872 static int __init msi_ec_init(void)
876 result = load_configuration();
880 battery_hook_register(&battery_hook);
884 static void __exit msi_ec_exit(void)
886 battery_hook_unregister(&battery_hook);
889 MODULE_LICENSE("GPL");
890 MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>");
891 MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>");
892 MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>");
893 MODULE_DESCRIPTION("MSI Embedded Controller");
895 module_init(msi_ec_init);
896 module_exit(msi_ec_exit);