From 4e2e009dc7a99a0be2cbb7e8869d1734fca77541 Mon Sep 17 00:00:00 2001 From: qzed Date: Sun, 21 Apr 2019 15:37:02 +0200 Subject: [PATCH] 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). --- configs/4.19/config | 1 + configs/5.0/config | 1 + patches/4.19/0001-surface-acpi.patch | 327 ++++++++++++++++++++++++++- patches/5.0/0001-surface-acpi.patch | 323 +++++++++++++++++++++++++- 4 files changed, 632 insertions(+), 20 deletions(-) diff --git a/configs/4.19/config b/configs/4.19/config index 296878624..ca8a5a41a 100644 --- a/configs/4.19/config +++ b/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 diff --git a/configs/5.0/config b/configs/5.0/config index ce8856f58..9c0b3dea7 100644 --- a/configs/5.0/config +++ b/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 diff --git a/patches/4.19/0001-surface-acpi.patch b/patches/4.19/0001-surface-acpi.patch index 9df9cc87f..31f9f7509 100644 --- a/patches/4.19/0001-surface-acpi.patch +++ b/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 -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 +#include +#include @@ -207,6 +220,8 @@ index 000000000000..9d11028562c9 +#include +#include +#include ++#include ++#include +#include +#include +#include @@ -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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SG5_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_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(); diff --git a/patches/5.0/0001-surface-acpi.patch b/patches/5.0/0001-surface-acpi.patch index 1884d46f9..eb9acc685 100644 --- a/patches/5.0/0001-surface-acpi.patch +++ b/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 -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 +#include +#include @@ -186,6 +199,8 @@ index 000000000000..9d11028562c9 +#include +#include +#include ++#include ++#include +#include +#include +#include @@ -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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SG5_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_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();