root/drivers/platform/x86/msi-ec.c
// 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>

#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 <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);