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).
This commit is contained in:
qzed 2019-04-21 15:37:02 +02:00
parent cd52f74d20
commit 4e2e009dc7
4 changed files with 632 additions and 20 deletions

View file

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

View file

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

View file

@ -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();

View file

@ -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();