Ver código fonte

Add performance-mode support for Surface Book 2

Adds a driver for the Surface platform Integration Device (SID) of the
Surface Book 2. This allows for setting the performance-mode, which can
be used to choose between performance-optimized vs. quiet operation.

The performance-mode can be set via the perf_mode sysfs attribute on the
corresponding device (MSHW0107).
qzed 6 anos atrás
pai
commit
4e2e009dc7

+ 1 - 0
configs/4.19/config

@@ -7778,6 +7778,7 @@ CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE=n
 CONFIG_SURFACE_ACPI_SAN=y
 CONFIG_SURFACE_ACPI_VHF=y
 CONFIG_SURFACE_ACPI_DTX=y
+CONFIG_SURFACE_ACPI_SID=y
 CONFIG_SENSORS_HDAPS=m
 CONFIG_INTEL_MENLOW=m
 CONFIG_EEEPC_LAPTOP=m

+ 1 - 0
configs/5.0/config

@@ -7831,6 +7831,7 @@ CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE=n
 CONFIG_SURFACE_ACPI_SAN=y
 CONFIG_SURFACE_ACPI_VHF=y
 CONFIG_SURFACE_ACPI_DTX=y
+CONFIG_SURFACE_ACPI_SID=y
 CONFIG_SENSORS_HDAPS=m
 CONFIG_INTEL_MENLOW=m
 CONFIG_EEEPC_LAPTOP=m

+ 316 - 11
patches/4.19/0001-surface-acpi.patch

@@ -1,20 +1,20 @@
-From 06454d6870b688c2981c4121d70db69fdf9845c7 Mon Sep 17 00:00:00 2001
+From fdd0f9532af763dbdbab7c6749f2e9d39af11db0 Mon Sep 17 00:00:00 2001
 From: qzed <qzed@users.noreply.github.com>
-Date: Thu, 4 Apr 2019 22:47:12 +0200
+Date: Sun, 21 Apr 2019 14:59:22 +0200
 Subject: [PATCH 01/11] surface-acpi
 
 ---
  drivers/acpi/acpica/dsopcode.c      |    2 +-
  drivers/acpi/acpica/exfield.c       |   26 +-
- drivers/platform/x86/Kconfig        |   84 +
+ drivers/platform/x86/Kconfig        |   97 +
  drivers/platform/x86/Makefile       |    1 +
- drivers/platform/x86/surface_acpi.c | 3536 +++++++++++++++++++++++++++
+ drivers/platform/x86/surface_acpi.c | 3828 +++++++++++++++++++++++++++
  drivers/tty/serdev/core.c           |   90 +-
- 6 files changed, 3720 insertions(+), 19 deletions(-)
+ 6 files changed, 4025 insertions(+), 19 deletions(-)
  create mode 100644 drivers/platform/x86/surface_acpi.c
 
 diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c
-index 78f9de260d5f..0cd858520f5b 100644
+index 2f4641e5ecde..beb22d7e245e 100644
 --- a/drivers/acpi/acpica/dsopcode.c
 +++ b/drivers/acpi/acpica/dsopcode.c
 @@ -123,7 +123,7 @@ acpi_ds_init_buffer_field(u16 aml_opcode,
@@ -80,10 +80,10 @@ index b272c329d45d..cf547883a993 100644
  		} else {	/* IPMI */
  
 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index 7563c07e14e4..4c4b138170c4 100644
+index 1e2524de6a63..9a47363a0c30 100644
 --- a/drivers/platform/x86/Kconfig
 +++ b/drivers/platform/x86/Kconfig
-@@ -573,6 +573,90 @@ config THINKPAD_ACPI_HOTKEY_POLL
+@@ -573,6 +573,103 @@ config THINKPAD_ACPI_HOTKEY_POLL
  	  If you are not sure, say Y here.  The driver enables polling only if
  	  it is strictly necessary to do so.
  
@@ -170,12 +170,25 @@ index 7563c07e14e4..4c4b138170c4 100644
 +	  upon device mode change.
 +
 +	  If you are not sure, say Y here.
++
++config SURFACE_ACPI_SID
++	bool "Surface Platform Integration Driver"
++	depends on SURFACE_ACPI_SSH
++	default y
++	---help---
++	  Surface Platform Integration Driver for the Microsoft Surface Devices.
++	  Currently only supports the Surface Book 2. This driver provides suport
++	  for setting performance-modes via the perf_mode sysfs attribute.
++	  Performance-modes directly influence the fan-profile of the device,
++	  allowing to choose between higher performance or quieter operation.
++
++	  If you are not sure, say Y here.
 +
  config SENSORS_HDAPS
  	tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
  	depends on INPUT
 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
-index e6d1becf81ce..ab8be80b6596 100644
+index dc29af4d8e2f..2250a32a5527 100644
 --- a/drivers/platform/x86/Makefile
 +++ b/drivers/platform/x86/Makefile
 @@ -35,6 +35,7 @@ obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
@@ -188,10 +201,10 @@ index e6d1becf81ce..ab8be80b6596 100644
  obj-$(CONFIG_FUJITSU_TABLET)	+= fujitsu-tablet.o
 diff --git a/drivers/platform/x86/surface_acpi.c b/drivers/platform/x86/surface_acpi.c
 new file mode 100644
-index 000000000000..9d11028562c9
+index 000000000000..ab793f6774a0
 --- /dev/null
 +++ b/drivers/platform/x86/surface_acpi.c
-@@ -0,0 +1,3536 @@
+@@ -0,0 +1,3828 @@
 +#include <asm/unaligned.h>
 +#include <linux/acpi.h>
 +#include <linux/completion.h>
@@ -207,6 +220,8 @@ index 000000000000..9d11028562c9
 +#include <linux/kernel.h>
 +#include <linux/kfifo.h>
 +#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
 +#include <linux/mutex.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm.h>
@@ -224,6 +239,8 @@ index 000000000000..9d11028562c9
 +#define USB_DEVICE_ID_MS_VHF				0xf001
 +#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION	0x0922
 +
++#define SG5_PARAM_PERM		(S_IRUGO | S_IWUSR)
++
 +
 +/*************************************************************************
 + * Surface Serial Hub driver (cross-driver interface)
@@ -3675,6 +3692,286 @@ index 000000000000..9d11028562c9
 +
 +
 +/*************************************************************************
++ * Surface Platform Integration Driver
++ */
++
++#ifdef CONFIG_SURFACE_ACPI_SID
++
++enum sg5_perf_mode {
++	SG5_PERF_MODE_NORMAL   = 1,
++	SG5_PERF_MODE_BATTERY  = 2,
++	SG5_PERF_MODE_PERF1    = 3,
++	SG5_PERF_MODE_PERF2    = 4,
++
++	__SG5_PERF_MODE__START = 1,
++	__SG5_PERF_MODE__END   = 4,
++};
++
++enum sg5_param_perf_mode {
++	SG5_PARAM_PERF_MODE_AS_IS    = 0,
++	SG5_PARAM_PERF_MODE_NORMAL   = SG5_PERF_MODE_NORMAL,
++	SG5_PARAM_PERF_MODE_BATTERY  = SG5_PERF_MODE_BATTERY,
++	SG5_PARAM_PERF_MODE_PERF1    = SG5_PERF_MODE_PERF1,
++	SG5_PARAM_PERF_MODE_PERF2    = SG5_PERF_MODE_PERF2,
++
++	__SG5_PARAM_PERF_MODE__START = 0,
++	__SG5_PARAM_PERF_MODE__END   = 4,
++};
++
++struct surface_sid_drvdata {
++	struct device_link *ec_link;
++};
++
++
++static int sg5_ec_perf_mode_get(void)
++{
++	u8 result_buf[8] = { 0 };
++	int status;
++
++	struct surfacegen5_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x02,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surfacegen5_buf result = {
++		.cap = ARRAY_SIZE(result_buf),
++		.len = 0,
++		.data = result_buf,
++	};
++
++	status = surfacegen5_ec_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (result.len != 8) {
++		return -EFAULT;
++	}
++
++	return get_unaligned_le32(&result.data[0]);
++}
++
++static int sg5_ec_perf_mode_set(int perf_mode)
++{
++	u8 payload[4] = { 0 };
++
++	struct surfacegen5_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x03,
++		.snc = 0x00,
++		.cdl = ARRAY_SIZE(payload),
++		.pld = payload,
++	};
++
++	if (perf_mode < __SG5_PERF_MODE__START || perf_mode > __SG5_PERF_MODE__END) {
++		return -EINVAL;
++	}
++
++	put_unaligned_le32(perf_mode, &rqst.pld[0]);
++	return surfacegen5_ec_rqst(&rqst, NULL);
++}
++
++
++static int param_perf_mode_set(const char *val, const struct kernel_param *kp)
++{
++	int perf_mode;
++	int status;
++
++	status = kstrtoint(val, 0, &perf_mode);
++	if (status) {
++		return status;
++	}
++
++	if (perf_mode < __SG5_PARAM_PERF_MODE__START || perf_mode > __SG5_PARAM_PERF_MODE__END) {
++		return -EINVAL;
++	}
++
++	return param_set_int(val, kp);
++}
++
++static const struct kernel_param_ops param_perf_mode_ops = {
++	.set = param_perf_mode_set,
++	.get = param_get_int,
++};
++
++static int param_perf_mode_init = SG5_PARAM_PERF_MODE_AS_IS;
++static int param_perf_mode_exit = SG5_PARAM_PERF_MODE_AS_IS;
++
++module_param_cb(perf_mode_init, &param_perf_mode_ops, &param_perf_mode_init, SG5_PARAM_PERM);
++module_param_cb(perf_mode_exit, &param_perf_mode_ops, &param_perf_mode_exit, SG5_PARAM_PERM);
++
++MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization");
++MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit");
++
++
++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data)
++{
++	int perf_mode;
++
++	perf_mode = sg5_ec_perf_mode_get();
++	if (perf_mode < 0) {
++		dev_err(dev, "failed to get current performance mode: %d", perf_mode);
++		return -EIO;
++	}
++
++	return sprintf(data, "%d\n", perf_mode);
++}
++
++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr,
++                               const char *data, size_t count)
++{
++	int perf_mode;
++	int status;
++
++	status = kstrtoint(data, 0, &perf_mode);
++	if (status) {
++		return status;
++	}
++
++	status = sg5_ec_perf_mode_set(perf_mode);
++	if (status) {
++		return status;
++	}
++
++	// TODO: Should we notify ACPI here?
++	//
++	//       There is a _DSM call described as
++	//           WSID._DSM: Notify DPTF on Slider State change
++	//       which calls
++	//           ODV3 = ToInteger (Arg3)
++	//           Notify(IETM, 0x88)
++	//       IETM is an INT3400 Intel Dynamic Power Performance Management
++	//       device, part of the DPTF framework. From the corresponding
++	//       kernel driver, it looks like event 0x88 is being ignored. Also
++	//       it is currently unknown what the consequecnes of setting ODV3
++	//       are.
++
++	return count;
++}
++
++const static DEVICE_ATTR_RW(perf_mode);
++
++
++static int surfacegen5_acpi_sid_probe(struct platform_device *pdev)
++{
++	struct surface_sid_drvdata *drvdata;
++	struct device_link *ec_link;
++	int status;
++
++	// link to ec
++	ec_link = surfacegen5_ec_consumer_add(&pdev->dev, DL_FLAG_PM_RUNTIME);
++	if (IS_ERR_OR_NULL(ec_link)) {
++		if (PTR_ERR(ec_link) == -ENXIO) {
++			// Defer probe if the _SSH driver has not set up the controller yet.
++			status = -EPROBE_DEFER;
++		} else {
++			status = -EFAULT;
++		}
++
++		goto err_probe_ec_link;
++	}
++
++	// set up driver data
++	drvdata = kzalloc(sizeof(struct surface_sid_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		status = -ENOMEM;
++		goto err_drvdata;
++	}
++	drvdata->ec_link = ec_link;
++	platform_set_drvdata(pdev, drvdata);
++
++	// set initial perf_mode
++	if (param_perf_mode_init != SG5_PARAM_PERF_MODE_AS_IS) {
++		status = sg5_ec_perf_mode_set(param_perf_mode_init);
++		if (status) {
++			goto err_set_perf;
++		}
++	}
++
++	// register perf_mode attribute
++	status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++	if (status) {
++		goto err_sysfs;
++	}
++
++	return 0;
++
++err_sysfs:
++	sg5_ec_perf_mode_set(param_perf_mode_exit);
++err_set_perf:
++	platform_set_drvdata(pdev, NULL);
++	kfree(drvdata);
++err_drvdata:
++	surfacegen5_ec_consumer_remove(ec_link);
++err_probe_ec_link:
++	return status;
++}
++
++static int surfacegen5_acpi_sid_remove(struct platform_device *pdev)
++{
++	struct surface_sid_drvdata *drvdata = platform_get_drvdata(pdev);
++
++	// remove perf_mode attribute
++	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++
++	// set exit perf_mode
++	sg5_ec_perf_mode_set(param_perf_mode_exit);
++
++	// remove consumer and clean up
++	surfacegen5_ec_consumer_remove(drvdata->ec_link);
++	platform_set_drvdata(pdev, NULL);
++	kfree(drvdata);
++
++	return 0;
++}
++
++
++static const struct acpi_device_id surfacegen5_acpi_sid_match[] = {
++	{ "MSHW0107", 0 },	/* Surface Book 2 */
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_sid_match);
++
++struct platform_driver surfacegen5_acpi_sid = {
++	.probe = surfacegen5_acpi_sid_probe,
++	.remove = surfacegen5_acpi_sid_remove,
++	.driver = {
++		.name = "surfacegen5_acpi_sid",
++		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_sid_match),
++	},
++};
++
++
++inline int surfacegen5_acpi_sid_register(void)
++{
++	return platform_driver_register(&surfacegen5_acpi_sid);
++}
++
++inline void surfacegen5_acpi_sid_unregister(void)
++{
++	platform_driver_unregister(&surfacegen5_acpi_sid);
++}
++
++#else /* CONFIG_SURFACE_ACPI_SID */
++
++inline int surfacegen5_acpi_sid_register(void)
++{
++	return 0;
++}
++
++inline void surfacegen5_acpi_sid_unregister(void)
++{
++}
++
++#endif /* CONFIG_SURFACE_ACPI_SID */
++
++
++/*************************************************************************
 + * Module initialization
 + */
 +
@@ -3702,8 +3999,15 @@ index 000000000000..9d11028562c9
 +		goto err_dtx;
 +	}
 +
++	status = surfacegen5_acpi_sid_register();
++	if (status) {
++		goto err_sid;
++	}
++
 +	return 0;
 +
++err_sid:
++	surfacegen5_acpi_sid_unregister();
 +err_dtx:
 +	surfacegen5_acpi_vhf_unregister();
 +err_vhf:
@@ -3716,6 +4020,7 @@ index 000000000000..9d11028562c9
 +
 +void __exit surface_acpi_exit(void)
 +{
++	surfacegen5_acpi_sid_unregister();
 +	surfacegen5_acpi_dtx_unregister();
 +	surfacegen5_acpi_vhf_unregister();
 +	surfacegen5_acpi_san_unregister();

+ 314 - 9
patches/5.0/0001-surface-acpi.patch

@@ -1,16 +1,16 @@
-From 83c4775597f3ab39e64a06242b596b57718d2dac Mon Sep 17 00:00:00 2001
+From 20b5a1e18431a908f82b9422f6e54979b73900d5 Mon Sep 17 00:00:00 2001
 From: qzed <qzed@users.noreply.github.com>
-Date: Thu, 4 Apr 2019 22:50:25 +0200
+Date: Sun, 21 Apr 2019 15:18:23 +0200
 Subject: [PATCH 01/11] surface-acpi
 
 ---
  drivers/acpi/acpica/dsopcode.c      |    2 +-
  drivers/acpi/acpica/exfield.c       |   12 +-
- drivers/platform/x86/Kconfig        |   84 +
+ drivers/platform/x86/Kconfig        |   97 +
  drivers/platform/x86/Makefile       |    1 +
- drivers/platform/x86/surface_acpi.c | 3536 +++++++++++++++++++++++++++
+ drivers/platform/x86/surface_acpi.c | 3828 +++++++++++++++++++++++++++
  drivers/tty/serdev/core.c           |   90 +-
- 6 files changed, 3719 insertions(+), 6 deletions(-)
+ 6 files changed, 4024 insertions(+), 6 deletions(-)
  create mode 100644 drivers/platform/x86/surface_acpi.c
 
 diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c
@@ -59,10 +59,10 @@ index e5798f15793a..55abd9e035a0 100644
  		buffer_desc = acpi_ut_create_buffer_object(buffer_length);
  		if (!buffer_desc) {
 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index b5e9db85e881..3d8c7a7f13e5 100644
+index b5e9db85e881..a00b5f6b23ce 100644
 --- a/drivers/platform/x86/Kconfig
 +++ b/drivers/platform/x86/Kconfig
-@@ -622,6 +622,90 @@ config THINKPAD_ACPI_HOTKEY_POLL
+@@ -622,6 +622,103 @@ config THINKPAD_ACPI_HOTKEY_POLL
  	  If you are not sure, say Y here.  The driver enables polling only if
  	  it is strictly necessary to do so.
  
@@ -149,6 +149,19 @@ index b5e9db85e881..3d8c7a7f13e5 100644
 +	  upon device mode change.
 +
 +	  If you are not sure, say Y here.
++
++config SURFACE_ACPI_SID
++	bool "Surface Platform Integration Driver"
++	depends on SURFACE_ACPI_SSH
++	default y
++	---help---
++	  Surface Platform Integration Driver for the Microsoft Surface Devices.
++	  Currently only supports the Surface Book 2. This driver provides suport
++	  for setting performance-modes via the perf_mode sysfs attribute.
++	  Performance-modes directly influence the fan-profile of the device,
++	  allowing to choose between higher performance or quieter operation.
++
++	  If you are not sure, say Y here.
 +
  config SENSORS_HDAPS
  	tristate "Thinkpad Hard Drive Active Protection System (hdaps)"
@@ -167,10 +180,10 @@ index ce8da260c223..8412fe7a169d 100644
  obj-$(CONFIG_FUJITSU_TABLET)	+= fujitsu-tablet.o
 diff --git a/drivers/platform/x86/surface_acpi.c b/drivers/platform/x86/surface_acpi.c
 new file mode 100644
-index 000000000000..9d11028562c9
+index 000000000000..ab793f6774a0
 --- /dev/null
 +++ b/drivers/platform/x86/surface_acpi.c
-@@ -0,0 +1,3536 @@
+@@ -0,0 +1,3828 @@
 +#include <asm/unaligned.h>
 +#include <linux/acpi.h>
 +#include <linux/completion.h>
@@ -186,6 +199,8 @@ index 000000000000..9d11028562c9
 +#include <linux/kernel.h>
 +#include <linux/kfifo.h>
 +#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
 +#include <linux/mutex.h>
 +#include <linux/platform_device.h>
 +#include <linux/pm.h>
@@ -203,6 +218,8 @@ index 000000000000..9d11028562c9
 +#define USB_DEVICE_ID_MS_VHF				0xf001
 +#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION	0x0922
 +
++#define SG5_PARAM_PERM		(S_IRUGO | S_IWUSR)
++
 +
 +/*************************************************************************
 + * Surface Serial Hub driver (cross-driver interface)
@@ -3654,6 +3671,286 @@ index 000000000000..9d11028562c9
 +
 +
 +/*************************************************************************
++ * Surface Platform Integration Driver
++ */
++
++#ifdef CONFIG_SURFACE_ACPI_SID
++
++enum sg5_perf_mode {
++	SG5_PERF_MODE_NORMAL   = 1,
++	SG5_PERF_MODE_BATTERY  = 2,
++	SG5_PERF_MODE_PERF1    = 3,
++	SG5_PERF_MODE_PERF2    = 4,
++
++	__SG5_PERF_MODE__START = 1,
++	__SG5_PERF_MODE__END   = 4,
++};
++
++enum sg5_param_perf_mode {
++	SG5_PARAM_PERF_MODE_AS_IS    = 0,
++	SG5_PARAM_PERF_MODE_NORMAL   = SG5_PERF_MODE_NORMAL,
++	SG5_PARAM_PERF_MODE_BATTERY  = SG5_PERF_MODE_BATTERY,
++	SG5_PARAM_PERF_MODE_PERF1    = SG5_PERF_MODE_PERF1,
++	SG5_PARAM_PERF_MODE_PERF2    = SG5_PERF_MODE_PERF2,
++
++	__SG5_PARAM_PERF_MODE__START = 0,
++	__SG5_PARAM_PERF_MODE__END   = 4,
++};
++
++struct surface_sid_drvdata {
++	struct device_link *ec_link;
++};
++
++
++static int sg5_ec_perf_mode_get(void)
++{
++	u8 result_buf[8] = { 0 };
++	int status;
++
++	struct surfacegen5_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x02,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surfacegen5_buf result = {
++		.cap = ARRAY_SIZE(result_buf),
++		.len = 0,
++		.data = result_buf,
++	};
++
++	status = surfacegen5_ec_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (result.len != 8) {
++		return -EFAULT;
++	}
++
++	return get_unaligned_le32(&result.data[0]);
++}
++
++static int sg5_ec_perf_mode_set(int perf_mode)
++{
++	u8 payload[4] = { 0 };
++
++	struct surfacegen5_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x03,
++		.snc = 0x00,
++		.cdl = ARRAY_SIZE(payload),
++		.pld = payload,
++	};
++
++	if (perf_mode < __SG5_PERF_MODE__START || perf_mode > __SG5_PERF_MODE__END) {
++		return -EINVAL;
++	}
++
++	put_unaligned_le32(perf_mode, &rqst.pld[0]);
++	return surfacegen5_ec_rqst(&rqst, NULL);
++}
++
++
++static int param_perf_mode_set(const char *val, const struct kernel_param *kp)
++{
++	int perf_mode;
++	int status;
++
++	status = kstrtoint(val, 0, &perf_mode);
++	if (status) {
++		return status;
++	}
++
++	if (perf_mode < __SG5_PARAM_PERF_MODE__START || perf_mode > __SG5_PARAM_PERF_MODE__END) {
++		return -EINVAL;
++	}
++
++	return param_set_int(val, kp);
++}
++
++static const struct kernel_param_ops param_perf_mode_ops = {
++	.set = param_perf_mode_set,
++	.get = param_get_int,
++};
++
++static int param_perf_mode_init = SG5_PARAM_PERF_MODE_AS_IS;
++static int param_perf_mode_exit = SG5_PARAM_PERF_MODE_AS_IS;
++
++module_param_cb(perf_mode_init, &param_perf_mode_ops, &param_perf_mode_init, SG5_PARAM_PERM);
++module_param_cb(perf_mode_exit, &param_perf_mode_ops, &param_perf_mode_exit, SG5_PARAM_PERM);
++
++MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization");
++MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit");
++
++
++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data)
++{
++	int perf_mode;
++
++	perf_mode = sg5_ec_perf_mode_get();
++	if (perf_mode < 0) {
++		dev_err(dev, "failed to get current performance mode: %d", perf_mode);
++		return -EIO;
++	}
++
++	return sprintf(data, "%d\n", perf_mode);
++}
++
++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr,
++                               const char *data, size_t count)
++{
++	int perf_mode;
++	int status;
++
++	status = kstrtoint(data, 0, &perf_mode);
++	if (status) {
++		return status;
++	}
++
++	status = sg5_ec_perf_mode_set(perf_mode);
++	if (status) {
++		return status;
++	}
++
++	// TODO: Should we notify ACPI here?
++	//
++	//       There is a _DSM call described as
++	//           WSID._DSM: Notify DPTF on Slider State change
++	//       which calls
++	//           ODV3 = ToInteger (Arg3)
++	//           Notify(IETM, 0x88)
++	//       IETM is an INT3400 Intel Dynamic Power Performance Management
++	//       device, part of the DPTF framework. From the corresponding
++	//       kernel driver, it looks like event 0x88 is being ignored. Also
++	//       it is currently unknown what the consequecnes of setting ODV3
++	//       are.
++
++	return count;
++}
++
++const static DEVICE_ATTR_RW(perf_mode);
++
++
++static int surfacegen5_acpi_sid_probe(struct platform_device *pdev)
++{
++	struct surface_sid_drvdata *drvdata;
++	struct device_link *ec_link;
++	int status;
++
++	// link to ec
++	ec_link = surfacegen5_ec_consumer_add(&pdev->dev, DL_FLAG_PM_RUNTIME);
++	if (IS_ERR_OR_NULL(ec_link)) {
++		if (PTR_ERR(ec_link) == -ENXIO) {
++			// Defer probe if the _SSH driver has not set up the controller yet.
++			status = -EPROBE_DEFER;
++		} else {
++			status = -EFAULT;
++		}
++
++		goto err_probe_ec_link;
++	}
++
++	// set up driver data
++	drvdata = kzalloc(sizeof(struct surface_sid_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		status = -ENOMEM;
++		goto err_drvdata;
++	}
++	drvdata->ec_link = ec_link;
++	platform_set_drvdata(pdev, drvdata);
++
++	// set initial perf_mode
++	if (param_perf_mode_init != SG5_PARAM_PERF_MODE_AS_IS) {
++		status = sg5_ec_perf_mode_set(param_perf_mode_init);
++		if (status) {
++			goto err_set_perf;
++		}
++	}
++
++	// register perf_mode attribute
++	status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++	if (status) {
++		goto err_sysfs;
++	}
++
++	return 0;
++
++err_sysfs:
++	sg5_ec_perf_mode_set(param_perf_mode_exit);
++err_set_perf:
++	platform_set_drvdata(pdev, NULL);
++	kfree(drvdata);
++err_drvdata:
++	surfacegen5_ec_consumer_remove(ec_link);
++err_probe_ec_link:
++	return status;
++}
++
++static int surfacegen5_acpi_sid_remove(struct platform_device *pdev)
++{
++	struct surface_sid_drvdata *drvdata = platform_get_drvdata(pdev);
++
++	// remove perf_mode attribute
++	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++
++	// set exit perf_mode
++	sg5_ec_perf_mode_set(param_perf_mode_exit);
++
++	// remove consumer and clean up
++	surfacegen5_ec_consumer_remove(drvdata->ec_link);
++	platform_set_drvdata(pdev, NULL);
++	kfree(drvdata);
++
++	return 0;
++}
++
++
++static const struct acpi_device_id surfacegen5_acpi_sid_match[] = {
++	{ "MSHW0107", 0 },	/* Surface Book 2 */
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_sid_match);
++
++struct platform_driver surfacegen5_acpi_sid = {
++	.probe = surfacegen5_acpi_sid_probe,
++	.remove = surfacegen5_acpi_sid_remove,
++	.driver = {
++		.name = "surfacegen5_acpi_sid",
++		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_sid_match),
++	},
++};
++
++
++inline int surfacegen5_acpi_sid_register(void)
++{
++	return platform_driver_register(&surfacegen5_acpi_sid);
++}
++
++inline void surfacegen5_acpi_sid_unregister(void)
++{
++	platform_driver_unregister(&surfacegen5_acpi_sid);
++}
++
++#else /* CONFIG_SURFACE_ACPI_SID */
++
++inline int surfacegen5_acpi_sid_register(void)
++{
++	return 0;
++}
++
++inline void surfacegen5_acpi_sid_unregister(void)
++{
++}
++
++#endif /* CONFIG_SURFACE_ACPI_SID */
++
++
++/*************************************************************************
 + * Module initialization
 + */
 +
@@ -3681,8 +3978,15 @@ index 000000000000..9d11028562c9
 +		goto err_dtx;
 +	}
 +
++	status = surfacegen5_acpi_sid_register();
++	if (status) {
++		goto err_sid;
++	}
++
 +	return 0;
 +
++err_sid:
++	surfacegen5_acpi_sid_unregister();
 +err_dtx:
 +	surfacegen5_acpi_vhf_unregister();
 +err_vhf:
@@ -3695,6 +3999,7 @@ index 000000000000..9d11028562c9
 +
 +void __exit surface_acpi_exit(void)
 +{
++	surfacegen5_acpi_sid_unregister();
 +	surfacegen5_acpi_dtx_unregister();
 +	surfacegen5_acpi_vhf_unregister();
 +	surfacegen5_acpi_san_unregister();