platform-driver-x86.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] platform/x86: Add new msi-ec driver
@ 2023-02-14 13:25 Nikita Kravets
  2023-02-14 15:03 ` Barnabás Pőcze
  2023-03-01 17:50 ` Hans de Goede
  0 siblings, 2 replies; 9+ messages in thread
From: Nikita Kravets @ 2023-02-14 13:25 UTC (permalink / raw)
  To: hdegoede; +Cc: platform-driver-x86, Nikita Kravets

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@gmail.com>
---
 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@red.ujaen.es>
+ * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
+ * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
+ */
+
+#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,
+};
+
+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 },
+		},
+		.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++) {
+		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);
+}
+
+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;
+}
+
+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;
+			}
+		}
+	}
+
+	pr_err("Your firmware version is not supported!\n");
+	return -EOPNOTSUPP;
+}
+
+static int __init msi_ec_init(void)
+{
+	int result;
+
+	if (acpi_disabled) {
+		pr_err("Unable to init because ACPI needs to be enabled first!\n");
+		return -ENODEV;
+	}
+
+	result = load_configuration();
+	if (result < 0)
+		return result;
+
+	battery_hook_register(&battery_hook);
+
+	pr_info("msi-ec: module_init\n");
+	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@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);
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@red.ujaen.es>
+ * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
+ * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
+ */
+
+#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


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-02-14 13:25 [PATCH] platform/x86: Add new msi-ec driver Nikita Kravets
@ 2023-02-14 15:03 ` Barnabás Pőcze
  2023-02-15 21:27   ` Nikita Kravets
  2023-03-01 17:50 ` Hans de Goede
  1 sibling, 1 reply; 9+ messages in thread
From: Barnabás Pőcze @ 2023-02-14 15:03 UTC (permalink / raw)
  To: Nikita Kravets; +Cc: hdegoede, platform-driver-x86

Hi


2023. február 14., kedd 14:25 keltezéssel, Nikita Kravets <teackot@gmail.com> í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@gmail.com>
> ---
>  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@red.ujaen.es>
> + * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
> + * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
> + */
> +
> +#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@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);
> 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@red.ujaen.es>
> + * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
> + * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
> + */
> +
> +#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

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-02-14 15:03 ` Barnabás Pőcze
@ 2023-02-15 21:27   ` Nikita Kravets
  2023-03-01 15:19     ` Hans de Goede
  0 siblings, 1 reply; 9+ messages in thread
From: Nikita Kravets @ 2023-02-15 21:27 UTC (permalink / raw)
  To: Barnabás Pőcze; +Cc: Hans de Goede, platform-driver-x86

Hi,

First of all, sorry if any of you got the message more than once.
Gmail (and I) messed some stuff up.

> Usually commas are omitted after sentinel entries.

I see, makes sense.

> Alternatively:
>
>         .allowed_fw = (const char * const []) {
>                 "...",
>                 "...",
>                 NULL
>         },
>
> (although this won't inherit the __initdata attribute as far as I can see)

This looks nicer so the question is: how important is it to put those
strings into initdata, as they don't take much memory.

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

Okay. I should also put the `i` declaration into the for loop header.
(I'm not the original creator of this function so I didn't touch it
yet)

> Why not
>
>   return device_add_groups(...);
>
> ?

Agreed. Didn't look into this one too.

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

So far all laptops we've tested only have one, controlled by a single
EC parameter.

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

Just checked, it does.

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

It only reads though, can it cause any harm?

> static int __init load_configuration(void)
> {
>         int result;
>
>         // get firmware version
>         u8 ver[MSI_EC_FW_VERSION_LENGTH + 1];
>         result = ec_get_firmware_version(ver);
>         if (result < 0) {
>                 return result;
>         }

Also a note from myself: I think this should return -EOPNOTSUPP if
ec_get_firmware() returns -ENODEV

-- 
Regards,
Nikita

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-02-15 21:27   ` Nikita Kravets
@ 2023-03-01 15:19     ` Hans de Goede
  0 siblings, 0 replies; 9+ messages in thread
From: Hans de Goede @ 2023-03-01 15:19 UTC (permalink / raw)
  To: Nikita Kravets, Barnabás Pőcze; +Cc: platform-driver-x86

Hi,

Before diving into a full review of the driver I thought
it would be good to first catch up with the email thread sofar.

I agree with everything discussed so far. One clarifying remark
below:

On 2/15/23 22:27, Nikita Kravets wrote:

<snip>

>> This will start poking the embedded controller when the module is loaded, regardless of the platform. I am not sure that is desirable.
> 
> It only reads though, can it cause any harm?

I have seen cases where some weird (i2c) hw reacts to
reads as if they are writes.

But since this is using the standardized ACPI EC access
routines I believe that doing reads should generally
speaking be safe.

Also it seems that atm the module must always be loaded
manually ?

I don't see any MODULE_ALIAS or MODULE_DEVICE_TABLE entries
in the driver to make it auto-load.

I think this should get a dmi_system_id tables with known
MSI DMI_SYS_VENDOR() matches in there + a
MODULE_DEVICE_TABLE() pointing to the dmi_system_id table
to have the driver auto-load on MSI systems.

I think what we should do here is:

1. Get the module in the mainline kernel
2. Do a blogpost asking MSI laptop users to test
3. Assuming testing does not show any regressions / issues
   (it will likely show lots of unsupported fw versions)
   add the MODULE_DEVICE_TABLE().

Regards,

Hans



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-02-14 13:25 [PATCH] platform/x86: Add new msi-ec driver Nikita Kravets
  2023-02-14 15:03 ` Barnabás Pőcze
@ 2023-03-01 17:50 ` Hans de Goede
  2023-03-01 20:17   ` Nikita Kravets
  1 sibling, 1 reply; 9+ messages in thread
From: Hans de Goede @ 2023-03-01 17:50 UTC (permalink / raw)
  To: Nikita Kravets; +Cc: platform-driver-x86

Hi,

On 2/14/23 14:25, 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
> Signed-off-by: Nikita Kravets <teackot@gmail.com>

Thanks overall this looks pretty good.

Please address the review-remarks from Barnabás.

I also have 2 small remarks myself:

1. See the remark inline below.

2. I ran checkpatch on the patch and it found several issues,
   I'll copy and paste the output to the end of this email,
   please address these issues.

   And if possible run scripts/checkpatch.pl yourself to
   verify the issues are fixed.


> ---
>  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

<snip>

> 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@red.ujaen.es>
> + * Copyright (C) 2023 Aakash Singh <mail@singhaakash.dev>
> + * Copyright (C) 2023 Nikita Kravets <teackot@gmail.com>
> + */
> +

Since you are using pr_info() / pr_err() please add:

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

This will automatically prefix all pr_*() calls with

"msi-ec: "

<snip>

> +	pr_err("Your firmware version is not supported!\n");

So this will then now get automatically prefixed.

> +	return -EOPNOTSUPP;
> +}
> +
> +static int __init msi_ec_init(void)
> +{
> +	int result;
> +
> +	if (acpi_disabled) {
> +		pr_err("Unable to init because ACPI needs to be enabled first!\n");

And this too.


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

And you can drop the manual "msi-ec: " prefixing here then.

> +	return 0;
> +}
> +
> +static void __exit msi_ec_exit(void)
> +{
> +	battery_hook_unregister(&battery_hook);
> +
> +	pr_info("msi-ec: module_exit\n");

And drop it here too.

Regards,

Hans


[hans@shalem platform-drivers-x86]$ git format-patch HEAD~
0001-platform-x86-Add-new-msi-ec-driver.patch
[hans@shalem platform-drivers-x86]$ scripts/checkpatch.pl 0001-platform-x86-Add-new-msi-ec-driver.patch
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#60: 
new file mode 100644

WARNING: Improper SPDX comment style for 'drivers/platform/x86/msi-ec.c', please use '//' instead
#65: FILE: drivers/platform/x86/msi-ec.c:1:
+/* SPDX-License-Identifier: GPL-2.0-or-later */

WARNING: Missing or malformed SPDX-License-Identifier tag in line 1
#65: FILE: drivers/platform/x86/msi-ec.c:1:
+/* SPDX-License-Identifier: GPL-2.0-or-later */

ERROR: Use of const init definition must use __initconst
#97: FILE: drivers/platform/x86/msi-ec.c:33:
+static const char *ALLOWED_FW_0[] __initdata = {

ERROR: Use of const init definition must use __initconst
#167: FILE: drivers/platform/x86/msi-ec.c:103:
+static const char *ALLOWED_FW_1[] __initdata = {

ERROR: Use of const init definition must use __initconst
#238: FILE: drivers/platform/x86/msi-ec.c:174:
+static const char *ALLOWED_FW_2[] __initdata = {

ERROR: Use of const init definition must use __initconst
#310: FILE: drivers/platform/x86/msi-ec.c:246:
+static const char *ALLOWED_FW_3[] __initdata = {

WARNING: Missing a blank line after declarations
#401: FILE: drivers/platform/x86/msi-ec.c:337:
+	u8 i;
+	for (i = 0; i < len; i++) {

WARNING: Missing a blank line after declarations
#539: FILE: drivers/platform/x86/msi-ec.c:475:
+	u8 fw_version[MSI_EC_FW_VERSION_LENGTH + 1];
+	result = ec_get_firmware_version(fw_version);

WARNING: braces {} are not necessary for single statement blocks
#540: FILE: drivers/platform/x86/msi-ec.c:476:
+	if (result < 0) {
+		return result;
+	}

total: 4 errors, 6 warnings, 667 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

0001-platform-x86-Add-new-msi-ec-driver.patch has style problems, please review.

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.






^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-03-01 17:50 ` Hans de Goede
@ 2023-03-01 20:17   ` Nikita Kravets
  2023-03-02  8:56     ` Hans de Goede
  0 siblings, 1 reply; 9+ messages in thread
From: Nikita Kravets @ 2023-03-01 20:17 UTC (permalink / raw)
  To: Hans de Goede, Barnabás Pőcze
  Cc: platform-driver-x86, Aakash Singh, Jose Angel Pastrana

Hi Hans,

We already have changes addressing Barnabás' remarks merged
into the original repo, including the pr_fmt macro, so I only
need to apply them to the kernel.

> I ran checkpatch on the patch and it found several issues

Thanks, I'll address them. Some of them are already fixed
in the original repo.

> Also it seems that atm the module must always be loaded
> manually ?

> I think this should get a dmi_system_id tables with known
> MSI DMI_SYS_VENDOR() matches in there + a
> MODULE_DEVICE_TABLE() pointing to the dmi_system_id table
> to have the driver auto-load on MSI systems.

It loads automatically for me. Though would be better
to only auto-load it on MSI systems.

Regards,

Nikita

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-03-01 20:17   ` Nikita Kravets
@ 2023-03-02  8:56     ` Hans de Goede
  2023-03-02 12:45       ` Nikita Kravets
  0 siblings, 1 reply; 9+ messages in thread
From: Hans de Goede @ 2023-03-02  8:56 UTC (permalink / raw)
  To: Nikita Kravets, Barnabás Pőcze
  Cc: platform-driver-x86, Aakash Singh, Jose Angel Pastrana

Hi Nikita,

On 3/1/23 21:17, Nikita Kravets wrote:
> Hi Hans,
> 
> We already have changes addressing Barnabás' remarks merged
> into the original repo, including the pr_fmt macro, so I only
> need to apply them to the kernel.
> 
>> I ran checkpatch on the patch and it found several issues
> 
> Thanks, I'll address them. Some of them are already fixed
> in the original repo.
> 
>> Also it seems that atm the module must always be loaded
>> manually ?
> 
>> I think this should get a dmi_system_id tables with known
>> MSI DMI_SYS_VENDOR() matches in there + a
>> MODULE_DEVICE_TABLE() pointing to the dmi_system_id table
>> to have the driver auto-load on MSI systems.
> 
> It loads automatically for me. Though would be better
> to only auto-load it on MSI systems.

I don't see any module-aliases in the submitted msi-ec.c,
so AFAICT it should not auto load.

Thus, that it is autoloaded for you is weird. Did you
maybe add it to a config file in /etc/modules-load.d/ ?

What is the output of "modinfo msi-ec" on your system ?

Regards,

Hans



^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-03-02  8:56     ` Hans de Goede
@ 2023-03-02 12:45       ` Nikita Kravets
  2023-03-02 14:05         ` Hans de Goede
  0 siblings, 1 reply; 9+ messages in thread
From: Nikita Kravets @ 2023-03-02 12:45 UTC (permalink / raw)
  To: Hans de Goede, Barnabás Pőcze
  Cc: platform-driver-x86, Aakash Singh, Jose Angel Pastrana

Here is my `modinfo msi-ec`:

name:           msi_ec
filename:       (builtin)
description:    MSI Embedded Controller
author:         Nikita Kravets <teackot@gmail.com>
author:         Aakash Singh <mail@singhaakash.dev>
author:         Jose Angel Pastrana <japp0005@red.ujaen.es>
license:        GPL
file:           drivers/platform/x86/msi-ec

> Did you maybe add it to a config file in /etc/modules-load.d/ ?

No, I don't have it there

Regards,

Nikita

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] platform/x86: Add new msi-ec driver
  2023-03-02 12:45       ` Nikita Kravets
@ 2023-03-02 14:05         ` Hans de Goede
  0 siblings, 0 replies; 9+ messages in thread
From: Hans de Goede @ 2023-03-02 14:05 UTC (permalink / raw)
  To: Nikita Kravets, Barnabás Pőcze
  Cc: platform-driver-x86, Aakash Singh, Jose Angel Pastrana

Hi,

On 3/2/23 13:45, Nikita Kravets wrote:
> Here is my `modinfo msi-ec`:
> 
> name:           msi_ec
> filename:       (builtin)
> description:    MSI Embedded Controller
> author:         Nikita Kravets <teackot@gmail.com>
> author:         Aakash Singh <mail@singhaakash.dev>
> author:         Jose Angel Pastrana <japp0005@red.ujaen.es>
> license:        GPL
> file:           drivers/platform/x86/msi-ec

Ah you have set it to builtin rather then building it as
a module.

Yes then it will work without any module-aliases since then it
is always part of the kernel.

Regards,

Hans


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2023-03-02 14:06 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-02-14 13:25 [PATCH] platform/x86: Add new msi-ec driver Nikita Kravets
2023-02-14 15:03 ` Barnabás Pőcze
2023-02-15 21:27   ` Nikita Kravets
2023-03-01 15:19     ` Hans de Goede
2023-03-01 17:50 ` Hans de Goede
2023-03-01 20:17   ` Nikita Kravets
2023-03-02  8:56     ` Hans de Goede
2023-03-02 12:45       ` Nikita Kravets
2023-03-02 14:05         ` Hans de Goede

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).