1*392cacf2SNikita Kravets // SPDX-License-Identifier: GPL-2.0-or-later 2*392cacf2SNikita Kravets 3*392cacf2SNikita Kravets /* 4*392cacf2SNikita Kravets * msi-ec: MSI laptops' embedded controller driver. 5*392cacf2SNikita Kravets * 6*392cacf2SNikita Kravets * This driver allows various MSI laptops' functionalities to be 7*392cacf2SNikita Kravets * controlled from userspace. 8*392cacf2SNikita Kravets * 9*392cacf2SNikita Kravets * It contains EC memory configurations for different firmware versions 10*392cacf2SNikita Kravets * and exports battery charge thresholds to userspace. 11*392cacf2SNikita Kravets * 12*392cacf2SNikita Kravets * Copyright (C) 2023 Jose Angel Pastrana <japp0005@red.ujaen.es> 13*392cacf2SNikita Kravets * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev> 14*392cacf2SNikita Kravets * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com> 15*392cacf2SNikita Kravets */ 16*392cacf2SNikita Kravets 17*392cacf2SNikita Kravets #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 18*392cacf2SNikita Kravets 19*392cacf2SNikita Kravets #include "msi-ec.h" 20*392cacf2SNikita Kravets 21*392cacf2SNikita Kravets #include <acpi/battery.h> 22*392cacf2SNikita Kravets #include <linux/acpi.h> 23*392cacf2SNikita Kravets #include <linux/init.h> 24*392cacf2SNikita Kravets #include <linux/kernel.h> 25*392cacf2SNikita Kravets #include <linux/module.h> 26*392cacf2SNikita Kravets #include <linux/platform_device.h> 27*392cacf2SNikita Kravets #include <linux/seq_file.h> 28*392cacf2SNikita Kravets #include <linux/string.h> 29*392cacf2SNikita Kravets 30*392cacf2SNikita Kravets static const char *const SM_ECO_NAME = "eco"; 31*392cacf2SNikita Kravets static const char *const SM_COMFORT_NAME = "comfort"; 32*392cacf2SNikita Kravets static const char *const SM_SPORT_NAME = "sport"; 33*392cacf2SNikita Kravets static const char *const SM_TURBO_NAME = "turbo"; 34*392cacf2SNikita Kravets 35*392cacf2SNikita Kravets static const char *const FM_AUTO_NAME = "auto"; 36*392cacf2SNikita Kravets static const char *const FM_SILENT_NAME = "silent"; 37*392cacf2SNikita Kravets static const char *const FM_BASIC_NAME = "basic"; 38*392cacf2SNikita Kravets static const char *const FM_ADVANCED_NAME = "advanced"; 39*392cacf2SNikita Kravets 40*392cacf2SNikita Kravets static const char * const ALLOWED_FW_0[] __initconst = { 41*392cacf2SNikita Kravets "14C1EMS1.012", 42*392cacf2SNikita Kravets "14C1EMS1.101", 43*392cacf2SNikita Kravets "14C1EMS1.102", 44*392cacf2SNikita Kravets NULL 45*392cacf2SNikita Kravets }; 46*392cacf2SNikita Kravets 47*392cacf2SNikita Kravets static struct msi_ec_conf CONF0 __initdata = { 48*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_0, 49*392cacf2SNikita Kravets .charge_control = { 50*392cacf2SNikita Kravets .address = 0xef, 51*392cacf2SNikita Kravets .offset_start = 0x8a, 52*392cacf2SNikita Kravets .offset_end = 0x80, 53*392cacf2SNikita Kravets .range_min = 0x8a, 54*392cacf2SNikita Kravets .range_max = 0xe4, 55*392cacf2SNikita Kravets }, 56*392cacf2SNikita Kravets .webcam = { 57*392cacf2SNikita Kravets .address = 0x2e, 58*392cacf2SNikita Kravets .block_address = 0x2f, 59*392cacf2SNikita Kravets .bit = 1, 60*392cacf2SNikita Kravets }, 61*392cacf2SNikita Kravets .fn_super_swap = { 62*392cacf2SNikita Kravets .address = 0xbf, 63*392cacf2SNikita Kravets .bit = 4, 64*392cacf2SNikita Kravets }, 65*392cacf2SNikita Kravets .cooler_boost = { 66*392cacf2SNikita Kravets .address = 0x98, 67*392cacf2SNikita Kravets .bit = 7, 68*392cacf2SNikita Kravets }, 69*392cacf2SNikita Kravets .shift_mode = { 70*392cacf2SNikita Kravets .address = 0xf2, 71*392cacf2SNikita Kravets .modes = { 72*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 73*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 74*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 75*392cacf2SNikita Kravets MSI_EC_MODE_NULL 76*392cacf2SNikita Kravets }, 77*392cacf2SNikita Kravets }, 78*392cacf2SNikita Kravets .super_battery = { 79*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 needs testing 80*392cacf2SNikita Kravets }, 81*392cacf2SNikita Kravets .fan_mode = { 82*392cacf2SNikita Kravets .address = 0xf4, 83*392cacf2SNikita Kravets .modes = { 84*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 85*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 86*392cacf2SNikita Kravets { FM_BASIC_NAME, 0x4d }, 87*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 88*392cacf2SNikita Kravets MSI_EC_MODE_NULL 89*392cacf2SNikita Kravets }, 90*392cacf2SNikita Kravets }, 91*392cacf2SNikita Kravets .cpu = { 92*392cacf2SNikita Kravets .rt_temp_address = 0x68, 93*392cacf2SNikita Kravets .rt_fan_speed_address = 0x71, 94*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 95*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 96*392cacf2SNikita Kravets .bs_fan_speed_address = 0x89, 97*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 98*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 99*392cacf2SNikita Kravets }, 100*392cacf2SNikita Kravets .gpu = { 101*392cacf2SNikita Kravets .rt_temp_address = 0x80, 102*392cacf2SNikita Kravets .rt_fan_speed_address = 0x89, 103*392cacf2SNikita Kravets }, 104*392cacf2SNikita Kravets .leds = { 105*392cacf2SNikita Kravets .micmute_led_address = 0x2b, 106*392cacf2SNikita Kravets .mute_led_address = 0x2c, 107*392cacf2SNikita Kravets .bit = 2, 108*392cacf2SNikita Kravets }, 109*392cacf2SNikita Kravets .kbd_bl = { 110*392cacf2SNikita Kravets .bl_mode_address = 0x2c, // ? 111*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 112*392cacf2SNikita Kravets .max_mode = 1, // ? 113*392cacf2SNikita Kravets .bl_state_address = 0xf3, 114*392cacf2SNikita Kravets .state_base_value = 0x80, 115*392cacf2SNikita Kravets .max_state = 3, 116*392cacf2SNikita Kravets }, 117*392cacf2SNikita Kravets }; 118*392cacf2SNikita Kravets 119*392cacf2SNikita Kravets static const char * const ALLOWED_FW_1[] __initconst = { 120*392cacf2SNikita Kravets "17F2EMS1.103", 121*392cacf2SNikita Kravets "17F2EMS1.104", 122*392cacf2SNikita Kravets "17F2EMS1.106", 123*392cacf2SNikita Kravets "17F2EMS1.107", 124*392cacf2SNikita Kravets NULL 125*392cacf2SNikita Kravets }; 126*392cacf2SNikita Kravets 127*392cacf2SNikita Kravets static struct msi_ec_conf CONF1 __initdata = { 128*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_1, 129*392cacf2SNikita Kravets .charge_control = { 130*392cacf2SNikita Kravets .address = 0xef, 131*392cacf2SNikita Kravets .offset_start = 0x8a, 132*392cacf2SNikita Kravets .offset_end = 0x80, 133*392cacf2SNikita Kravets .range_min = 0x8a, 134*392cacf2SNikita Kravets .range_max = 0xe4, 135*392cacf2SNikita Kravets }, 136*392cacf2SNikita Kravets .webcam = { 137*392cacf2SNikita Kravets .address = 0x2e, 138*392cacf2SNikita Kravets .block_address = 0x2f, 139*392cacf2SNikita Kravets .bit = 1, 140*392cacf2SNikita Kravets }, 141*392cacf2SNikita Kravets .fn_super_swap = { 142*392cacf2SNikita Kravets .address = 0xbf, 143*392cacf2SNikita Kravets .bit = 4, 144*392cacf2SNikita Kravets }, 145*392cacf2SNikita Kravets .cooler_boost = { 146*392cacf2SNikita Kravets .address = 0x98, 147*392cacf2SNikita Kravets .bit = 7, 148*392cacf2SNikita Kravets }, 149*392cacf2SNikita Kravets .shift_mode = { 150*392cacf2SNikita Kravets .address = 0xf2, 151*392cacf2SNikita Kravets .modes = { 152*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 153*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 154*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 155*392cacf2SNikita Kravets { SM_TURBO_NAME, 0xc4 }, 156*392cacf2SNikita Kravets MSI_EC_MODE_NULL 157*392cacf2SNikita Kravets }, 158*392cacf2SNikita Kravets }, 159*392cacf2SNikita Kravets .super_battery = { 160*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, 161*392cacf2SNikita Kravets }, 162*392cacf2SNikita Kravets .fan_mode = { 163*392cacf2SNikita Kravets .address = 0xf4, 164*392cacf2SNikita Kravets .modes = { 165*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 166*392cacf2SNikita Kravets { FM_BASIC_NAME, 0x4d }, 167*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 168*392cacf2SNikita Kravets MSI_EC_MODE_NULL 169*392cacf2SNikita Kravets }, 170*392cacf2SNikita Kravets }, 171*392cacf2SNikita Kravets .cpu = { 172*392cacf2SNikita Kravets .rt_temp_address = 0x68, 173*392cacf2SNikita Kravets .rt_fan_speed_address = 0x71, 174*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 175*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 176*392cacf2SNikita Kravets .bs_fan_speed_address = 0x89, 177*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 178*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 179*392cacf2SNikita Kravets }, 180*392cacf2SNikita Kravets .gpu = { 181*392cacf2SNikita Kravets .rt_temp_address = 0x80, 182*392cacf2SNikita Kravets .rt_fan_speed_address = 0x89, 183*392cacf2SNikita Kravets }, 184*392cacf2SNikita Kravets .leds = { 185*392cacf2SNikita Kravets .micmute_led_address = 0x2b, 186*392cacf2SNikita Kravets .mute_led_address = 0x2c, 187*392cacf2SNikita Kravets .bit = 2, 188*392cacf2SNikita Kravets }, 189*392cacf2SNikita Kravets .kbd_bl = { 190*392cacf2SNikita Kravets .bl_mode_address = 0x2c, // ? 191*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 192*392cacf2SNikita Kravets .max_mode = 1, // ? 193*392cacf2SNikita Kravets .bl_state_address = 0xf3, 194*392cacf2SNikita Kravets .state_base_value = 0x80, 195*392cacf2SNikita Kravets .max_state = 3, 196*392cacf2SNikita Kravets }, 197*392cacf2SNikita Kravets }; 198*392cacf2SNikita Kravets 199*392cacf2SNikita Kravets static const char * const ALLOWED_FW_2[] __initconst = { 200*392cacf2SNikita Kravets "1552EMS1.118", 201*392cacf2SNikita Kravets NULL 202*392cacf2SNikita Kravets }; 203*392cacf2SNikita Kravets 204*392cacf2SNikita Kravets static struct msi_ec_conf CONF2 __initdata = { 205*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_2, 206*392cacf2SNikita Kravets .charge_control = { 207*392cacf2SNikita Kravets .address = 0xd7, 208*392cacf2SNikita Kravets .offset_start = 0x8a, 209*392cacf2SNikita Kravets .offset_end = 0x80, 210*392cacf2SNikita Kravets .range_min = 0x8a, 211*392cacf2SNikita Kravets .range_max = 0xe4, 212*392cacf2SNikita Kravets }, 213*392cacf2SNikita Kravets .webcam = { 214*392cacf2SNikita Kravets .address = 0x2e, 215*392cacf2SNikita Kravets .block_address = 0x2f, 216*392cacf2SNikita Kravets .bit = 1, 217*392cacf2SNikita Kravets }, 218*392cacf2SNikita Kravets .fn_super_swap = { 219*392cacf2SNikita Kravets .address = 0xe8, 220*392cacf2SNikita Kravets .bit = 4, 221*392cacf2SNikita Kravets }, 222*392cacf2SNikita Kravets .cooler_boost = { 223*392cacf2SNikita Kravets .address = 0x98, 224*392cacf2SNikita Kravets .bit = 7, 225*392cacf2SNikita Kravets }, 226*392cacf2SNikita Kravets .shift_mode = { 227*392cacf2SNikita Kravets .address = 0xf2, 228*392cacf2SNikita Kravets .modes = { 229*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 230*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 231*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 232*392cacf2SNikita Kravets MSI_EC_MODE_NULL 233*392cacf2SNikita Kravets }, 234*392cacf2SNikita Kravets }, 235*392cacf2SNikita Kravets .super_battery = { 236*392cacf2SNikita Kravets .address = 0xeb, 237*392cacf2SNikita Kravets .mask = 0x0f, 238*392cacf2SNikita Kravets }, 239*392cacf2SNikita Kravets .fan_mode = { 240*392cacf2SNikita Kravets .address = 0xd4, 241*392cacf2SNikita Kravets .modes = { 242*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 243*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 244*392cacf2SNikita Kravets { FM_BASIC_NAME, 0x4d }, 245*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 246*392cacf2SNikita Kravets MSI_EC_MODE_NULL 247*392cacf2SNikita Kravets }, 248*392cacf2SNikita Kravets }, 249*392cacf2SNikita Kravets .cpu = { 250*392cacf2SNikita Kravets .rt_temp_address = 0x68, 251*392cacf2SNikita Kravets .rt_fan_speed_address = 0x71, 252*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 253*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 254*392cacf2SNikita Kravets .bs_fan_speed_address = 0x89, 255*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 256*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 257*392cacf2SNikita Kravets }, 258*392cacf2SNikita Kravets .gpu = { 259*392cacf2SNikita Kravets .rt_temp_address = 0x80, 260*392cacf2SNikita Kravets .rt_fan_speed_address = 0x89, 261*392cacf2SNikita Kravets }, 262*392cacf2SNikita Kravets .leds = { 263*392cacf2SNikita Kravets .micmute_led_address = 0x2c, 264*392cacf2SNikita Kravets .mute_led_address = 0x2d, 265*392cacf2SNikita Kravets .bit = 1, 266*392cacf2SNikita Kravets }, 267*392cacf2SNikita Kravets .kbd_bl = { 268*392cacf2SNikita Kravets .bl_mode_address = 0x2c, // ? 269*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 270*392cacf2SNikita Kravets .max_mode = 1, // ? 271*392cacf2SNikita Kravets .bl_state_address = 0xd3, 272*392cacf2SNikita Kravets .state_base_value = 0x80, 273*392cacf2SNikita Kravets .max_state = 3, 274*392cacf2SNikita Kravets }, 275*392cacf2SNikita Kravets }; 276*392cacf2SNikita Kravets 277*392cacf2SNikita Kravets static const char * const ALLOWED_FW_3[] __initconst = { 278*392cacf2SNikita Kravets "1592EMS1.111", 279*392cacf2SNikita Kravets "E1592IMS.10C", 280*392cacf2SNikita Kravets NULL 281*392cacf2SNikita Kravets }; 282*392cacf2SNikita Kravets 283*392cacf2SNikita Kravets static struct msi_ec_conf CONF3 __initdata = { 284*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_3, 285*392cacf2SNikita Kravets .charge_control = { 286*392cacf2SNikita Kravets .address = 0xef, 287*392cacf2SNikita Kravets .offset_start = 0x8a, 288*392cacf2SNikita Kravets .offset_end = 0x80, 289*392cacf2SNikita Kravets .range_min = 0x8a, 290*392cacf2SNikita Kravets .range_max = 0xe4, 291*392cacf2SNikita Kravets }, 292*392cacf2SNikita Kravets .webcam = { 293*392cacf2SNikita Kravets .address = 0x2e, 294*392cacf2SNikita Kravets .block_address = 0x2f, 295*392cacf2SNikita Kravets .bit = 1, 296*392cacf2SNikita Kravets }, 297*392cacf2SNikita Kravets .fn_super_swap = { 298*392cacf2SNikita Kravets .address = 0xe8, 299*392cacf2SNikita Kravets .bit = 4, 300*392cacf2SNikita Kravets }, 301*392cacf2SNikita Kravets .cooler_boost = { 302*392cacf2SNikita Kravets .address = 0x98, 303*392cacf2SNikita Kravets .bit = 7, 304*392cacf2SNikita Kravets }, 305*392cacf2SNikita Kravets .shift_mode = { 306*392cacf2SNikita Kravets .address = 0xd2, 307*392cacf2SNikita Kravets .modes = { 308*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 309*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 310*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 311*392cacf2SNikita Kravets MSI_EC_MODE_NULL 312*392cacf2SNikita Kravets }, 313*392cacf2SNikita Kravets }, 314*392cacf2SNikita Kravets .super_battery = { 315*392cacf2SNikita Kravets .address = 0xeb, 316*392cacf2SNikita Kravets .mask = 0x0f, 317*392cacf2SNikita Kravets }, 318*392cacf2SNikita Kravets .fan_mode = { 319*392cacf2SNikita Kravets .address = 0xd4, 320*392cacf2SNikita Kravets .modes = { 321*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 322*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 323*392cacf2SNikita Kravets { FM_BASIC_NAME, 0x4d }, 324*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 325*392cacf2SNikita Kravets MSI_EC_MODE_NULL 326*392cacf2SNikita Kravets }, 327*392cacf2SNikita Kravets }, 328*392cacf2SNikita Kravets .cpu = { 329*392cacf2SNikita Kravets .rt_temp_address = 0x68, 330*392cacf2SNikita Kravets .rt_fan_speed_address = 0xc9, 331*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 332*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 333*392cacf2SNikita Kravets .bs_fan_speed_address = 0x89, // ? 334*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 335*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 336*392cacf2SNikita Kravets }, 337*392cacf2SNikita Kravets .gpu = { 338*392cacf2SNikita Kravets .rt_temp_address = 0x80, 339*392cacf2SNikita Kravets .rt_fan_speed_address = 0x89, 340*392cacf2SNikita Kravets }, 341*392cacf2SNikita Kravets .leds = { 342*392cacf2SNikita Kravets .micmute_led_address = 0x2b, 343*392cacf2SNikita Kravets .mute_led_address = 0x2c, 344*392cacf2SNikita Kravets .bit = 1, 345*392cacf2SNikita Kravets }, 346*392cacf2SNikita Kravets .kbd_bl = { 347*392cacf2SNikita Kravets .bl_mode_address = 0x2c, // ? 348*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 349*392cacf2SNikita Kravets .max_mode = 1, // ? 350*392cacf2SNikita Kravets .bl_state_address = 0xd3, 351*392cacf2SNikita Kravets .state_base_value = 0x80, 352*392cacf2SNikita Kravets .max_state = 3, 353*392cacf2SNikita Kravets }, 354*392cacf2SNikita Kravets }; 355*392cacf2SNikita Kravets 356*392cacf2SNikita Kravets static const char * const ALLOWED_FW_4[] __initconst = { 357*392cacf2SNikita Kravets "16V4EMS1.114", 358*392cacf2SNikita Kravets NULL 359*392cacf2SNikita Kravets }; 360*392cacf2SNikita Kravets 361*392cacf2SNikita Kravets static struct msi_ec_conf CONF4 __initdata = { 362*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_4, 363*392cacf2SNikita Kravets .charge_control = { 364*392cacf2SNikita Kravets .address = 0xd7, 365*392cacf2SNikita Kravets .offset_start = 0x8a, 366*392cacf2SNikita Kravets .offset_end = 0x80, 367*392cacf2SNikita Kravets .range_min = 0x8a, 368*392cacf2SNikita Kravets .range_max = 0xe4, 369*392cacf2SNikita Kravets }, 370*392cacf2SNikita Kravets .webcam = { 371*392cacf2SNikita Kravets .address = 0x2e, 372*392cacf2SNikita Kravets .block_address = 0x2f, 373*392cacf2SNikita Kravets .bit = 1, 374*392cacf2SNikita Kravets }, 375*392cacf2SNikita Kravets .fn_super_swap = { 376*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, // supported, but unknown 377*392cacf2SNikita Kravets .bit = 4, 378*392cacf2SNikita Kravets }, 379*392cacf2SNikita Kravets .cooler_boost = { 380*392cacf2SNikita Kravets .address = 0x98, 381*392cacf2SNikita Kravets .bit = 7, 382*392cacf2SNikita Kravets }, 383*392cacf2SNikita Kravets .shift_mode = { 384*392cacf2SNikita Kravets .address = 0xd2, 385*392cacf2SNikita Kravets .modes = { 386*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 387*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 388*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 389*392cacf2SNikita Kravets MSI_EC_MODE_NULL 390*392cacf2SNikita Kravets }, 391*392cacf2SNikita Kravets }, 392*392cacf2SNikita Kravets .super_battery = { // may be supported, but address is unknown 393*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, 394*392cacf2SNikita Kravets .mask = 0x0f, 395*392cacf2SNikita Kravets }, 396*392cacf2SNikita Kravets .fan_mode = { 397*392cacf2SNikita Kravets .address = 0xd4, 398*392cacf2SNikita Kravets .modes = { 399*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 400*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 401*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 402*392cacf2SNikita Kravets MSI_EC_MODE_NULL 403*392cacf2SNikita Kravets }, 404*392cacf2SNikita Kravets }, 405*392cacf2SNikita Kravets .cpu = { 406*392cacf2SNikita Kravets .rt_temp_address = 0x68, // needs testing 407*392cacf2SNikita Kravets .rt_fan_speed_address = 0x71, // needs testing 408*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 409*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 410*392cacf2SNikita Kravets .bs_fan_speed_address = MSI_EC_ADDR_UNKNOWN, 411*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 412*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 413*392cacf2SNikita Kravets }, 414*392cacf2SNikita Kravets .gpu = { 415*392cacf2SNikita Kravets .rt_temp_address = 0x80, 416*392cacf2SNikita Kravets .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, 417*392cacf2SNikita Kravets }, 418*392cacf2SNikita Kravets .leds = { 419*392cacf2SNikita Kravets .micmute_led_address = MSI_EC_ADDR_UNKNOWN, 420*392cacf2SNikita Kravets .mute_led_address = MSI_EC_ADDR_UNKNOWN, 421*392cacf2SNikita Kravets .bit = 1, 422*392cacf2SNikita Kravets }, 423*392cacf2SNikita Kravets .kbd_bl = { 424*392cacf2SNikita Kravets .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? 425*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 426*392cacf2SNikita Kravets .max_mode = 1, // ? 427*392cacf2SNikita Kravets .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xd3, not functional 428*392cacf2SNikita Kravets .state_base_value = 0x80, 429*392cacf2SNikita Kravets .max_state = 3, 430*392cacf2SNikita Kravets }, 431*392cacf2SNikita Kravets }; 432*392cacf2SNikita Kravets 433*392cacf2SNikita Kravets static const char * const ALLOWED_FW_5[] __initconst = { 434*392cacf2SNikita Kravets "158LEMS1.103", 435*392cacf2SNikita Kravets "158LEMS1.105", 436*392cacf2SNikita Kravets "158LEMS1.106", 437*392cacf2SNikita Kravets NULL 438*392cacf2SNikita Kravets }; 439*392cacf2SNikita Kravets 440*392cacf2SNikita Kravets static struct msi_ec_conf CONF5 __initdata = { 441*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_5, 442*392cacf2SNikita Kravets .charge_control = { 443*392cacf2SNikita Kravets .address = 0xef, 444*392cacf2SNikita Kravets .offset_start = 0x8a, 445*392cacf2SNikita Kravets .offset_end = 0x80, 446*392cacf2SNikita Kravets .range_min = 0x8a, 447*392cacf2SNikita Kravets .range_max = 0xe4, 448*392cacf2SNikita Kravets }, 449*392cacf2SNikita Kravets .webcam = { 450*392cacf2SNikita Kravets .address = 0x2e, 451*392cacf2SNikita Kravets .block_address = 0x2f, 452*392cacf2SNikita Kravets .bit = 1, 453*392cacf2SNikita Kravets }, 454*392cacf2SNikita Kravets .fn_super_swap = { // todo: reverse 455*392cacf2SNikita Kravets .address = 0xbf, 456*392cacf2SNikita Kravets .bit = 4, 457*392cacf2SNikita Kravets }, 458*392cacf2SNikita Kravets .cooler_boost = { 459*392cacf2SNikita Kravets .address = 0x98, 460*392cacf2SNikita Kravets .bit = 7, 461*392cacf2SNikita Kravets }, 462*392cacf2SNikita Kravets .shift_mode = { 463*392cacf2SNikita Kravets .address = 0xf2, 464*392cacf2SNikita Kravets .modes = { 465*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 466*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 467*392cacf2SNikita Kravets { SM_TURBO_NAME, 0xc4 }, 468*392cacf2SNikita Kravets MSI_EC_MODE_NULL 469*392cacf2SNikita Kravets }, 470*392cacf2SNikita Kravets }, 471*392cacf2SNikita Kravets .super_battery = { // unsupported? 472*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, 473*392cacf2SNikita Kravets .mask = 0x0f, 474*392cacf2SNikita Kravets }, 475*392cacf2SNikita Kravets .fan_mode = { 476*392cacf2SNikita Kravets .address = 0xf4, 477*392cacf2SNikita Kravets .modes = { 478*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 479*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 480*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 481*392cacf2SNikita Kravets MSI_EC_MODE_NULL 482*392cacf2SNikita Kravets }, 483*392cacf2SNikita Kravets }, 484*392cacf2SNikita Kravets .cpu = { 485*392cacf2SNikita Kravets .rt_temp_address = 0x68, // needs testing 486*392cacf2SNikita Kravets .rt_fan_speed_address = 0x71, // needs testing 487*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 488*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 489*392cacf2SNikita Kravets .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, 490*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 491*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 492*392cacf2SNikita Kravets }, 493*392cacf2SNikita Kravets .gpu = { 494*392cacf2SNikita Kravets .rt_temp_address = MSI_EC_ADDR_UNKNOWN, 495*392cacf2SNikita Kravets .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, 496*392cacf2SNikita Kravets }, 497*392cacf2SNikita Kravets .leds = { 498*392cacf2SNikita Kravets .micmute_led_address = 0x2b, 499*392cacf2SNikita Kravets .mute_led_address = 0x2c, 500*392cacf2SNikita Kravets .bit = 2, 501*392cacf2SNikita Kravets }, 502*392cacf2SNikita Kravets .kbd_bl = { 503*392cacf2SNikita Kravets .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? 504*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 505*392cacf2SNikita Kravets .max_mode = 1, // ? 506*392cacf2SNikita Kravets .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional 507*392cacf2SNikita Kravets .state_base_value = 0x80, 508*392cacf2SNikita Kravets .max_state = 3, 509*392cacf2SNikita Kravets }, 510*392cacf2SNikita Kravets }; 511*392cacf2SNikita Kravets 512*392cacf2SNikita Kravets static const char * const ALLOWED_FW_6[] __initconst = { 513*392cacf2SNikita Kravets "1542EMS1.102", 514*392cacf2SNikita Kravets "1542EMS1.104", 515*392cacf2SNikita Kravets NULL 516*392cacf2SNikita Kravets }; 517*392cacf2SNikita Kravets 518*392cacf2SNikita Kravets static struct msi_ec_conf CONF6 __initdata = { 519*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_6, 520*392cacf2SNikita Kravets .charge_control = { 521*392cacf2SNikita Kravets .address = 0xef, 522*392cacf2SNikita Kravets .offset_start = 0x8a, 523*392cacf2SNikita Kravets .offset_end = 0x80, 524*392cacf2SNikita Kravets .range_min = 0x8a, 525*392cacf2SNikita Kravets .range_max = 0xe4, 526*392cacf2SNikita Kravets }, 527*392cacf2SNikita Kravets .webcam = { 528*392cacf2SNikita Kravets .address = 0x2e, 529*392cacf2SNikita Kravets .block_address = MSI_EC_ADDR_UNSUPP, 530*392cacf2SNikita Kravets .bit = 1, 531*392cacf2SNikita Kravets }, 532*392cacf2SNikita Kravets .fn_super_swap = { 533*392cacf2SNikita Kravets .address = 0xbf, // todo: reverse 534*392cacf2SNikita Kravets .bit = 4, 535*392cacf2SNikita Kravets }, 536*392cacf2SNikita Kravets .cooler_boost = { 537*392cacf2SNikita Kravets .address = 0x98, 538*392cacf2SNikita Kravets .bit = 7, 539*392cacf2SNikita Kravets }, 540*392cacf2SNikita Kravets .shift_mode = { 541*392cacf2SNikita Kravets .address = 0xf2, 542*392cacf2SNikita Kravets .modes = { 543*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 544*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 545*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 546*392cacf2SNikita Kravets { SM_TURBO_NAME, 0xc4 }, 547*392cacf2SNikita Kravets MSI_EC_MODE_NULL 548*392cacf2SNikita Kravets }, 549*392cacf2SNikita Kravets }, 550*392cacf2SNikita Kravets .super_battery = { 551*392cacf2SNikita Kravets .address = 0xd5, 552*392cacf2SNikita Kravets .mask = 0x0f, 553*392cacf2SNikita Kravets }, 554*392cacf2SNikita Kravets .fan_mode = { 555*392cacf2SNikita Kravets .address = 0xf4, 556*392cacf2SNikita Kravets .modes = { 557*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, 558*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 559*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 560*392cacf2SNikita Kravets MSI_EC_MODE_NULL 561*392cacf2SNikita Kravets }, 562*392cacf2SNikita Kravets }, 563*392cacf2SNikita Kravets .cpu = { 564*392cacf2SNikita Kravets .rt_temp_address = 0x68, 565*392cacf2SNikita Kravets .rt_fan_speed_address = 0xc9, 566*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 567*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 568*392cacf2SNikita Kravets .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, 569*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 570*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 571*392cacf2SNikita Kravets }, 572*392cacf2SNikita Kravets .gpu = { 573*392cacf2SNikita Kravets .rt_temp_address = 0x80, 574*392cacf2SNikita Kravets .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, 575*392cacf2SNikita Kravets }, 576*392cacf2SNikita Kravets .leds = { 577*392cacf2SNikita Kravets .micmute_led_address = MSI_EC_ADDR_UNSUPP, 578*392cacf2SNikita Kravets .mute_led_address = MSI_EC_ADDR_UNSUPP, 579*392cacf2SNikita Kravets .bit = 2, 580*392cacf2SNikita Kravets }, 581*392cacf2SNikita Kravets .kbd_bl = { 582*392cacf2SNikita Kravets .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? 583*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 584*392cacf2SNikita Kravets .max_mode = 1, // ? 585*392cacf2SNikita Kravets .bl_state_address = MSI_EC_ADDR_UNSUPP, // 0xf3, not functional 586*392cacf2SNikita Kravets .state_base_value = 0x80, 587*392cacf2SNikita Kravets .max_state = 3, 588*392cacf2SNikita Kravets }, 589*392cacf2SNikita Kravets }; 590*392cacf2SNikita Kravets 591*392cacf2SNikita Kravets static const char * const ALLOWED_FW_7[] __initconst = { 592*392cacf2SNikita Kravets "17FKEMS1.108", 593*392cacf2SNikita Kravets "17FKEMS1.109", 594*392cacf2SNikita Kravets "17FKEMS1.10A", 595*392cacf2SNikita Kravets NULL 596*392cacf2SNikita Kravets }; 597*392cacf2SNikita Kravets 598*392cacf2SNikita Kravets static struct msi_ec_conf CONF7 __initdata = { 599*392cacf2SNikita Kravets .allowed_fw = ALLOWED_FW_7, 600*392cacf2SNikita Kravets .charge_control = { 601*392cacf2SNikita Kravets .address = 0xef, 602*392cacf2SNikita Kravets .offset_start = 0x8a, 603*392cacf2SNikita Kravets .offset_end = 0x80, 604*392cacf2SNikita Kravets .range_min = 0x8a, 605*392cacf2SNikita Kravets .range_max = 0xe4, 606*392cacf2SNikita Kravets }, 607*392cacf2SNikita Kravets .webcam = { 608*392cacf2SNikita Kravets .address = 0x2e, 609*392cacf2SNikita Kravets .block_address = MSI_EC_ADDR_UNSUPP, 610*392cacf2SNikita Kravets .bit = 1, 611*392cacf2SNikita Kravets }, 612*392cacf2SNikita Kravets .fn_super_swap = { 613*392cacf2SNikita Kravets .address = 0xbf, // needs testing 614*392cacf2SNikita Kravets .bit = 4, 615*392cacf2SNikita Kravets }, 616*392cacf2SNikita Kravets .cooler_boost = { 617*392cacf2SNikita Kravets .address = 0x98, 618*392cacf2SNikita Kravets .bit = 7, 619*392cacf2SNikita Kravets }, 620*392cacf2SNikita Kravets .shift_mode = { 621*392cacf2SNikita Kravets .address = 0xf2, 622*392cacf2SNikita Kravets .modes = { 623*392cacf2SNikita Kravets { SM_ECO_NAME, 0xc2 }, 624*392cacf2SNikita Kravets { SM_COMFORT_NAME, 0xc1 }, 625*392cacf2SNikita Kravets { SM_SPORT_NAME, 0xc0 }, 626*392cacf2SNikita Kravets { SM_TURBO_NAME, 0xc4 }, 627*392cacf2SNikita Kravets MSI_EC_MODE_NULL 628*392cacf2SNikita Kravets }, 629*392cacf2SNikita Kravets }, 630*392cacf2SNikita Kravets .super_battery = { 631*392cacf2SNikita Kravets .address = MSI_EC_ADDR_UNKNOWN, // 0xd5 but has its own wet of modes 632*392cacf2SNikita Kravets .mask = 0x0f, 633*392cacf2SNikita Kravets }, 634*392cacf2SNikita Kravets .fan_mode = { 635*392cacf2SNikita Kravets .address = 0xf4, 636*392cacf2SNikita Kravets .modes = { 637*392cacf2SNikita Kravets { FM_AUTO_NAME, 0x0d }, // d may not be relevant 638*392cacf2SNikita Kravets { FM_SILENT_NAME, 0x1d }, 639*392cacf2SNikita Kravets { FM_ADVANCED_NAME, 0x8d }, 640*392cacf2SNikita Kravets MSI_EC_MODE_NULL 641*392cacf2SNikita Kravets }, 642*392cacf2SNikita Kravets }, 643*392cacf2SNikita Kravets .cpu = { 644*392cacf2SNikita Kravets .rt_temp_address = 0x68, 645*392cacf2SNikita Kravets .rt_fan_speed_address = 0xc9, // needs testing 646*392cacf2SNikita Kravets .rt_fan_speed_base_min = 0x19, 647*392cacf2SNikita Kravets .rt_fan_speed_base_max = 0x37, 648*392cacf2SNikita Kravets .bs_fan_speed_address = MSI_EC_ADDR_UNSUPP, 649*392cacf2SNikita Kravets .bs_fan_speed_base_min = 0x00, 650*392cacf2SNikita Kravets .bs_fan_speed_base_max = 0x0f, 651*392cacf2SNikita Kravets }, 652*392cacf2SNikita Kravets .gpu = { 653*392cacf2SNikita Kravets .rt_temp_address = MSI_EC_ADDR_UNKNOWN, 654*392cacf2SNikita Kravets .rt_fan_speed_address = MSI_EC_ADDR_UNKNOWN, 655*392cacf2SNikita Kravets }, 656*392cacf2SNikita Kravets .leds = { 657*392cacf2SNikita Kravets .micmute_led_address = MSI_EC_ADDR_UNSUPP, 658*392cacf2SNikita Kravets .mute_led_address = 0x2c, 659*392cacf2SNikita Kravets .bit = 2, 660*392cacf2SNikita Kravets }, 661*392cacf2SNikita Kravets .kbd_bl = { 662*392cacf2SNikita Kravets .bl_mode_address = MSI_EC_ADDR_UNKNOWN, // ? 663*392cacf2SNikita Kravets .bl_modes = { 0x00, 0x08 }, // ? 664*392cacf2SNikita Kravets .max_mode = 1, // ? 665*392cacf2SNikita Kravets .bl_state_address = 0xf3, 666*392cacf2SNikita Kravets .state_base_value = 0x80, 667*392cacf2SNikita Kravets .max_state = 3, 668*392cacf2SNikita Kravets }, 669*392cacf2SNikita Kravets }; 670*392cacf2SNikita Kravets 671*392cacf2SNikita Kravets static struct msi_ec_conf *CONFIGS[] __initdata = { 672*392cacf2SNikita Kravets &CONF0, 673*392cacf2SNikita Kravets &CONF1, 674*392cacf2SNikita Kravets &CONF2, 675*392cacf2SNikita Kravets &CONF3, 676*392cacf2SNikita Kravets &CONF4, 677*392cacf2SNikita Kravets &CONF5, 678*392cacf2SNikita Kravets &CONF6, 679*392cacf2SNikita Kravets &CONF7, 680*392cacf2SNikita Kravets NULL 681*392cacf2SNikita Kravets }; 682*392cacf2SNikita Kravets 683*392cacf2SNikita Kravets static struct msi_ec_conf conf; // current configuration 684*392cacf2SNikita Kravets 685*392cacf2SNikita Kravets /* 686*392cacf2SNikita Kravets * Helper functions 687*392cacf2SNikita Kravets */ 688*392cacf2SNikita Kravets 689*392cacf2SNikita Kravets static int ec_read_seq(u8 addr, u8 *buf, u8 len) 690*392cacf2SNikita Kravets { 691*392cacf2SNikita Kravets int result; 692*392cacf2SNikita Kravets 693*392cacf2SNikita Kravets for (u8 i = 0; i < len; i++) { 694*392cacf2SNikita Kravets result = ec_read(addr + i, buf + i); 695*392cacf2SNikita Kravets if (result < 0) 696*392cacf2SNikita Kravets return result; 697*392cacf2SNikita Kravets } 698*392cacf2SNikita Kravets 699*392cacf2SNikita Kravets return 0; 700*392cacf2SNikita Kravets } 701*392cacf2SNikita Kravets 702*392cacf2SNikita Kravets static int ec_get_firmware_version(u8 buf[MSI_EC_FW_VERSION_LENGTH + 1]) 703*392cacf2SNikita Kravets { 704*392cacf2SNikita Kravets int result; 705*392cacf2SNikita Kravets 706*392cacf2SNikita Kravets memset(buf, 0, MSI_EC_FW_VERSION_LENGTH + 1); 707*392cacf2SNikita Kravets result = ec_read_seq(MSI_EC_FW_VERSION_ADDRESS, 708*392cacf2SNikita Kravets buf, 709*392cacf2SNikita Kravets MSI_EC_FW_VERSION_LENGTH); 710*392cacf2SNikita Kravets if (result < 0) 711*392cacf2SNikita Kravets return result; 712*392cacf2SNikita Kravets 713*392cacf2SNikita Kravets return MSI_EC_FW_VERSION_LENGTH + 1; 714*392cacf2SNikita Kravets } 715*392cacf2SNikita Kravets 716*392cacf2SNikita Kravets /* 717*392cacf2SNikita Kravets * Sysfs power_supply subsystem 718*392cacf2SNikita Kravets */ 719*392cacf2SNikita Kravets 720*392cacf2SNikita Kravets static ssize_t charge_control_threshold_show(u8 offset, 721*392cacf2SNikita Kravets struct device *device, 722*392cacf2SNikita Kravets struct device_attribute *attr, 723*392cacf2SNikita Kravets char *buf) 724*392cacf2SNikita Kravets { 725*392cacf2SNikita Kravets u8 rdata; 726*392cacf2SNikita Kravets int result; 727*392cacf2SNikita Kravets 728*392cacf2SNikita Kravets result = ec_read(conf.charge_control.address, &rdata); 729*392cacf2SNikita Kravets if (result < 0) 730*392cacf2SNikita Kravets return result; 731*392cacf2SNikita Kravets 732*392cacf2SNikita Kravets return sysfs_emit(buf, "%i\n", rdata - offset); 733*392cacf2SNikita Kravets } 734*392cacf2SNikita Kravets 735*392cacf2SNikita Kravets static ssize_t charge_control_threshold_store(u8 offset, 736*392cacf2SNikita Kravets struct device *dev, 737*392cacf2SNikita Kravets struct device_attribute *attr, 738*392cacf2SNikita Kravets const char *buf, size_t count) 739*392cacf2SNikita Kravets { 740*392cacf2SNikita Kravets u8 wdata; 741*392cacf2SNikita Kravets int result; 742*392cacf2SNikita Kravets 743*392cacf2SNikita Kravets result = kstrtou8(buf, 10, &wdata); 744*392cacf2SNikita Kravets if (result < 0) 745*392cacf2SNikita Kravets return result; 746*392cacf2SNikita Kravets 747*392cacf2SNikita Kravets wdata += offset; 748*392cacf2SNikita Kravets if (wdata < conf.charge_control.range_min || 749*392cacf2SNikita Kravets wdata > conf.charge_control.range_max) 750*392cacf2SNikita Kravets return -EINVAL; 751*392cacf2SNikita Kravets 752*392cacf2SNikita Kravets result = ec_write(conf.charge_control.address, wdata); 753*392cacf2SNikita Kravets if (result < 0) 754*392cacf2SNikita Kravets return result; 755*392cacf2SNikita Kravets 756*392cacf2SNikita Kravets return count; 757*392cacf2SNikita Kravets } 758*392cacf2SNikita Kravets 759*392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_show(struct device *device, 760*392cacf2SNikita Kravets struct device_attribute *attr, 761*392cacf2SNikita Kravets char *buf) 762*392cacf2SNikita Kravets { 763*392cacf2SNikita Kravets return charge_control_threshold_show(conf.charge_control.offset_start, 764*392cacf2SNikita Kravets device, attr, buf); 765*392cacf2SNikita Kravets } 766*392cacf2SNikita Kravets 767*392cacf2SNikita Kravets static ssize_t charge_control_start_threshold_store(struct device *dev, 768*392cacf2SNikita Kravets struct device_attribute *attr, 769*392cacf2SNikita Kravets const char *buf, size_t count) 770*392cacf2SNikita Kravets { 771*392cacf2SNikita Kravets return charge_control_threshold_store(conf.charge_control.offset_start, 772*392cacf2SNikita Kravets dev, attr, buf, count); 773*392cacf2SNikita Kravets } 774*392cacf2SNikita Kravets 775*392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_show(struct device *device, 776*392cacf2SNikita Kravets struct device_attribute *attr, 777*392cacf2SNikita Kravets char *buf) 778*392cacf2SNikita Kravets { 779*392cacf2SNikita Kravets return charge_control_threshold_show(conf.charge_control.offset_end, 780*392cacf2SNikita Kravets device, attr, buf); 781*392cacf2SNikita Kravets } 782*392cacf2SNikita Kravets 783*392cacf2SNikita Kravets static ssize_t charge_control_end_threshold_store(struct device *dev, 784*392cacf2SNikita Kravets struct device_attribute *attr, 785*392cacf2SNikita Kravets const char *buf, size_t count) 786*392cacf2SNikita Kravets { 787*392cacf2SNikita Kravets return charge_control_threshold_store(conf.charge_control.offset_end, 788*392cacf2SNikita Kravets dev, attr, buf, count); 789*392cacf2SNikita Kravets } 790*392cacf2SNikita Kravets 791*392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_start_threshold); 792*392cacf2SNikita Kravets static DEVICE_ATTR_RW(charge_control_end_threshold); 793*392cacf2SNikita Kravets 794*392cacf2SNikita Kravets static struct attribute *msi_battery_attrs[] = { 795*392cacf2SNikita Kravets &dev_attr_charge_control_start_threshold.attr, 796*392cacf2SNikita Kravets &dev_attr_charge_control_end_threshold.attr, 797*392cacf2SNikita Kravets NULL 798*392cacf2SNikita Kravets }; 799*392cacf2SNikita Kravets 800*392cacf2SNikita Kravets ATTRIBUTE_GROUPS(msi_battery); 801*392cacf2SNikita Kravets 802*392cacf2SNikita Kravets static int msi_battery_add(struct power_supply *battery, 803*392cacf2SNikita Kravets struct acpi_battery_hook *hook) 804*392cacf2SNikita Kravets { 805*392cacf2SNikita Kravets return device_add_groups(&battery->dev, msi_battery_groups); 806*392cacf2SNikita Kravets } 807*392cacf2SNikita Kravets 808*392cacf2SNikita Kravets static int msi_battery_remove(struct power_supply *battery, 809*392cacf2SNikita Kravets struct acpi_battery_hook *hook) 810*392cacf2SNikita Kravets { 811*392cacf2SNikita Kravets device_remove_groups(&battery->dev, msi_battery_groups); 812*392cacf2SNikita Kravets return 0; 813*392cacf2SNikita Kravets } 814*392cacf2SNikita Kravets 815*392cacf2SNikita Kravets static struct acpi_battery_hook battery_hook = { 816*392cacf2SNikita Kravets .add_battery = msi_battery_add, 817*392cacf2SNikita Kravets .remove_battery = msi_battery_remove, 818*392cacf2SNikita Kravets .name = MSI_EC_DRIVER_NAME, 819*392cacf2SNikita Kravets }; 820*392cacf2SNikita Kravets 821*392cacf2SNikita Kravets /* 822*392cacf2SNikita Kravets * Module load/unload 823*392cacf2SNikita Kravets */ 824*392cacf2SNikita Kravets 825*392cacf2SNikita Kravets static const struct dmi_system_id msi_dmi_table[] __initconst __maybe_unused = { 826*392cacf2SNikita Kravets { 827*392cacf2SNikita Kravets .matches = { 828*392cacf2SNikita Kravets DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"), 829*392cacf2SNikita Kravets }, 830*392cacf2SNikita Kravets }, 831*392cacf2SNikita Kravets { 832*392cacf2SNikita Kravets .matches = { 833*392cacf2SNikita Kravets DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), 834*392cacf2SNikita Kravets }, 835*392cacf2SNikita Kravets }, 836*392cacf2SNikita Kravets {} 837*392cacf2SNikita Kravets }; 838*392cacf2SNikita Kravets MODULE_DEVICE_TABLE(dmi, msi_dmi_table); 839*392cacf2SNikita Kravets 840*392cacf2SNikita Kravets static int __init load_configuration(void) 841*392cacf2SNikita Kravets { 842*392cacf2SNikita Kravets int result; 843*392cacf2SNikita Kravets 844*392cacf2SNikita Kravets u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1]; 845*392cacf2SNikita Kravets 846*392cacf2SNikita Kravets /* get firmware version */ 847*392cacf2SNikita Kravets result = ec_get_firmware_version(fw_version); 848*392cacf2SNikita Kravets if (result < 0) 849*392cacf2SNikita Kravets return result; 850*392cacf2SNikita Kravets 851*392cacf2SNikita Kravets /* load the suitable configuration, if exists */ 852*392cacf2SNikita Kravets for (int i = 0; CONFIGS[i]; i++) { 853*392cacf2SNikita Kravets if (match_string(CONFIGS[i]->allowed_fw, -1, fw_version) != -EINVAL) { 854*392cacf2SNikita Kravets conf = *CONFIGS[i]; 855*392cacf2SNikita Kravets conf.allowed_fw = NULL; 856*392cacf2SNikita Kravets return 0; 857*392cacf2SNikita Kravets } 858*392cacf2SNikita Kravets } 859*392cacf2SNikita Kravets 860*392cacf2SNikita Kravets /* config not found */ 861*392cacf2SNikita Kravets 862*392cacf2SNikita Kravets for (int i = 0; i < MSI_EC_FW_VERSION_LENGTH; i++) { 863*392cacf2SNikita Kravets if (!isgraph(fw_version[i])) { 864*392cacf2SNikita Kravets pr_warn("Unable to find a valid firmware version!\n"); 865*392cacf2SNikita Kravets return -EOPNOTSUPP; 866*392cacf2SNikita Kravets } 867*392cacf2SNikita Kravets } 868*392cacf2SNikita Kravets 869*392cacf2SNikita Kravets pr_warn("Firmware version is not supported: '%s'\n", fw_version); 870*392cacf2SNikita Kravets return -EOPNOTSUPP; 871*392cacf2SNikita Kravets } 872*392cacf2SNikita Kravets 873*392cacf2SNikita Kravets static int __init msi_ec_init(void) 874*392cacf2SNikita Kravets { 875*392cacf2SNikita Kravets int result; 876*392cacf2SNikita Kravets 877*392cacf2SNikita Kravets result = load_configuration(); 878*392cacf2SNikita Kravets if (result < 0) 879*392cacf2SNikita Kravets return result; 880*392cacf2SNikita Kravets 881*392cacf2SNikita Kravets battery_hook_register(&battery_hook); 882*392cacf2SNikita Kravets return 0; 883*392cacf2SNikita Kravets } 884*392cacf2SNikita Kravets 885*392cacf2SNikita Kravets static void __exit msi_ec_exit(void) 886*392cacf2SNikita Kravets { 887*392cacf2SNikita Kravets battery_hook_unregister(&battery_hook); 888*392cacf2SNikita Kravets } 889*392cacf2SNikita Kravets 890*392cacf2SNikita Kravets MODULE_LICENSE("GPL"); 891*392cacf2SNikita Kravets MODULE_AUTHOR("Jose Angel Pastrana <japp0005@red.ujaen.es>"); 892*392cacf2SNikita Kravets MODULE_AUTHOR("Aakash Singh <mail@singhaakash.dev>"); 893*392cacf2SNikita Kravets MODULE_AUTHOR("Nikita Kravets <teackot@gmail.com>"); 894*392cacf2SNikita Kravets MODULE_DESCRIPTION("MSI Embedded Controller"); 895*392cacf2SNikita Kravets 896*392cacf2SNikita Kravets module_init(msi_ec_init); 897*392cacf2SNikita Kravets module_exit(msi_ec_exit); 898