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.
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 static const char *const SM_ECO_NAME = "eco";
31 static const char *const SM_COMFORT_NAME = "comfort";
32 static const char *const SM_SPORT_NAME = "sport";
33 static const char *const SM_TURBO_NAME = "turbo";
35 static const char *const FM_AUTO_NAME = "auto";
36 static const char *const FM_SILENT_NAME = "silent";
37 static const char *const FM_BASIC_NAME = "basic";
38 static const char *const 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 = {
283 static struct msi_ec_conf CONF3 __initdata = {
284 .allowed_fw = ALLOWED_FW_3,
287 .offset_start = 0x8a,
294 .block_address = 0x2f,
308 { SM_ECO_NAME, 0xc2 },
309 { SM_COMFORT_NAME, 0xc1 },
310 { SM_SPORT_NAME, 0xc0 },
321 { FM_AUTO_NAME, 0x0d },
322 { FM_SILENT_NAME, 0x1d },
323 { FM_BASIC_NAME, 0x4d },
324 { FM_ADVANCED_NAME, 0x8d },
329 .rt_temp_address = 0x68,
330 .rt_fan_speed_address = 0xc9,
331 .rt_fan_speed_base_min = 0x19,
332 .rt_fan_speed_base_max = 0x37,
333 .bs_fan_speed_address = 0x89, // ?
334 .bs_fan_speed_base_min = 0x00,
335 .bs_fan_speed_base_max = 0x0f,
338 .rt_temp_address = 0x80,
339 .rt_fan_speed_address = 0x89,
342 .micmute_led_address = 0x2b,
343 .mute_led_address = 0x2c,
347 .bl_mode_address = 0x2c, // ?
348 .bl_modes = { 0x00, 0x08 }, // ?
350 .bl_state_address = 0xd3,
351 .state_base_value = 0x80,
356 static const char * const ALLOWED_FW_4[] __initconst = {
361 static struct msi_ec_conf CONF4 __initdata = {
362 .allowed_fw = ALLOWED_FW_4,
365 .offset_start = 0x8a,
372 .block_address = 0x2f,
376 .address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown
386 { SM_ECO_NAME, 0xc2 },
387 { SM_COMFORT_NAME, 0xc1 },
388 { SM_SPORT_NAME, 0xc0 },
392 .super_battery = { // may be supported, but address is unknown
393 .address = MSI_EC_ADDR_UNKNOWN,
399 { FM_AUTO_NAME, 0x0d },
400 { FM_SILENT_NAME, 0x1d },
401 { FM_ADVANCED_NAME, 0x8d },
406 .rt_temp_address = 0x68, // needs testing
407 .rt_fan_speed_address = 0x71, // needs testing
408 .rt_fan_speed_base_min = 0x19,
409 .rt_fan_speed_base_max = 0x37,
410 .bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
411 .bs_fan_speed_base_min = 0x00,
412 .bs_fan_speed_base_max = 0x0f,
415 .rt_temp_address = 0x80,
416 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
419 .micmute_led_address = MSI_EC_ADDR_UNKNOWN,
420 .mute_led_address = MSI_EC_ADDR_UNKNOWN,
424 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
425 .bl_modes = { 0x00, 0x08 }, // ?
427 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional
428 .state_base_value = 0x80,
433 static const char * const ALLOWED_FW_5[] __initconst = {
440 static struct msi_ec_conf CONF5 __initdata = {
441 .allowed_fw = ALLOWED_FW_5,
444 .offset_start = 0x8a,
451 .block_address = 0x2f,
454 .fn_super_swap = { // todo: reverse
465 { SM_ECO_NAME, 0xc2 },
466 { SM_COMFORT_NAME, 0xc1 },
467 { SM_TURBO_NAME, 0xc4 },
471 .super_battery = { // unsupported?
472 .address = MSI_EC_ADDR_UNKNOWN,
478 { FM_AUTO_NAME, 0x0d },
479 { FM_SILENT_NAME, 0x1d },
480 { FM_ADVANCED_NAME, 0x8d },
485 .rt_temp_address = 0x68, // needs testing
486 .rt_fan_speed_address = 0x71, // needs testing
487 .rt_fan_speed_base_min = 0x19,
488 .rt_fan_speed_base_max = 0x37,
489 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
490 .bs_fan_speed_base_min = 0x00,
491 .bs_fan_speed_base_max = 0x0f,
494 .rt_temp_address = MSI_EC_ADDR_UNKNOWN,
495 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
498 .micmute_led_address = 0x2b,
499 .mute_led_address = 0x2c,
503 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
504 .bl_modes = { 0x00, 0x08 }, // ?
506 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
507 .state_base_value = 0x80,
512 static const char * const ALLOWED_FW_6[] __initconst = {
518 static struct msi_ec_conf CONF6 __initdata = {
519 .allowed_fw = ALLOWED_FW_6,
522 .offset_start = 0x8a,
529 .block_address = MSI_EC_ADDR_UNSUPP,
533 .address = 0xbf, // todo: reverse
543 { SM_ECO_NAME, 0xc2 },
544 { SM_COMFORT_NAME, 0xc1 },
545 { SM_SPORT_NAME, 0xc0 },
546 { SM_TURBO_NAME, 0xc4 },
557 { FM_AUTO_NAME, 0x0d },
558 { FM_SILENT_NAME, 0x1d },
559 { FM_ADVANCED_NAME, 0x8d },
564 .rt_temp_address = 0x68,
565 .rt_fan_speed_address = 0xc9,
566 .rt_fan_speed_base_min = 0x19,
567 .rt_fan_speed_base_max = 0x37,
568 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
569 .bs_fan_speed_base_min = 0x00,
570 .bs_fan_speed_base_max = 0x0f,
573 .rt_temp_address = 0x80,
574 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
577 .micmute_led_address = MSI_EC_ADDR_UNSUPP,
578 .mute_led_address = MSI_EC_ADDR_UNSUPP,
582 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
583 .bl_modes = { 0x00, 0x08 }, // ?
585 .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional
586 .state_base_value = 0x80,
591 static const char * const ALLOWED_FW_7[] __initconst = {
598 static struct msi_ec_conf CONF7 __initdata = {
599 .allowed_fw = ALLOWED_FW_7,
602 .offset_start = 0x8a,
609 .block_address = MSI_EC_ADDR_UNSUPP,
613 .address = 0xbf, // needs testing
623 { SM_ECO_NAME, 0xc2 },
624 { SM_COMFORT_NAME, 0xc1 },
625 { SM_SPORT_NAME, 0xc0 },
626 { SM_TURBO_NAME, 0xc4 },
631 .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes
637 { FM_AUTO_NAME, 0x0d }, // d may not be relevant
638 { FM_SILENT_NAME, 0x1d },
639 { FM_ADVANCED_NAME, 0x8d },
644 .rt_temp_address = 0x68,
645 .rt_fan_speed_address = 0xc9, // needs testing
646 .rt_fan_speed_base_min = 0x19,
647 .rt_fan_speed_base_max = 0x37,
648 .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP,
649 .bs_fan_speed_base_min = 0x00,
650 .bs_fan_speed_base_max = 0x0f,
653 .rt_temp_address = MSI_EC_ADDR_UNKNOWN,
654 .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN,
657 .micmute_led_address = MSI_EC_ADDR_UNSUPP,
658 .mute_led_address = 0x2c,
662 .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ?
663 .bl_modes = { 0x00, 0x08 }, // ?
665 .bl_state_address = 0xf3,
666 .state_base_value = 0x80,
671 static struct msi_ec_conf *CONFIGS[] __initdata = {
683 static struct msi_ec_conf conf; // current configuration
689 static int ec_read_seq(u8 addr, u8 *buf, u8 len)
693 for (u8 i = 0; i < len; i++) {
694 result = ec_read(addr + i, buf + i);
702 static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1])
706 memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1);
707 result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS,
709 MSI_EC_FW_VERSION_LENGTH);
713 return MSI_EC_FW_VERSION_LENGTH + 1;
717 * Sysfs power_supply subsystem
720 static ssize_t charge_control_threshold_show(u8 offset,
721 struct device *device,
722 struct device_attribute *attr,
728 result = ec_read(conf.charge_control.address, &rdata);
732 return sysfs_emit(buf, "%i\n", rdata - offset);
735 static ssize_t charge_control_threshold_store(u8 offset,
737 struct device_attribute *attr,
738 const char *buf, size_t count)
743 result = kstrtou8(buf, 10, &wdata);
748 if (wdata < conf.charge_control.range_min ||
749 wdata > conf.charge_control.range_max)
752 result = ec_write(conf.charge_control.address, wdata);
759 static ssize_t charge_control_start_threshold_show(struct device *device,
760 struct device_attribute *attr,
763 return charge_control_threshold_show(conf.charge_control.offset_start,
767 static ssize_t charge_control_start_threshold_store(struct device *dev,
768 struct device_attribute *attr,
769 const char *buf, size_t count)
771 return charge_control_threshold_store(conf.charge_control.offset_start,
772 dev, attr, buf, count);
775 static ssize_t charge_control_end_threshold_show(struct device *device,
776 struct device_attribute *attr,
779 return charge_control_threshold_show(conf.charge_control.offset_end,
783 static ssize_t charge_control_end_threshold_store(struct device *dev,
784 struct device_attribute *attr,
785 const char *buf, size_t count)
787 return charge_control_threshold_store(conf.charge_control.offset_end,
788 dev, attr, buf, count);
791 static DEVICE_ATTR_RW(charge_control_start_threshold);
792 static DEVICE_ATTR_RW(charge_control_end_threshold);
794 static struct attribute *msi_battery_attrs[] = {
795 &dev_attr_charge_control_start_threshold.attr,
796 &dev_attr_charge_control_end_threshold.attr,
800 ATTRIBUTE_GROUPS(msi_battery);
802 static int msi_battery_add(struct power_supply *battery,
803 struct acpi_battery_hook *hook)
805 return device_add_groups(&battery->dev, msi_battery_groups);
808 static int msi_battery_remove(struct power_supply *battery,
809 struct acpi_battery_hook *hook)
811 device_remove_groups(&battery->dev, msi_battery_groups);
815 static struct acpi_battery_hook battery_hook = {
816 .add_battery = msi_battery_add,
817 .remove_battery = msi_battery_remove,
818 .name = MSI_EC_DRIVER_NAME,
825 static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = {
828 DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"),
833 DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
838 MODULE_DEVICE_TABLE(dmi, msi_dmi_table);
840 static int __init load_configuration(void)
844 u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
846 /* get firmware version */
847 result = ec_get_firmware_version(fw_version);
851 /* load the suitable configuration, if exists */
852 for (int i = 0; CONFIGS[i]; i++) {
853 if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) {
855 conf.allowed_fw = NULL;
860 /* config not found */
862 for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) {
863 if (!isgraph(fw_version[i])) {
864 pr_warn("Unable to find a valid firmware version!\n");
869 pr_warn("Firmware version is not supported: '%s'\n", fw_version);
873 static int __init msi_ec_init(void)
877 result = load_configuration();
881 battery_hook_register(&battery_hook);
885 static void __exit msi_ec_exit(void)
887 battery_hook_unregister(&battery_hook);
890 MODULE_LICENSE("GPL");
894 MODULE_DESCRIPTION("MSI Embedded Controller");
896 module_init(msi_ec_init);
897 module_exit(msi_ec_exit);