| // 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 <japp0005@red.ujaen.es> |
| * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev> |
| * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com> |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include "msi-ec.h" |
| |
| #include <acpi/battery.h> |
| #include <linux/acpi.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/seq_file.h> |
| #include <linux/string.h> |
| |
| static const char *const SM_ECO_NAME = "eco"; |
| static const char *const SM_COMFORT_NAME = "comfort"; |
| static const char *const SM_SPORT_NAME = "sport"; |
| static const char *const SM_TURBO_NAME = "turbo"; |
| |
| static const char *const FM_AUTO_NAME = "auto"; |
| static const char *const FM_SILENT_NAME = "silent"; |
| static const char *const FM_BASIC_NAME = "basic"; |
| static const char *const 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_super_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_super_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_super_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", |
| "E1592IMS.10C", |
| NULL |
| }; |
| |
| static struct msi_ec_conf CONF3 __initdata = { |
| .allowed_fw = ALLOWED_FW_3, |
| .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_super_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_super_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_super_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_super_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_super_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 struct msi_ec_conf *CONFIGS[] __initdata = { |
| &CONF0, |
| &CONF1, |
| &CONF2, |
| &CONF3, |
| &CONF4, |
| &CONF5, |
| &CONF6, |
| &CONF7, |
| 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 <japp0005@red.ujaen.es>"); |
| MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>"); |
| MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>"); |
| MODULE_DESCRIPTION("MSI Embedded Controller"); |
| |
| module_init(msi_ec_init); |
| module_exit(msi_ec_exit); |