2020-10-01 16:04:03 +00:00
|
|
|
From eeca95aae2ca982584ff3fea350e0db1b46db63d Mon Sep 17 00:00:00 2001
|
2020-08-03 19:02:06 +00:00
|
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
Date: Sat, 25 Jul 2020 17:19:53 +0200
|
2020-08-18 04:08:22 +00:00
|
|
|
Subject: [PATCH 5/6] surface-sam-over-hid
|
2020-08-03 19:02:06 +00:00
|
|
|
|
|
|
|
---
|
2020-09-08 14:37:03 +00:00
|
|
|
drivers/i2c/i2c-core-acpi.c | 35 +++++++
|
|
|
|
drivers/platform/x86/Kconfig | 7 ++
|
|
|
|
drivers/platform/x86/Makefile | 1 +
|
|
|
|
drivers/platform/x86/sb1_dgpu_sw.c | 162 +++++++++++++++++++++++++++++
|
|
|
|
4 files changed, 205 insertions(+)
|
|
|
|
create mode 100644 drivers/platform/x86/sb1_dgpu_sw.c
|
2020-08-03 19:02:06 +00:00
|
|
|
|
|
|
|
diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
|
2020-09-12 14:12:52 +00:00
|
|
|
index 2ade99b105b9..60b9cb51d5f7 100644
|
2020-08-03 19:02:06 +00:00
|
|
|
--- a/drivers/i2c/i2c-core-acpi.c
|
|
|
|
+++ b/drivers/i2c/i2c-core-acpi.c
|
|
|
|
@@ -574,6 +574,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client,
|
|
|
|
return (ret == 1) ? 0 : -EIO;
|
|
|
|
}
|
|
|
|
|
|
|
|
+static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client,
|
|
|
|
+ u8 *data, u8 data_len)
|
|
|
|
+{
|
|
|
|
+ struct i2c_msg msgs[1];
|
|
|
|
+ int ret = AE_OK;
|
|
|
|
+
|
|
|
|
+ msgs[0].addr = client->addr;
|
|
|
|
+ msgs[0].flags = client->flags;
|
|
|
|
+ msgs[0].len = data_len + 1;
|
|
|
|
+ msgs[0].buf = data;
|
|
|
|
+
|
|
|
|
+ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
|
|
|
+
|
|
|
|
+ if (ret < 0) {
|
|
|
|
+ dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret);
|
|
|
|
+ return ret;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 1 transfer must have completed successfully */
|
|
|
|
+ return (ret == 1) ? 0 : -EIO;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
static acpi_status
|
|
|
|
i2c_acpi_space_handler(u32 function, acpi_physical_address command,
|
|
|
|
u32 bits, u64 *value64,
|
|
|
|
@@ -675,6 +697,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command,
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
+ case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES:
|
|
|
|
+ if (action == ACPI_READ) {
|
|
|
|
+ dev_warn(&adapter->dev,
|
|
|
|
+ "protocol 0x%02x not supported for client 0x%02x\n",
|
|
|
|
+ accessor_type, client->addr);
|
|
|
|
+ ret = AE_BAD_PARAMETER;
|
|
|
|
+ goto err;
|
|
|
|
+ } else {
|
|
|
|
+ status = acpi_gsb_i2c_write_raw_bytes(client,
|
|
|
|
+ gsb->data, info->access_length);
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
default:
|
|
|
|
dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n",
|
|
|
|
accessor_type, client->addr);
|
2020-09-08 14:37:03 +00:00
|
|
|
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
|
2020-09-12 14:12:52 +00:00
|
|
|
index 0581a54cf562..14db2795ff63 100644
|
2020-09-08 14:37:03 +00:00
|
|
|
--- a/drivers/platform/x86/Kconfig
|
|
|
|
+++ b/drivers/platform/x86/Kconfig
|
|
|
|
@@ -879,6 +879,13 @@ config SURFACE_PRO3_BUTTON
|
|
|
|
help
|
|
|
|
This driver handles the power/home/volume buttons on the Microsoft Surface Pro 3/4 tablet.
|
|
|
|
|
|
|
|
+config SURFACE_BOOK1_DGPU_SWITCH
|
|
|
|
+ tristate "Surface Book 1 dGPU Switch Driver"
|
|
|
|
+ depends on ACPI && SYSFS
|
|
|
|
+ help
|
|
|
|
+ This driver provides a sysfs switch to set the power-state of the
|
|
|
|
+ discrete GPU found on the Microsoft Surface Book 1.
|
|
|
|
+
|
|
|
|
config MSI_LAPTOP
|
|
|
|
tristate "MSI Laptop Extras"
|
|
|
|
depends on ACPI
|
|
|
|
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
|
2020-09-12 14:12:52 +00:00
|
|
|
index 2b85852a1a87..5156523b5863 100644
|
2020-09-08 14:37:03 +00:00
|
|
|
--- a/drivers/platform/x86/Makefile
|
|
|
|
+++ b/drivers/platform/x86/Makefile
|
|
|
|
@@ -85,6 +85,7 @@ obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o
|
|
|
|
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
|
|
|
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
|
|
|
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
|
|
|
+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o
|
|
|
|
|
|
|
|
# MSI
|
|
|
|
obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
|
|
|
|
diff --git a/drivers/platform/x86/sb1_dgpu_sw.c b/drivers/platform/x86/sb1_dgpu_sw.c
|
|
|
|
new file mode 100644
|
2020-09-12 14:12:52 +00:00
|
|
|
index 000000000000..8c66ed5110fd
|
2020-09-08 14:37:03 +00:00
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/platform/x86/sb1_dgpu_sw.c
|
|
|
|
@@ -0,0 +1,162 @@
|
|
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
+
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/acpi.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+#ifdef pr_fmt
|
|
|
|
+#undef pr_fmt
|
|
|
|
+#endif
|
|
|
|
+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4,
|
|
|
|
+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35);
|
|
|
|
+
|
|
|
|
+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI"
|
|
|
|
+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON"
|
|
|
|
+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF"
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static int sb1_dgpu_sw_dsmcall(void)
|
|
|
|
+{
|
|
|
|
+ union acpi_object *ret;
|
|
|
|
+ acpi_handle handle;
|
|
|
|
+ acpi_status status;
|
|
|
|
+
|
|
|
|
+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle);
|
|
|
|
+ if (status)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER);
|
|
|
|
+ if (!ret)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ ACPI_FREE(ret);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int sb1_dgpu_sw_hgon(void)
|
|
|
|
+{
|
|
|
|
+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
|
|
|
|
+ acpi_status status;
|
|
|
|
+
|
|
|
|
+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf);
|
|
|
|
+ if (status) {
|
|
|
|
+ pr_err("failed to run HGON: %d\n", status);
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (buf.pointer)
|
|
|
|
+ ACPI_FREE(buf.pointer);
|
|
|
|
+
|
|
|
|
+ pr_info("turned-on dGPU via HGON\n");
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int sb1_dgpu_sw_hgof(void)
|
|
|
|
+{
|
|
|
|
+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
|
|
|
|
+ acpi_status status;
|
|
|
|
+
|
|
|
|
+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf);
|
|
|
|
+ if (status) {
|
|
|
|
+ pr_err("failed to run HGOF: %d\n", status);
|
|
|
|
+ return -EINVAL;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (buf.pointer)
|
|
|
|
+ ACPI_FREE(buf.pointer);
|
|
|
|
+
|
|
|
|
+ pr_info("turned-off dGPU via HGOF\n");
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
+ const char *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ int status, value;
|
|
|
|
+
|
|
|
|
+ status = kstrtoint(buf, 0, &value);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (value != 1)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ status = sb1_dgpu_sw_dsmcall();
|
|
|
|
+
|
|
|
|
+ return status < 0 ? status : len;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr,
|
|
|
|
+ const char *buf, size_t len)
|
|
|
|
+{
|
|
|
|
+ bool power;
|
|
|
|
+ int status;
|
|
|
|
+
|
|
|
|
+ status = kstrtobool(buf, &power);
|
|
|
|
+ if (status < 0)
|
|
|
|
+ return status;
|
|
|
|
+
|
|
|
|
+ if (power)
|
|
|
|
+ status = sb1_dgpu_sw_hgon();
|
|
|
|
+ else
|
|
|
|
+ status = sb1_dgpu_sw_hgof();
|
|
|
|
+
|
|
|
|
+ return status < 0 ? status : len;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static DEVICE_ATTR_WO(dgpu_dsmcall);
|
|
|
|
+static DEVICE_ATTR_WO(dgpu_power);
|
|
|
|
+
|
|
|
|
+static struct attribute *sb1_dgpu_sw_attrs[] = {
|
|
|
|
+ &dev_attr_dgpu_dsmcall.attr,
|
|
|
|
+ &dev_attr_dgpu_power.attr,
|
|
|
|
+ NULL,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct attribute_group sb1_dgpu_sw_attr_group = {
|
|
|
|
+ .attrs = sb1_dgpu_sw_attrs,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+static int sb1_dgpu_sw_probe(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int sb1_dgpu_sw_remove(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * The dGPU power seems to be actually handled by MSHW0040. However, that is
|
|
|
|
+ * also the power-/volume-button device with a mainline driver. So let's use
|
|
|
|
+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device.
|
|
|
|
+ */
|
|
|
|
+static const struct acpi_device_id sb1_dgpu_sw_match[] = {
|
|
|
|
+ { "MSHW0041", },
|
|
|
|
+ { },
|
|
|
|
+};
|
|
|
|
+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match);
|
|
|
|
+
|
|
|
|
+static struct platform_driver sb1_dgpu_sw = {
|
|
|
|
+ .probe = sb1_dgpu_sw_probe,
|
|
|
|
+ .remove = sb1_dgpu_sw_remove,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "sb1_dgpu_sw",
|
|
|
|
+ .acpi_match_table = sb1_dgpu_sw_match,
|
|
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+module_platform_driver(sb1_dgpu_sw);
|
|
|
|
+
|
|
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
|
|
+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1");
|
|
|
|
+MODULE_LICENSE("GPL");
|
2020-08-03 19:02:06 +00:00
|
|
|
--
|
2020-08-18 04:08:22 +00:00
|
|
|
2.28.0
|
2020-08-03 19:02:06 +00:00
|
|
|
|