Changes: - Update patches to v5.11.7 - SAM: - Small code cleanup Links: - kernel:f94094df0a
- SAM:53fbf8d1f8
335 lines
10 KiB
Diff
335 lines
10 KiB
Diff
From 68cd14de7a5df97b8dba160a116fe5596c84fee4 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sat, 25 Jul 2020 17:19:53 +0200
|
|
Subject: [PATCH] i2c: acpi: Implement RawBytes read access
|
|
|
|
Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C
|
|
device via a generic serial bus operation region and RawBytes read
|
|
access. On the Surface Book 1, this access is required to turn on (and
|
|
off) the discrete GPU.
|
|
|
|
Multiple things are to note here:
|
|
|
|
a) The RawBytes access is device/driver dependent. The ACPI
|
|
specification states:
|
|
|
|
> Raw accesses assume that the writer has knowledge of the bus that
|
|
> the access is made over and the device that is being accessed. The
|
|
> protocol may only ensure that the buffer is transmitted to the
|
|
> appropriate driver, but the driver must be able to interpret the
|
|
> buffer to communicate to a register.
|
|
|
|
Thus this implementation may likely not work on other devices
|
|
accessing I2C via the RawBytes accessor type.
|
|
|
|
b) The MSHW0030 I2C device is an HID-over-I2C device which seems to
|
|
serve multiple functions:
|
|
|
|
1. It is the main access point for the legacy-type Surface Aggregator
|
|
Module (also referred to as SAM-over-HID, as opposed to the newer
|
|
SAM-over-SSH/UART). It has currently not been determined on how
|
|
support for the legacy SAM should be implemented. Likely via a
|
|
custom HID driver.
|
|
|
|
2. It seems to serve as the HID device for the Integrated Sensor Hub.
|
|
This might complicate matters with regards to implementing a
|
|
SAM-over-HID driver required by legacy SAM.
|
|
|
|
In light of this, the simplest approach has been chosen for now.
|
|
However, it may make more sense regarding breakage and compatibility to
|
|
either provide functionality for replacing or enhancing the default
|
|
operation region handler via some additional API functions, or even to
|
|
completely blacklist MSHW0030 from the I2C core and provide a custom
|
|
driver for it.
|
|
|
|
Replacing/enhancing the default operation region handler would, however,
|
|
either require some sort of secondary driver and access point for it,
|
|
from which the new API functions would be called and the new handler
|
|
(part) would be installed, or hard-coding them via some sort of
|
|
quirk-like interface into the I2C core.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam-over-hid
|
|
---
|
|
drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++
|
|
1 file changed, 35 insertions(+)
|
|
|
|
diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
|
|
index 37c510d9347a..aed579942436 100644
|
|
--- 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);
|
|
--
|
|
2.30.2
|
|
|
|
From c6612d79e80e2fd300ef5ca9d35ddc0c1658bdcd Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sat, 13 Feb 2021 16:41:18 +0100
|
|
Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch
|
|
|
|
Add driver exposing the discrete GPU power-switch of the Microsoft
|
|
Surface Book 1 to user-space.
|
|
|
|
On the Surface Book 1, the dGPU power is controlled via the Surface
|
|
System Aggregator Module (SAM). The specific SAM-over-HID command for
|
|
this is exposed via ACPI. This module provides a simple driver exposing
|
|
the ACPI call via a sysfs parameter to user-space, so that users can
|
|
easily power-on/-off the dGPU.
|
|
|
|
Patchset: surface-sam-over-hid
|
|
---
|
|
drivers/platform/surface/Kconfig | 7 +
|
|
drivers/platform/surface/Makefile | 1 +
|
|
.../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++
|
|
3 files changed, 170 insertions(+)
|
|
create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c
|
|
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
index 2c941cdac9ee..b5dc9148066c 100644
|
|
--- a/drivers/platform/surface/Kconfig
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
@@ -41,6 +41,13 @@ config SURFACE_3_POWER_OPREGION
|
|
This driver provides 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 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 SURFACE_GPE
|
|
tristate "Surface GPE/Lid Support Driver"
|
|
depends on DMI
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
index cedfb027ded1..3d5fa0daa56b 100644
|
|
--- a/drivers/platform/surface/Makefile
|
|
+++ b/drivers/platform/surface/Makefile
|
|
@@ -7,5 +7,6 @@
|
|
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_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
|
|
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
|
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
|
diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c
|
|
new file mode 100644
|
|
index 000000000000..8b816ed8f35c
|
|
--- /dev/null
|
|
+++ b/drivers/platform/surface/surfacebook1_dgpu_switch.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 = "surfacebook1_dgpu_switch",
|
|
+ .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.30.2
|
|
|