// SPDX-License-Identifier: GPL-2.0-or-later /* * msi-ec: MSI laptops' embedded controller driver. * * This driver allows various MSI laptops' functionalities to be * controlled from userspace. * * It contains EC memory configurations for different firmware versions * and exports battery charge thresholds to userspace. * * Copyright (C) 2023 Jose Angel Pastrana * Copyright (C) 2023 Aakash Singh * Copyright (C) 2023 Nikita Kravets */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include "msi-ec.h" #include #include #include #include #include #include #include #include #define SM_ECO_NAME "eco" #define SM_COMFORT_NAME "comfort" #define SM_SPORT_NAME "sport" #define SM_TURBO_NAME "turbo" #define FM_AUTO_NAME "auto" #define FM_SILENT_NAME "silent" #define FM_BASIC_NAME "basic" #define FM_ADVANCED_NAME "advanced" static const char * const ALLOWED_FW_0[] __initconst = { "14C1EMS1.012", "14C1EMS1.101", "14C1EMS1.102", NULL }; static struct msi_ec_conf CONF0 __initdata = { .allowed_fw = ALLOWED_FW_0, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xbf, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_BASIC_NAME, 0x4d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = 0x89, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2b, .mute_led_address = 0x2c, .bit = 2, }, .kbd_bl = { .bl_mode_address = 0x2c, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = 0xf3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_1[] __initconst = { "17F2EMS1.103", "17F2EMS1.104", "17F2EMS1.106", "17F2EMS1.107", NULL }; static struct msi_ec_conf CONF1 __initdata = { .allowed_fw = ALLOWED_FW_1, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xbf, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNKNOWN, }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_BASIC_NAME, 0x4d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = 0x89, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2b, .mute_led_address = 0x2c, .bit = 2, }, .kbd_bl = { .bl_mode_address = 0x2c, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = 0xf3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_2[] __initconst = { "1552EMS1.118", NULL }; static struct msi_ec_conf CONF2 __initdata = { .allowed_fw = ALLOWED_FW_2, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xeb, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_BASIC_NAME, 0x4d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = 0x89, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2c, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = 0x2c, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_3[] __initconst = { "1592EMS1.111", NULL }; static struct msi_ec_conf CONF3 __initdata = { .allowed_fw = ALLOWED_FW_3, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xeb, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_BASIC_NAME, 0x4d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0xc9, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = 0x89, // ? .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2b, .mute_led_address = 0x2c, .bit = 1, }, .kbd_bl = { .bl_mode_address = 0x2c, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_4[] __initconst = { "16V4EMS1.114", NULL }; static struct msi_ec_conf CONF4 __initdata = { .allowed_fw = ALLOWED_FW_4, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { // may be supported, but address is unknown .address = MSI_EC_ADDR_UNKNOWN, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, // needs testing .rt_fan_speed_address = 0x71, // needs testing .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, }, .leds = { .micmute_led_address = MSI_EC_ADDR_UNKNOWN, .mute_led_address = MSI_EC_ADDR_UNKNOWN, .bit = 1, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_5[] __initconst = { "158LEMS1.103", "158LEMS1.105", "158LEMS1.106", NULL }; static struct msi_ec_conf CONF5 __initdata = { .allowed_fw = ALLOWED_FW_5, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { // todo: reverse .address = 0xbf, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { // unsupported? .address = MSI_EC_ADDR_UNKNOWN, .mask = 0x0f, }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, // needs testing .rt_fan_speed_address = 0x71, // needs testing .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNKNOWN, .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, }, .leds = { .micmute_led_address = 0x2b, .mute_led_address = 0x2c, .bit = 2, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_6[] __initconst = { "1542EMS1.102", "1542EMS1.104", NULL }; static struct msi_ec_conf CONF6 __initdata = { .allowed_fw = ALLOWED_FW_6, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = MSI_EC_ADDR_UNSUPP, .bit = 1, }, .fn_win_swap = { .address = 0xbf, // todo: reverse .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xd5, .mask = 0x0f, }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0xc9, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, }, .leds = { .micmute_led_address = MSI_EC_ADDR_UNSUPP, .mute_led_address = MSI_EC_ADDR_UNSUPP, .bit = 2, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_7[] __initconst = { "17FKEMS1.108", "17FKEMS1.109", "17FKEMS1.10A", NULL }; static struct msi_ec_conf CONF7 __initdata = { .allowed_fw = ALLOWED_FW_7, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = MSI_EC_ADDR_UNSUPP, .bit = 1, }, .fn_win_swap = { .address = 0xbf, // needs testing .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes .mask = 0x0f, }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, // d may not be relevant { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0xc9, // needs testing .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNKNOWN, .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, }, .leds = { .micmute_led_address = MSI_EC_ADDR_UNSUPP, .mute_led_address = 0x2c, .bit = 2, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = 0xf3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_8[] __initconst = { "14F1EMS1.115", NULL }; static struct msi_ec_conf CONF8 __initdata = { .allowed_fw = ALLOWED_FW_8, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = MSI_EC_ADDR_UNSUPP, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xeb, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_BASIC_NAME, 0x4d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNKNOWN, .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, }, .leds = { .micmute_led_address = MSI_EC_ADDR_UNSUPP, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? .bl_modes = { 0x00, 0x08 }, // ? .max_mode = 1, // ? .bl_state_address = MSI_EC_ADDR_UNSUPP, // not functional .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_9[] __initconst = { "14JKEMS1.104", NULL }; static struct msi_ec_conf CONF9 __initdata = { .allowed_fw = ALLOWED_FW_9, .charge_control = { .address = 0xef, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xbf, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xf2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNSUPP, // unsupported or enabled by ECO shift .mask = 0x0f, }, .fan_mode = { .address = 0xf4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x00, .rt_fan_speed_base_max = 0x96, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNSUPP, .rt_fan_speed_address = MSI_EC_ADDR_UNSUPP, }, .leds = { .micmute_led_address = 0x2b, .mute_led_address = 0x2c, .bit = 2, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNSUPP, // not presented in MSI app .bl_modes = { 0x00, 0x08 }, .max_mode = 1, .bl_state_address = 0xf3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_10[] __initconst = { "1582EMS1.107", // GF66 11UC NULL }; static struct msi_ec_conf CONF10 __initdata = { .allowed_fw = ALLOWED_FW_10, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = MSI_EC_ADDR_UNSUPP, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xe5, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, // ? .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN, // ? .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2c, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = 0x2c, .bl_modes = { 0x00, 0x08 }, .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_11[] __initconst = { "16S6EMS1.111", // Prestige 15 a11scx "1552EMS1.115", // Modern 15 a11m NULL }; static struct msi_ec_conf CONF11 __initdata = { .allowed_fw = ALLOWED_FW_11, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = MSI_EC_ADDR_UNKNOWN, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = 0xeb, .mask = 0x0f, }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x4d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNSUPP, .rt_fan_speed_address = MSI_EC_ADDR_UNSUPP, }, .leds = { .micmute_led_address = 0x2c, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, .bl_modes = {}, // ? .max_mode = 1, // ? .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_12[] __initconst = { "16R6EMS1.104", // GF63 Thin 11UC NULL }; static struct msi_ec_conf CONF12 __initdata = { .allowed_fw = ALLOWED_FW_12, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, { SM_COMFORT_NAME, 0xc1 }, { SM_SPORT_NAME, 0xc0 }, { SM_TURBO_NAME, 0xc4 }, MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNSUPP, // 0xeb .mask = 0x0f, // 00, 0f }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, .rt_fan_speed_base_min = 0x19, .rt_fan_speed_base_max = 0x37, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = MSI_EC_ADDR_UNSUPP, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = MSI_EC_ADDR_UNSUPP, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = MSI_EC_ADDR_UNKNOWN, .bl_modes = { 0x00, 0x08 }, .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static const char * const ALLOWED_FW_13[] __initconst = { "1594EMS1.109", // MSI Prestige 16 Studio A13VE NULL }; static struct msi_ec_conf CONF13 __initdata = { .allowed_fw = ALLOWED_FW_13, .charge_control = { .address = 0xd7, .offset_start = 0x8a, .offset_end = 0x80, .range_min = 0x8a, .range_max = 0xe4, }, .webcam = { .address = 0x2e, .block_address = 0x2f, .bit = 1, }, .fn_win_swap = { .address = 0xe8, .bit = 4, // 0x00-0x10 }, .cooler_boost = { .address = 0x98, .bit = 7, }, .shift_mode = { .address = 0xd2, .modes = { { SM_ECO_NAME, 0xc2 }, // super battery { SM_COMFORT_NAME, 0xc1 }, // balanced { SM_TURBO_NAME, 0xc4 }, // extreme MSI_EC_MODE_NULL }, }, .super_battery = { .address = MSI_EC_ADDR_UNSUPP, .mask = 0x0f, // 00, 0f }, .fan_mode = { .address = 0xd4, .modes = { { FM_AUTO_NAME, 0x0d }, { FM_SILENT_NAME, 0x1d }, { FM_ADVANCED_NAME, 0x8d }, MSI_EC_MODE_NULL }, }, .cpu = { .rt_temp_address = 0x68, .rt_fan_speed_address = 0x71, // 0x0-0x96 .rt_fan_speed_base_min = 0x00, .rt_fan_speed_base_max = 0x96, .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, .bs_fan_speed_base_min = 0x00, .bs_fan_speed_base_max = 0x0f, }, .gpu = { .rt_temp_address = 0x80, .rt_fan_speed_address = 0x89, }, .leds = { .micmute_led_address = 0x2c, .mute_led_address = 0x2d, .bit = 1, }, .kbd_bl = { .bl_mode_address = 0x2c, // KB auto turn off .bl_modes = { 0x00, 0x08 }, // always on; off after 10 sec .max_mode = 1, .bl_state_address = 0xd3, .state_base_value = 0x80, .max_state = 3, }, }; static struct msi_ec_conf *CONFIGS[] __initdata = { &CONF0, &CONF1, &CONF2, &CONF3, &CONF4, &CONF5, &CONF6, &CONF7, &CONF8, &CONF9, &CONF10, &CONF11, &CONF12, &CONF13, NULL }; static struct msi_ec_conf conf; // current configuration /* * Helper functions */ static int ec_read_seq(u8 addr, u8 *buf, u8 len) { int result; for (u8 i = 0; i < len; i++) { result = ec_read(addr + i, buf + i); if (result < 0) return result; } return 0; } static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1]) { int result; memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1); result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS, buf, MSI_EC_FW_VERSION_LENGTH); if (result < 0) return result; return MSI_EC_FW_VERSION_LENGTH + 1; } /* * Sysfs power_supply subsystem */ static ssize_t charge_control_threshold_show(u8 offset, struct device *device, struct device_attribute *attr, char *buf) { u8 rdata; int result; result = ec_read(conf.charge_control.address, &rdata); if (result < 0) return result; return sysfs_emit(buf, "%i\n", rdata - offset); } static ssize_t charge_control_threshold_store(u8 offset, struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { u8 wdata; int result; result = kstrtou8(buf, 10, &wdata); if (result < 0) return result; wdata += offset; if (wdata < conf.charge_control.range_min || wdata > conf.charge_control.range_max) return -EINVAL; result = ec_write(conf.charge_control.address, wdata); if (result < 0) return result; return count; } static ssize_t charge_control_start_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { return charge_control_threshold_show(conf.charge_control.offset_start, device, attr, buf); } static ssize_t charge_control_start_threshold_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return charge_control_threshold_store(conf.charge_control.offset_start, dev, attr, buf, count); } static ssize_t charge_control_end_threshold_show(struct device *device, struct device_attribute *attr, char *buf) { return charge_control_threshold_show(conf.charge_control.offset_end, device, attr, buf); } static ssize_t charge_control_end_threshold_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { return charge_control_threshold_store(conf.charge_control.offset_end, dev, attr, buf, count); } static DEVICE_ATTR_RW(charge_control_start_threshold); static DEVICE_ATTR_RW(charge_control_end_threshold); static struct attribute *msi_battery_attrs[] = { &dev_attr_charge_control_start_threshold.attr, &dev_attr_charge_control_end_threshold.attr, NULL }; ATTRIBUTE_GROUPS(msi_battery); static int msi_battery_add(struct power_supply *battery, struct acpi_battery_hook *hook) { return device_add_groups(&battery->dev, msi_battery_groups); } static int msi_battery_remove(struct power_supply *battery, struct acpi_battery_hook *hook) { device_remove_groups(&battery->dev, msi_battery_groups); return 0; } static struct acpi_battery_hook battery_hook = { .add_battery = msi_battery_add, .remove_battery = msi_battery_remove, .name = MSI_EC_DRIVER_NAME, }; /* * Module load/unload */ static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = { { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"), }, }, { .matches = { DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), }, }, {} }; MODULE_DEVICE_TABLE(dmi, msi_dmi_table); static int __init load_configuration(void) { int result; u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1]; /* get firmware version */ result = ec_get_firmware_version(fw_version); if (result < 0) return result; /* load the suitable configuration, if exists */ for (int i = 0; CONFIGS[i]; i++) { if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) { conf = *CONFIGS[i]; conf.allowed_fw = NULL; return 0; } } /* config not found */ for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) { if (!isgraph(fw_version[i])) { pr_warn("Unable to find a valid firmware version!\n"); return -EOPNOTSUPP; } } pr_warn("Firmware version is not supported: '%s'\n", fw_version); return -EOPNOTSUPP; } static int __init msi_ec_init(void) { int result; result = load_configuration(); if (result < 0) return result; battery_hook_register(&battery_hook); return 0; } static void __exit msi_ec_exit(void) { battery_hook_unregister(&battery_hook); } MODULE_LICENSE("GPL"); MODULE_AUTHOR("Jose Angel Pastrana "); MODULE_AUTHOR("Aakash Singh "); MODULE_AUTHOR("Nikita Kravets "); MODULE_DESCRIPTION("MSI Embedded Controller"); module_init(msi_ec_init); module_exit(msi_ec_exit);