Changes: - Fix a bug preventing the Surface Serial Hub driver from probing successfully under certain conditions. Links: - SAM:f973e273e1
- kernel:ac0c9d22ca
268 lines
7.2 KiB
Diff
268 lines
7.2 KiB
Diff
From 1d8ed7b3b4944c8f3abf1a6094050b1b822a2ac0 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sat, 25 Jul 2020 17:19:53 +0200
|
|
Subject: [PATCH 6/7] surface-sam-over-hid
|
|
|
|
---
|
|
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
|
|
|
|
diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
|
|
index ce70b5288472..5df647c4d9a5 100644
|
|
--- a/drivers/i2c/i2c-core-acpi.c
|
|
+++ b/drivers/i2c/i2c-core-acpi.c
|
|
@@ -582,6 +582,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,
|
|
@@ -683,6 +705,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);
|
|
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
|
|
index 348c795019fa..5495e3702f65 100644
|
|
--- a/drivers/platform/x86/Kconfig
|
|
+++ b/drivers/platform/x86/Kconfig
|
|
@@ -1216,6 +1216,13 @@ config SURFACE_3_POWER_OPREGION
|
|
Select this option to enable support for ACPI operation
|
|
region of the Surface 3 battery platform driver.
|
|
|
|
+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 INTEL_PUNIT_IPC
|
|
tristate "Intel P-Unit IPC Driver"
|
|
---help---
|
|
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
|
|
index 6dd955ad9bf1..b3e0a2c6924b 100644
|
|
--- a/drivers/platform/x86/Makefile
|
|
+++ b/drivers/platform/x86/Makefile
|
|
@@ -86,6 +86,7 @@ obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o
|
|
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
|
obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o
|
|
obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
|
+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += sb1_dgpu_sw.o
|
|
obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o
|
|
obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o
|
|
obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \
|
|
diff --git a/drivers/platform/x86/sb1_dgpu_sw.c b/drivers/platform/x86/sb1_dgpu_sw.c
|
|
new file mode 100644
|
|
index 000000000000..8c66ed5110fd
|
|
--- /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");
|
|
--
|
|
2.28.0
|
|
|