Hi, On 3/20/23 23:55, Nikita Kravets wrote: > Add a new driver to allow various MSI laptops' functionalities to be > controlled from userspace. This includes such features as power > profiles (aka shift modes), fan speed, charge thresholds, LEDs, etc. > > This driver contains EC memory configurations for different firmware > versions and exports battery charge thresholds to userspace (note, > that start and end thresholds control the same EC parameter > and are always 10% apart). > > Link: https://github.com/BeardOverflow/msi-ec/ > Discussion: https://github.com/BeardOverflow/msi-ec/pull/13 > Cc: Aakash Singh <mail@xxxxxxxxxxxxxxx> > Cc: Jose Angel Pastrana <japp0005@xxxxxxxxxxxx> > Signed-off-by: Nikita Kravets <teackot@xxxxxxxxx> Thank you! I have merged this now with a few minor fixups done while merging: - Add "depends on ACPI_BATTERY" to the Kconfig part for the used battery hook functions - Converted a few remaining c++ style comments to /* */ style - Dropped the pr_info("module_[init|exit]\n") messages (IMHO these just spam dmesg without adding much) Thank you for your patch, I've applied this patch to my review-hans branch: https://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git/log/?h=review-hans Note it will show up in my review-hans branch once I've pushed my local branch there, which might take a while. Once I've run some tests on this branch the patches there will be added to the platform-drivers-x86/for-next branch and eventually will be included in the pdx86 pull-request to Linus for the next merge-window. Regards, Hans > --- > Changes in v2: > - fix checkpatch issues: > - update MAINTAINERS > - fix SPDX license identifier > - use __initconst for const init definitions > - minor formatting > - omit commas after sentinel entries > - use pr_fmt macro to automatically prefix messages > - remove a redundant ACPI check > - in ec_read_seq(): define the counter in the for loop header, > make i and len the same type > - better error handling in msi_battery_add() > - use sysfs_emit() instead of sprintf() > - catch up with the main driver repo: > - add new configurations > - add compatible EC FW versions to existing configurations > - allow fan_mode parameter to have a custom set of modes > - add MSI_EC_ADDR_UNKNOWN and MSI_EC_ADDR_UNSUPP = 0xff01 to specify > unknown addresses and unsupported parameters. When converted to u8, > they evaluate to 0x01, which is a readonly address in MSI EC, > so in case of bugs writing to them should be safe > - use sentinel entries for modes arrays > > Changes in v3: > - function style consistency > - change comments style > - simplify searching for a suitable conf > - log the not supported version with pr_warn > - add modaliases > > MAINTAINERS | 7 + > drivers/platform/x86/Kconfig | 7 + > drivers/platform/x86/Makefile | 1 + > drivers/platform/x86/msi-ec.c | 913 ++++++++++++++++++++++++++++++++++ > drivers/platform/x86/msi-ec.h | 122 +++++ > 5 files changed, 1050 insertions(+) > create mode 100644 drivers/platform/x86/msi-ec.c > create mode 100644 drivers/platform/x86/msi-ec.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index fb1471cb5ed3..651578a14360 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14191,6 +14191,13 @@ S: Maintained > F: Documentation/devicetree/bindings/net/ieee802154/mrf24j40.txt > F: drivers/net/ieee802154/mrf24j40.c > > +MSI EC DRIVER > +M: Nikita Kravets <teackot@xxxxxxxxx> > +L: platform-driver-x86@xxxxxxxxxxxxxxx > +S: Maintained > +W: https://github.com/BeardOverflow/msi-ec > +F: drivers/platform/x86/msi-ec.* > + > MSI LAPTOP SUPPORT > M: "Lee, Chun-Yi" <jlee@xxxxxxxx> > L: platform-driver-x86@xxxxxxxxxxxxxxx > diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig > index 5692385e2d26..4534d11f9ca5 100644 > --- a/drivers/platform/x86/Kconfig > +++ b/drivers/platform/x86/Kconfig > @@ -644,6 +644,13 @@ config THINKPAD_LMI > > source "drivers/platform/x86/intel/Kconfig" > > +config MSI_EC > + tristate "MSI EC Extras" > + depends on ACPI > + help > + This driver allows various MSI laptops' functionalities to be > + controlled from userspace, including battery charge threshold. > + > config MSI_LAPTOP > tristate "MSI Laptop Extras" > depends on ACPI > diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile > index 1d3d1b02541b..7cc2beca8208 100644 > --- a/drivers/platform/x86/Makefile > +++ b/drivers/platform/x86/Makefile > @@ -71,6 +71,7 @@ obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o > obj-y += intel/ > > # MSI > +obj-$(CONFIG_MSI_EC) += msi-ec.o > obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o > obj-$(CONFIG_MSI_WMI) += msi-wmi.o > > diff --git a/drivers/platform/x86/msi-ec.c b/drivers/platform/x86/msi-ec.c > new file mode 100644 > index 000000000000..76450d9c6a22 > --- /dev/null > +++ b/drivers/platform/x86/msi-ec.c > @@ -0,0 +1,913 @@ > +// 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@xxxxxxxxxxxx> > + * Copyright (C) 2023 Aakash Singh <mail@xxxxxxxxxxxxxxx> > + * Copyright (C) 2023 Nikita Kravets <teackot@xxxxxxxxx> > + */ > + > +#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 = { > + { > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT"), > + }, > + }, > + { > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"), > + }, > + }, > + { > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, > + "Micro-Star International Co., Ltd."), > + }, > + }, > + { > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, > + "MICRO-STAR INTERNATIONAL CO., LTD"), > + }, > + }, > + {} > +}; > +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); > + > + pr_info("module_init\n"); > + return 0; > +} > + > +static void __exit msi_ec_exit(void) > +{ > + battery_hook_unregister(&battery_hook); > + > + pr_info("module_exit\n"); > +} > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Jose Angel Pastrana <japp0005@xxxxxxxxxxxx>"); > +MODULE_AUTHOR("Aakash Singh <mail@xxxxxxxxxxxxxxx>"); > +MODULE_AUTHOR("Nikita Kravets <teackot@xxxxxxxxx>"); > +MODULE_DESCRIPTION("MSI Embedded Controller"); > + > +module_init(msi_ec_init); > +module_exit(msi_ec_exit); > diff --git a/drivers/platform/x86/msi-ec.h b/drivers/platform/x86/msi-ec.h > new file mode 100644 > index 000000000000..be3533dc9cc6 > --- /dev/null > +++ b/drivers/platform/x86/msi-ec.h > @@ -0,0 +1,122 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > + > +/* > + * msi-ec: MSI laptops' embedded controller driver. > + * > + * Copyright (C) 2023 Jose Angel Pastrana <japp0005@xxxxxxxxxxxx> > + * Copyright (C) 2023 Aakash Singh <mail@xxxxxxxxxxxxxxx> > + * Copyright (C) 2023 Nikita Kravets <teackot@xxxxxxxxx> > + */ > + > +#ifndef _MSI_EC_H_ > +#define _MSI_EC_H_ > + > +#include <linux/types.h> > + > +#define MSI_EC_DRIVER_NAME "msi-ec" > + > +#define MSI_EC_ADDR_UNKNOWN 0xff01 // unknown address > +#define MSI_EC_ADDR_UNSUPP 0xff01 // unsupported parameter > + > +// Firmware info addresses are universal > +#define MSI_EC_FW_VERSION_ADDRESS 0xa0 > +#define MSI_EC_FW_DATE_ADDRESS 0xac > +#define MSI_EC_FW_TIME_ADDRESS 0xb4 > +#define MSI_EC_FW_VERSION_LENGTH 12 > +#define MSI_EC_FW_DATE_LENGTH 8 > +#define MSI_EC_FW_TIME_LENGTH 8 > + > +struct msi_ec_charge_control_conf { > + int address; > + int offset_start; > + int offset_end; > + int range_min; > + int range_max; > +}; > + > +struct msi_ec_webcam_conf { > + int address; > + int block_address; > + int bit; > +}; > + > +struct msi_ec_fn_super_swap_conf { > + int address; > + int bit; > +}; > + > +struct msi_ec_cooler_boost_conf { > + int address; > + int bit; > +}; > + > +#define MSI_EC_MODE_NULL { NULL, 0 } > +struct msi_ec_mode { > + const char *name; > + int value; > +}; > + > +struct msi_ec_shift_mode_conf { > + int address; > + struct msi_ec_mode modes[5]; // fixed size for easier hard coding > +}; > + > +struct msi_ec_super_battery_conf { > + int address; > + int mask; > +}; > + > +struct msi_ec_fan_mode_conf { > + int address; > + struct msi_ec_mode modes[5]; // fixed size for easier hard coding > +}; > + > +struct msi_ec_cpu_conf { > + int rt_temp_address; > + int rt_fan_speed_address; // realtime > + int rt_fan_speed_base_min; > + int rt_fan_speed_base_max; > + int bs_fan_speed_address; // basic > + int bs_fan_speed_base_min; > + int bs_fan_speed_base_max; > +}; > + > +struct msi_ec_gpu_conf { > + int rt_temp_address; > + int rt_fan_speed_address; // realtime > +}; > + > +struct msi_ec_led_conf { > + int micmute_led_address; > + int mute_led_address; > + int bit; > +}; > + > +#define MSI_EC_KBD_BL_STATE_MASK 0x3 > +struct msi_ec_kbd_bl_conf { > + int bl_mode_address; > + int bl_modes[2]; > + int max_mode; > + > + int bl_state_address; > + int state_base_value; > + int max_state; > +}; > + > +struct msi_ec_conf { > + const char * const *allowed_fw; > + > + struct msi_ec_charge_control_conf charge_control; > + struct msi_ec_webcam_conf webcam; > + struct msi_ec_fn_super_swap_conf fn_super_swap; > + struct msi_ec_cooler_boost_conf cooler_boost; > + struct msi_ec_shift_mode_conf shift_mode; > + struct msi_ec_super_battery_conf super_battery; > + struct msi_ec_fan_mode_conf fan_mode; > + struct msi_ec_cpu_conf cpu; > + struct msi_ec_gpu_conf gpu; > + struct msi_ec_led_conf leds; > + struct msi_ec_kbd_bl_conf kbd_bl; > +}; > + > +#endif // _MSI_EC_H_