Re: [PATCH] platform/x86: Add new msi-ec driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi


2023. február 14., kedd 14:25 keltezéssel, Nikita Kravets <teackot@xxxxxxxxx> írta:

> 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
> Signed-off-by: Nikita Kravets <teackot@xxxxxxxxx>
> ---
>  drivers/platform/x86/Kconfig  |   7 +
>  drivers/platform/x86/Makefile |   1 +
>  drivers/platform/x86/msi-ec.c | 528 ++++++++++++++++++++++++++++++++++
>  drivers/platform/x86/msi-ec.h | 119 ++++++++
>  4 files changed, 655 insertions(+)
>  create mode 100644 drivers/platform/x86/msi-ec.c
>  create mode 100644 drivers/platform/x86/msi-ec.h
> 
> 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..b32106445bf6
> --- /dev/null
> +++ b/drivers/platform/x86/msi-ec.c
> @@ -0,0 +1,528 @@
> +/* 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>
> + */
> +
> +#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/proc_fs.h>
> +#include <linux/seq_file.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 *ALLOWED_FW_0[] __initdata = {
> +	"14C1EMS1.101",
> +	NULL,

Usually commas are omitted after sentinel entries.


> +};
> +
> +static struct msi_ec_conf CONF0 __initdata = {
> +	.allowed_fw = ALLOWED_FW_0,

Alternatively:

	.allowed_fw = (const char * const []) {
		"...",
		"...",
		NULL
	},

(although this won't inherit the __initdata attribute as far as I can see)


> +	.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 },
> +		},
> +		.modes_count = 3,
> +	},
> +	.super_battery = {
> +		.supported = false,
> +	},
> +	.fan_mode = {
> +		.address = 0xf4,
> +	},
> +	.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 *ALLOWED_FW_1[] __initdata = {
> +	"17F2EMS1.106",
> +	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 },
> +		},
> +		.modes_count = 4,
> +	},
> +	.super_battery = {
> +		.supported = false,
> +	},
> +	.fan_mode = {
> +		.address = 0xf4,
> +	},
> +	.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 *ALLOWED_FW_2[] __initdata = {
> +	"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 },
> +		},
> +		.modes_count = 3,
> +	},
> +	.super_battery = {
> +		.supported = true,
> +		.address   = 0xeb,
> +		.mask      = 0x0f,
> +	},
> +	.fan_mode = {
> +		.address = 0xd4,
> +	},
> +	.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 *ALLOWED_FW_3[] __initdata = {
> +	"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 },
> +		},
> +		.modes_count = 3,
> +	},
> +	.super_battery = {
> +		.supported = true,
> +		.address   = 0xeb,
> +		.mask      = 0x0f,
> +	},
> +	.fan_mode = {
> +		.address = 0xd4,
> +	},
> +	.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 struct msi_ec_conf *CONFIGURATIONS[] __initdata = {
> +	&CONF0,
> +	&CONF1,
> +	&CONF2,
> +	&CONF3,
> +	NULL,
> +};
> +
> +static struct msi_ec_conf conf; // current configuration
> +
> +// ============================================================ //
> +// Helper functions
> +// ============================================================ //
> +
> +static int ec_read_seq(u8 addr, u8 *buf, int len)
> +{
> +	int result;
> +	u8 i;
> +	for (i = 0; i < len; i++) {

It's a small thing, but I would make `i` and `len` be the same type.


> +		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 sprintf(buf, "%i\n", rdata - offset);

Please prefer `sysfs_emit()`.


> +}
> +
> +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)
> +{
> +	if (device_add_groups(&battery->dev, msi_battery_groups))
> +		return -ENODEV;
> +	return 0;

Why not

  return device_add_groups(...);

?

Furthermore, is it possible that there are two or more batteries?


> +}
> +
> +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 int __init load_configuration(void)
> +{
> +	int result;
> +
> +	// get firmware version
> +	u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
> +	result = ec_get_firmware_version(fw_version);
> +	if (result < 0) {
> +		return result;
> +	}
> +
> +	// load the suitable configuration, if exists
> +	for (int i = 0; CONFIGURATIONS[i]; i++) {
> +		for (int j = 0; CONFIGURATIONS[i]->allowed_fw[j]; j++) {
> +			if (strcmp(CONFIGURATIONS[i]->allowed_fw[j], fw_version) == 0) {
> +				memcpy(&conf, CONFIGURATIONS[i], sizeof(struct msi_ec_conf));
> +				conf.allowed_fw = NULL;
> +				return 0;
> +			}
> +		}
> +	}

Have you checked if `match_string()` from string.h works here?


> +
> +	pr_err("Your firmware version is not supported!\n");
> +	return -EOPNOTSUPP;
> +}
> +
> +static int __init msi_ec_init(void)
> +{
> +	int result;
> +
> +	if (acpi_disabled) {

I am wondering how useful this check really is.


> +		pr_err("Unable to init because ACPI needs to be enabled first!\n");
> +		return -ENODEV;
> +	}
> +
> +	result = load_configuration();

This will start poking the embedded controller when the module is loaded,
regardless of the platform. I am not sure that is desirable.


> +	if (result < 0)
> +		return result;
> +
> +	battery_hook_register(&battery_hook);
> +
> +	pr_info("msi-ec: module_init\n");

Instead of manually prefixing the messages, I suggest you do

  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

before including any headers and then you can drop the "msi-ec: " from the strings.


> +	return 0;
> +}
> +
> +static void __exit msi_ec_exit(void)
> +{
> +	battery_hook_unregister(&battery_hook);
> +
> +	pr_info("msi-ec: 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..4de6bba363ff
> --- /dev/null
> +++ b/drivers/platform/x86/msi-ec.h
> @@ -0,0 +1,119 @@
> +/* 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"
> +
> +// 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;
> +};
> +
> +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
> +	int modes_count;
> +};
> +
> +struct msi_ec_super_battery_conf {
> +	bool supported;
> +	int address;
> +	int mask;
> +};
> +
> +struct msi_ec_fan_mode_conf {
> +	int address;
> +};
> +
> +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 **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_
> --
> 2.39.1
> 


Regards,
Barnabás Pőcze




[Index of Archives]     [Linux Kernel Development]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux