Browse Source

Update surface-acpi

- Rename Kconfig options (SURFACE_ACPI_XXX -> SURFACE_SAM_XXX)
- Rename identifiers/functions.
- Split into multiple modules (surface_sam_ssh, surface_sam_san, ...)
Maximilian Luz 5 năm trước cách đây
mục cha
commit
3172322f64

+ 3009 - 3073
patches/4.19/0001-surface-acpi.patch

@@ -1,17 +1,31 @@
-From bcb840821cd18b10375f24efb5b44f7f5adcc535 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Wed, 2 Oct 2019 22:28:37 +0200
+From 789b866d520178ff70caef9ad977290305cd27db Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
+Date: Mon, 26 Aug 2019 01:15:40 +0200
 Subject: [PATCH 01/12] surface-acpi
 
 ---
- drivers/acpi/acpica/dsopcode.c      |    2 +-
- drivers/acpi/acpica/exfield.c       |   26 +-
- drivers/platform/x86/Kconfig        |   97 +
- drivers/platform/x86/Makefile       |    1 +
- drivers/platform/x86/surface_acpi.c | 4010 +++++++++++++++++++++++++++
- drivers/tty/serdev/core.c           |  111 +-
- 6 files changed, 4218 insertions(+), 29 deletions(-)
- create mode 100644 drivers/platform/x86/surface_acpi.c
+ drivers/acpi/acpica/dsopcode.c                |    2 +-
+ drivers/acpi/acpica/exfield.c                 |   26 +-
+ drivers/platform/x86/Kconfig                  |    2 +
+ drivers/platform/x86/Makefile                 |    1 +
+ drivers/platform/x86/surface_sam/Kconfig      |  104 +
+ drivers/platform/x86/surface_sam/Makefile     |    5 +
+ .../x86/surface_sam/surface_sam_dtx.c         |  620 ++++++
+ .../x86/surface_sam/surface_sam_san.c         |  708 +++++++
+ .../x86/surface_sam/surface_sam_sid.c         |  483 +++++
+ .../x86/surface_sam/surface_sam_ssh.c         | 1691 +++++++++++++++++
+ .../x86/surface_sam/surface_sam_ssh.h         |   91 +
+ .../x86/surface_sam/surface_sam_vhf.c         |  286 +++
+ drivers/tty/serdev/core.c                     |  111 +-
+ 13 files changed, 4101 insertions(+), 29 deletions(-)
+ create mode 100644 drivers/platform/x86/surface_sam/Kconfig
+ create mode 100644 drivers/platform/x86/surface_sam/Makefile
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_dtx.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.h
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_vhf.c
 
 diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c
 index 2f4641e5ecde..beb22d7e245e 100644
@@ -80,28 +94,56 @@ index b272c329d45d..cf547883a993 100644
  		} else {	/* IPMI */
  
 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index 1e2524de6a63..9a47363a0c30 100644
+index 1e2524de6a63..ea17f993320e 100644
 --- a/drivers/platform/x86/Kconfig
 +++ b/drivers/platform/x86/Kconfig
-@@ -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.
+@@ -1243,6 +1243,8 @@ config INTEL_ATOMISP2_PM
+ 	  To compile this driver as a module, choose M here: the module
+ 	  will be called intel_atomisp2_pm.
  
-+config SURFACE_ACPI
-+	depends on ACPI
-+	tristate "Microsoft Surface ACPI/Platform Drivers"
-+	---help---
-+	  ACPI and platform drivers for Microsoft Surface devices.
++source "drivers/platform/x86/surface_sam/Kconfig"
 +
-+config SURFACE_ACPI_SSH
-+	bool "Surface Serial Hub Driver"
-+	depends on SURFACE_ACPI
+ endif # X86_PLATFORM_DEVICES
+ 
+ config PMC_ATOM
+diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
+index dc29af4d8e2f..ddc2fbfaf110 100644
+--- a/drivers/platform/x86/Makefile
++++ b/drivers/platform/x86/Makefile
+@@ -93,3 +93,4 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o
+ obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN)	+= intel_chtdc_ti_pwrbtn.o
+ obj-$(CONFIG_I2C_MULTI_INSTANTIATE)	+= i2c-multi-instantiate.o
+ obj-$(CONFIG_INTEL_ATOMISP2_PM)	+= intel_atomisp2_pm.o
++obj-$(CONFIG_SURFACE_SAM)	+= surface_sam/
+diff --git a/drivers/platform/x86/surface_sam/Kconfig b/drivers/platform/x86/surface_sam/Kconfig
+new file mode 100644
+index 000000000000..3ef7d69a214f
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/Kconfig
+@@ -0,0 +1,104 @@
++menuconfig SURFACE_SAM
++    depends on ACPI
++    tristate "Microsoft Surface/System Aggregator Module and Platform Drivers"
++    ---help---
++      Drivers for the Surface/System Aggregator Module (SAM) of Microsoft
++      Surface devices.
++
++      SAM is an embedded controller that provides access to various
++      functionalities on these devices, including battery status, keyboard
++      events (on the Laptops) and many more.
++
++      Say Y here if you have a Microsoft Surface device with a SAM device
++      (i.e. 5th generation or later).
++
++config SURFACE_SAM_SSH
++	tristate "Surface Serial Hub Driver"
++	depends on SURFACE_SAM
 +	depends on X86_INTEL_LPSS
 +	depends on SERIAL_8250_DW
 +	depends on SERIAL_8250_DMA
 +	depends on SERIAL_DEV_CTRL_TTYPORT
 +	select CRC_CCITT
-+	default y
++	default m
 +	---help---
 +	  Surface Serial Hub driver for 5th generation (or later) Microsoft
 +	  Surface devices.
@@ -109,15 +151,15 @@ index 1e2524de6a63..9a47363a0c30 100644
 +	  This is the base driver for the embedded serial controller found on
 +	  5th generation (and later) Microsoft Surface devices (e.g. Book 2,
 +	  Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only
-+	  provides access to the embedded controller and subsequent drivers are
-+	  required for the respective functionalities.
++	  provides access to the embedded controller (SAM) and subsequent
++	  drivers are required for the respective functionalities.
 +
 +	  If you have a 5th generation (or later) Microsoft Surface device, say
 +	  Y or M here.
 +
-+config SURFACE_ACPI_SSH_DEBUG_DEVICE
++config SURFACE_SAM_SSH_DEBUG_DEVICE
 +	bool "Surface Serial Hub Debug Device"
-+	depends on SURFACE_ACPI_SSH
++	depends on SURFACE_SAM_SSH
 +	default n
 +	---help---
 +	  Debug device for direct communication with the embedded controller
@@ -126,10 +168,10 @@ index 1e2524de6a63..9a47363a0c30 100644
 +
 +	  If you are not sure, say N here.
 +
-+config SURFACE_ACPI_SAN
-+	bool "Surface ACPI Notify Driver"
-+	depends on SURFACE_ACPI_SSH
-+	default y
++config SURFACE_SAM_SAN
++	tristate "Surface ACPI Notify Driver"
++	depends on SURFACE_SAM_SSH
++	default m
 +	---help---
 +	  Surface ACPI Notify driver for 5th generation (or later) Microsoft
 +	  Surface devices.
@@ -140,11 +182,11 @@ index 1e2524de6a63..9a47363a0c30 100644
 +
 +	  If you are not sure, say Y here.
 +
-+config SURFACE_ACPI_VHF
-+	bool "Surface Virtual HID Framework Driver"
-+	depends on SURFACE_ACPI_SSH
++config SURFACE_SAM_VHF
++	tristate "Surface Virtual HID Framework Driver"
++	depends on SURFACE_SAM_SSH
 +	depends on HID
-+	default y
++	default m
 +	---help---
 +	  Surface Virtual HID Framework driver for 5th generation (or later)
 +	  Microsoft Surface devices.
@@ -154,11 +196,11 @@ index 1e2524de6a63..9a47363a0c30 100644
 +
 +	  If you are not sure, say Y here.
 +
-+config SURFACE_ACPI_DTX
-+	bool "Surface Detachment System (DTX) Driver"
-+	depends on SURFACE_ACPI_SSH
++config SURFACE_SAM_DTX
++	tristate "Surface Detachment System (DTX) Driver"
++	depends on SURFACE_SAM_SSH
 +	depends on INPUT
-+	default y
++	default m
 +	---help---
 +	  Surface Detachment System (DTX) driver for the Microsoft Surface Book
 +	  2. This driver provides support for proper detachment handling in
@@ -171,10 +213,10 @@ index 1e2524de6a63..9a47363a0c30 100644
 +
 +	  If you are not sure, say Y here.
 +
-+config SURFACE_ACPI_SID
-+	bool "Surface Platform Integration Driver"
-+	depends on SURFACE_ACPI_SSH
-+	default y
++config SURFACE_SAM_SID
++	tristate "Surface Platform Integration Driver"
++	depends on SURFACE_SAM_SSH
++	default m
 +	---help---
 +	  Surface Platform Integration Driver for the Microsoft Surface Devices.
 +	  Currently only supports the Surface Book 2. This driver provides suport
@@ -183,4037 +225,3931 @@ index 1e2524de6a63..9a47363a0c30 100644
 +	  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 dc29af4d8e2f..2250a32a5527 100644
---- a/drivers/platform/x86/Makefile
-+++ b/drivers/platform/x86/Makefile
-@@ -35,6 +35,7 @@ obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
- obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
- obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
- obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
-+obj-$(CONFIG_SURFACE_ACPI)	+= surface_acpi.o
- obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
- obj-$(CONFIG_FUJITSU_LAPTOP)	+= fujitsu-laptop.o
- obj-$(CONFIG_FUJITSU_TABLET)	+= fujitsu-tablet.o
-diff --git a/drivers/platform/x86/surface_acpi.c b/drivers/platform/x86/surface_acpi.c
+diff --git a/drivers/platform/x86/surface_sam/Makefile b/drivers/platform/x86/surface_sam/Makefile
 new file mode 100644
-index 000000000000..5dbf48a3d9b3
+index 000000000000..5431174ea993
 --- /dev/null
-+++ b/drivers/platform/x86/surface_acpi.c
-@@ -0,0 +1,4010 @@
-+#include <asm/unaligned.h>
++++ b/drivers/platform/x86/surface_sam/Makefile
+@@ -0,0 +1,5 @@
++obj-$(CONFIG_SURFACE_SAM_SSH)	+= surface_sam_ssh.o
++obj-$(CONFIG_SURFACE_SAM_SAN)	+= surface_sam_san.o
++obj-$(CONFIG_SURFACE_SAM_SID)	+= surface_sam_sid.o
++obj-$(CONFIG_SURFACE_SAM_DTX)	+= surface_sam_dtx.o
++obj-$(CONFIG_SURFACE_SAM_VHF)	+= surface_sam_vhf.o
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_dtx.c b/drivers/platform/x86/surface_sam/surface_sam_dtx.c
+new file mode 100644
+index 000000000000..9f2c873f1452
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_dtx.c
+@@ -0,0 +1,620 @@
++/*
++ * Detachment system (DTX) driver for Microsoft Surface Book 2.
++ */
++
 +#include <linux/acpi.h>
-+#include <linux/completion.h>
-+#include <linux/crc-ccitt.h>
 +#include <linux/delay.h>
-+#include <linux/device.h>
-+#include <linux/dmaengine.h>
-+#include <linux/dmi.h>
 +#include <linux/fs.h>
-+#include <linux/hid.h>
 +#include <linux/input.h>
 +#include <linux/ioctl.h>
-+#include <linux/jiffies.h>
 +#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>
 +#include <linux/poll.h>
 +#include <linux/rculist.h>
-+#include <linux/refcount.h>
-+#include <linux/serdev.h>
++#include <linux/slab.h>
 +#include <linux/spinlock.h>
-+#include <linux/sysfs.h>
-+#include <linux/types.h>
-+#include <linux/workqueue.h>
++#include <linux/platform_device.h>
++
++#include "surface_sam_ssh.h"
 +
 +
 +#define USB_VENDOR_ID_MICROSOFT				0x045e
-+#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)
++// name copied from MS device manager
++#define DTX_INPUT_NAME	"Microsoft Surface Base 2 Integration Device"
 +
 +
-+/*************************************************************************
-+ * Surface Serial Hub driver (cross-driver interface)
-+ */
++#define DTX_CMD_LATCH_LOCK				_IO(0x11, 0x01)
++#define DTX_CMD_LATCH_UNLOCK				_IO(0x11, 0x02)
++#define DTX_CMD_LATCH_REQUEST				_IO(0x11, 0x03)
++#define DTX_CMD_LATCH_OPEN				_IO(0x11, 0x04)
++#define DTX_CMD_GET_OPMODE				_IOR(0x11, 0x05, int)
 +
-+#ifdef CONFIG_SURFACE_ACPI_SSH
++#define SAM_RQST_DTX_TC					0x11
++#define SAM_RQST_DTX_CID_LATCH_LOCK			0x06
++#define SAM_RQST_DTX_CID_LATCH_UNLOCK			0x07
++#define SAM_RQST_DTX_CID_LATCH_REQUEST			0x08
++#define SAM_RQST_DTX_CID_LATCH_OPEN			0x09
++#define SAM_RQST_DTX_CID_GET_OPMODE			0x0D
 +
-+/*
-+ * Maximum request payload size in bytes.
-+ * Value based on ACPI (255 bytes minus header/status bytes).
-+ */
-+#define SURFACEGEN5_MAX_RQST_PAYLOAD	(255 - 10)
++#define SAM_EVENT_DTX_TC				0x11
++#define SAM_EVENT_DTX_RQID				0x0011
++#define SAM_EVENT_DTX_CID_CONNECTION			0x0c
++#define SAM_EVENT_DTX_CID_BUTTON			0x0e
++#define SAM_EVENT_DTX_CID_ERROR				0x0f
++#define SAM_EVENT_DTX_CID_LATCH_STATUS			0x11
 +
-+/*
-+ * Maximum response payload size in bytes.
-+ * Value based on ACPI (255 bytes minus header/status bytes).
-+ */
-+#define SURFACEGEN5_MAX_RQST_RESPONSE	(255 - 4)
++#define DTX_OPMODE_TABLET				0x00
++#define DTX_OPMODE_LAPTOP				0x01
++#define DTX_OPMODE_STUDIO				0x02
 +
-+#define SURFACEGEN5_RQID_EVENT_BITS	5
++#define DTX_LATCH_CLOSED				0x00
++#define DTX_LATCH_OPENED				0x01
 +
-+#define SURFACEGEN5_EVENT_IMMEDIATE	((unsigned long) -1)
 +
++// Warning: This must always be a power of 2!
++#define DTX_CLIENT_BUF_SIZE				16
 +
-+struct surfacegen5_buf {
-+	u8 cap;
-+	u8 len;
-+	u8 *data;
-+};
++#define DTX_CONNECT_OPMODE_DELAY			1000
 +
-+struct surfacegen5_rqst {
-+	u8 tc;
-+	u8 iid;
-+	u8 cid;
-+	u8 snc;
-+	u8 cdl;
-+	u8 *pld;
-+};
++#define DTX_ERR		KERN_ERR "surface_sam_dtx: "
++#define DTX_WARN	KERN_WARNING "surface_sam_dtx: "
 +
-+struct surfacegen5_event {
-+	u16 rqid;
-+	u8  tc;
-+	u8  iid;
-+	u8  cid;
-+	u8  len;
-+	u8 *pld;
-+};
 +
++struct surface_dtx_event {
++	u8 type;
++	u8 code;
++	u8 arg0;
++	u8 arg1;
++} __packed;
 +
-+typedef int (*surfacegen5_ec_event_handler_fn)(struct surfacegen5_event *event, void *data);
-+typedef unsigned long (*surfacegen5_ec_event_handler_delay)(struct surfacegen5_event *event, void *data);
++struct surface_dtx_dev {
++	wait_queue_head_t waitq;
++	struct miscdevice mdev;
++	spinlock_t client_lock;
++	struct list_head client_list;
++	struct mutex mutex;
++	bool active;
++	spinlock_t input_lock;
++	struct input_dev *input_dev;
++};
 +
-+int surfacegen5_ec_consumer_register(struct device *consumer);
++struct surface_dtx_client {
++	struct list_head node;
++	struct surface_dtx_dev *ddev;
++	struct fasync_struct *fasync;
++	spinlock_t buffer_lock;
++	unsigned int buffer_head;
++	unsigned int buffer_tail;
++	struct surface_dtx_event buffer[DTX_CLIENT_BUF_SIZE];
++};
 +
-+int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result);
 +
-+int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid);
-+int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid);
-+int surfacegen5_ec_remove_event_handler(u16 rqid);
-+int surfacegen5_ec_set_event_handler(u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data);
-+int surfacegen5_ec_set_delayed_event_handler(u16 rqid,
-+		surfacegen5_ec_event_handler_fn fn,
-+		surfacegen5_ec_event_handler_delay delay, void *data);
++static struct surface_dtx_dev surface_dtx_dev;
 +
-+#endif /* CONFIG_SURFACE_ACPI_SSH */
 +
++static int surface_sam_query_opmpde(void)
++{
++	u8 result_buf[1];
++	int status;
 +
-+/*************************************************************************
-+ * Surface Serial Hub Debug Device (cross-driver interface)
-+ */
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = SAM_RQST_DTX_TC,
++		.iid = 0,
++		.cid = SAM_RQST_DTX_CID_GET_OPMODE,
++		.snc = 1,
++		.cdl = 0,
++		.pld = NULL,
++	};
 +
-+#ifdef CONFIG_SURFACE_ACPI_SSH
++	struct surface_sam_ssh_buf result = {
++		.cap = 1,
++		.len = 0,
++		.data = result_buf,
++	};
 +
-+int surfacegen5_ssh_sysfs_register(struct device *dev);
-+void surfacegen5_ssh_sysfs_unregister(struct device *dev);
++	status = surface_sam_ssh_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
 +
-+#endif /* CONFIG_SURFACE_ACPI_SSH */
++	if (result.len != 1) {
++		return -EFAULT;
++	}
 +
++	return result.data[0];
++}
 +
-+/*************************************************************************
-+ * Surface Serial Hub driver (private implementation)
-+ */
 +
-+#ifdef CONFIG_SURFACE_ACPI_SSH
++static int dtx_cmd_simple(u8 cid)
++{
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = SAM_RQST_DTX_TC,
++		.iid = 0,
++		.cid = cid,
++		.snc = 0,
++		.cdl = 0,
++		.pld = NULL,
++	};
 +
-+#define SG5_RQST_TAG_FULL		"surfacegen5_ec_rqst: "
-+#define SG5_RQST_TAG			"rqst: "
-+#define SG5_EVENT_TAG			"event: "
-+#define SG5_RECV_TAG			"recv: "
++	return surface_sam_ssh_rqst(&rqst, NULL);
++}
 +
-+#define SG5_SUPPORTED_FLOW_CONTROL_MASK		(~((u8) ACPI_UART_FLOW_CONTROL_HW))
++static int dtx_cmd_get_opmode(int __user *buf)
++{
++	int opmode = surface_sam_query_opmpde();
++	if (opmode < 0) {
++		return opmode;
++	}
 +
-+#define SG5_BYTELEN_SYNC		2
-+#define SG5_BYTELEN_TERM		2
-+#define SG5_BYTELEN_CRC			2
-+#define SG5_BYTELEN_CTRL		4	// command-header, ACK, or RETRY
-+#define SG5_BYTELEN_CMDFRAME		8	// without payload
++	if (put_user(opmode, buf)) {
++		return -EACCES;
++	}
 +
-+#define SG5_MAX_WRITE (                 \
-+	  SG5_BYTELEN_SYNC              \
-+	+ SG5_BYTELEN_CTRL              \
-+	+ SG5_BYTELEN_CRC               \
-+	+ SG5_BYTELEN_CMDFRAME          \
-+	+ SURFACEGEN5_MAX_RQST_PAYLOAD  \
-+	+ SG5_BYTELEN_CRC               \
-+)
++	return 0;
++}
 +
-+#define SG5_MSG_LEN_CTRL (              \
-+	  SG5_BYTELEN_SYNC              \
-+	+ SG5_BYTELEN_CTRL              \
-+	+ SG5_BYTELEN_CRC               \
-+	+ SG5_BYTELEN_TERM              \
-+)
 +
-+#define SG5_MSG_LEN_CMD_BASE (          \
-+	  SG5_BYTELEN_SYNC              \
-+	+ SG5_BYTELEN_CTRL              \
-+	+ SG5_BYTELEN_CRC               \
-+	+ SG5_BYTELEN_CRC               \
-+)	// without payload and command-frame
++static int surface_dtx_open(struct inode *inode, struct file *file)
++{
++	struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev);
++	struct surface_dtx_client *client;
 +
-+#define SG5_WRITE_TIMEOUT		msecs_to_jiffies(1000)
-+#define SG5_READ_TIMEOUT		msecs_to_jiffies(1000)
-+#define SG5_NUM_RETRY			3
++	// initialize client
++	client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL);
++	if (!client) {
++		return -ENOMEM;
++	}
 +
-+#define SG5_WRITE_BUF_LEN		SG5_MAX_WRITE
-+#define SG5_READ_BUF_LEN		512		// must be power of 2
-+#define SG5_EVAL_BUF_LEN		SG5_MAX_WRITE	// also works for reading
++	spin_lock_init(&client->buffer_lock);
++	client->buffer_head = 0;
++	client->buffer_tail = 0;
++	client->ddev = ddev;
 +
-+#define SG5_FRAME_TYPE_CMD		0x80
-+#define SG5_FRAME_TYPE_ACK		0x40
-+#define SG5_FRAME_TYPE_RETRY		0x04
++	// attach client
++	spin_lock(&ddev->client_lock);
++	list_add_tail_rcu(&client->node, &ddev->client_list);
++	spin_unlock(&ddev->client_lock);
 +
-+#define SG5_FRAME_OFFS_CTRL		SG5_BYTELEN_SYNC
-+#define SG5_FRAME_OFFS_CTRL_CRC		(SG5_FRAME_OFFS_CTRL + SG5_BYTELEN_CTRL)
-+#define SG5_FRAME_OFFS_TERM		(SG5_FRAME_OFFS_CTRL_CRC + SG5_BYTELEN_CRC)
-+#define SG5_FRAME_OFFS_CMD		SG5_FRAME_OFFS_TERM	// either TERM or CMD
-+#define SG5_FRAME_OFFS_CMD_PLD		(SG5_FRAME_OFFS_CMD + SG5_BYTELEN_CMDFRAME)
++	file->private_data = client;
++	nonseekable_open(inode, file);
 +
-+/*
-+ * A note on Request IDs (RQIDs):
-+ * 	0x0000 is not a valid RQID
-+ * 	0x0001 is valid, but reserved for Surface Laptop keyboard events
-+ */
-+#define SG5_NUM_EVENT_TYPES		((1 << SURFACEGEN5_RQID_EVENT_BITS) - 1)
++	return 0;
++}
 +
-+/*
-+ * Sync:			aa 55
-+ * Terminate:			ff ff
-+ *
-+ * Request Message:		sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame)
-+ * Ack Message:			sync ack crc(ack) terminate
-+ * Retry Message:		sync retry crc(retry) terminate
-+ * Response Message:		sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame)
-+ *
-+ * Command Header:		80 LEN 00 SEQ
-+ * Ack:                 	40 00 00 SEQ
-+ * Retry:			04 00 00 00
-+ * Command Request Frame:	80 RTC 01 00 RIID RQID RCID PLD
-+ * Command Response Frame:	80 RTC 00 01 RIID RQID RCID PLD
-+ */
++static int surface_dtx_release(struct inode *inode, struct file *file)
++{
++	struct surface_dtx_client *client = file->private_data;
 +
-+struct surfacegen5_frame_ctrl {
-+	u8 type;
-+	u8 len;			// without crc
-+	u8 pad;
-+	u8 seq;
-+} __packed;
++	// detach client
++	spin_lock(&client->ddev->client_lock);
++	list_del_rcu(&client->node);
++	spin_unlock(&client->ddev->client_lock);
++	synchronize_rcu();
 +
-+struct surfacegen5_frame_cmd {
-+	u8 type;
-+	u8 tc;
-+	u8 unknown1;
-+	u8 unknown2;
-+	u8 iid;
-+	u8 rqid_lo;		// id for request/response matching (low byte)
-+	u8 rqid_hi;		// id for request/response matching (high byte)
-+	u8 cid;
-+} __packed;
++	kfree(client);
++	file->private_data = NULL;
 +
++	return 0;
++}
 +
-+enum surfacegen5_ec_state {
-+	SG5_EC_UNINITIALIZED,
-+	SG5_EC_INITIALIZED,
-+	SG5_EC_SUSPENDED,
-+};
++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
++{
++	struct surface_dtx_client *client = file->private_data;
++	struct surface_dtx_dev *ddev = client->ddev;
++	struct surface_dtx_event event;
++	size_t read = 0;
++	int status = 0;
 +
-+struct surfacegen5_ec_counters {
-+	u8  seq;		// control sequence id
-+	u16 rqid;		// id for request/response matching
-+};
++	if (count != 0 && count < sizeof(struct surface_dtx_event)) {
++		return -EINVAL;
++	}
 +
-+struct surfacegen5_ec_writer {
-+	u8 *data;
-+	u8 *ptr;
-+} __packed;
++	if (!ddev->active) {
++		return -ENODEV;
++	}
 +
-+enum surfacegen5_ec_receiver_state {
-+	SG5_RCV_DISCARD,
-+	SG5_RCV_CONTROL,
-+	SG5_RCV_COMMAND,
-+};
++	// check availability
++	if (client->buffer_head == client->buffer_tail){
++		if (file->f_flags & O_NONBLOCK) {
++			return -EAGAIN;
++		}
 +
-+struct surfacegen5_ec_receiver {
-+	spinlock_t        lock;
-+	enum surfacegen5_ec_receiver_state state;
-+	struct completion signal;
-+	struct kfifo      fifo;
-+	struct {
-+		bool pld;
-+		u8   seq;
-+		u16  rqid;
-+	} expect;
-+	struct {
-+		u16 cap;
-+		u16 len;
-+		u8 *ptr;
-+	} eval_buf;
-+};
++		status = wait_event_interruptible(ddev->waitq,
++				client->buffer_head != client->buffer_tail ||
++				!ddev->active);
++		if (status) {
++			return status;
++		}
 +
-+struct surfacegen5_ec_event_handler {
-+	surfacegen5_ec_event_handler_fn handler;
-+	surfacegen5_ec_event_handler_delay delay;
-+	void *data;
-+};
++		if (!ddev->active) {
++			return -ENODEV;
++		}
++	}
 +
-+struct surfacegen5_ec_events {
-+	spinlock_t lock;
-+	struct workqueue_struct *queue_ack;
-+	struct workqueue_struct *queue_evt;
-+	struct surfacegen5_ec_event_handler handler[SG5_NUM_EVENT_TYPES];
-+};
-+
-+struct surfacegen5_ec {
-+	struct mutex                   lock;
-+	enum surfacegen5_ec_state      state;
-+	struct serdev_device          *serdev;
-+	struct surfacegen5_ec_counters counter;
-+	struct surfacegen5_ec_writer   writer;
-+	struct surfacegen5_ec_receiver receiver;
-+	struct surfacegen5_ec_events   events;
-+};
++	// copy events one by one
++	while (read + sizeof(struct surface_dtx_event) <= count) {
++		spin_lock_irq(&client->buffer_lock);
 +
-+struct surfacegen5_fifo_packet {
-+	u8 type;	// packet type (ACK/RETRY/CMD)
-+	u8 seq;
-+	u8 len;
-+};
++		if(client->buffer_head == client->buffer_tail) {
++			spin_unlock_irq(&client->buffer_lock);
++			break;
++		}
 +
-+struct surfacegen5_event_work {
-+	refcount_t               refcount;
-+	struct surfacegen5_ec   *ec;
-+	struct work_struct       work_ack;
-+	struct delayed_work      work_evt;
-+	struct surfacegen5_event event;
-+	u8                       seq;
-+};
++		// get one event
++		event = client->buffer[client->buffer_tail];
++		client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1);
++		spin_unlock_irq(&client->buffer_lock);
 +
++		// copy to userspace
++		if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) {
++			return -EFAULT;
++		}
 +
-+static struct surfacegen5_ec surfacegen5_ec = {
-+	.lock   = __MUTEX_INITIALIZER(surfacegen5_ec.lock),
-+	.state  = SG5_EC_UNINITIALIZED,
-+	.serdev = NULL,
-+	.counter = {
-+		.seq  = 0,
-+		.rqid = 0,
-+	},
-+	.writer = {
-+		.data = NULL,
-+		.ptr  = NULL,
-+	},
-+	.receiver = {
-+		.lock = __SPIN_LOCK_UNLOCKED(),
-+		.state = SG5_RCV_DISCARD,
-+		.expect = {},
-+	},
-+	.events = {
-+		.lock = __SPIN_LOCK_UNLOCKED(),
-+		.handler = {},
++		read += sizeof(struct surface_dtx_event);
 +	}
-+};
 +
++	return read;
++}
 +
-+static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec,
-+                                 const struct surfacegen5_rqst *rqst,
-+				 struct surfacegen5_buf *result);
++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
++{
++	struct surface_dtx_client *client = file->private_data;
++	int mask;
 +
++	poll_wait(file, &client->ddev->waitq, pt);
 +
-+inline static struct surfacegen5_ec *surfacegen5_ec_acquire(void)
-+{
-+	struct surfacegen5_ec *ec = &surfacegen5_ec;
++	if (client->ddev->active) {
++		mask = EPOLLOUT | EPOLLWRNORM;
++	} else {
++		mask = EPOLLHUP | EPOLLERR;
++	}
 +
-+	mutex_lock(&ec->lock);
-+	return ec;
++	if (client->buffer_head != client->buffer_tail) {
++		mask |= EPOLLIN | EPOLLRDNORM;
++	}
++
++	return mask;
 +}
 +
-+inline static void surfacegen5_ec_release(struct surfacegen5_ec *ec)
++static int surface_dtx_fasync(int fd, struct file *file, int on)
 +{
-+	mutex_unlock(&ec->lock);
++	struct surface_dtx_client *client = file->private_data;
++
++	return fasync_helper(fd, file, on, &client->fasync);
 +}
 +
-+inline static struct surfacegen5_ec *surfacegen5_ec_acquire_init(void)
++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 +{
-+	struct surfacegen5_ec *ec = surfacegen5_ec_acquire();
++	struct surface_dtx_client *client = file->private_data;
++	struct surface_dtx_dev *ddev = client->ddev;
++	int status;
 +
-+	if (ec->state == SG5_EC_UNINITIALIZED) {
-+		surfacegen5_ec_release(ec);
-+		return NULL;
++	status = mutex_lock_interruptible(&ddev->mutex);
++	if (status) {
++		return status;
 +	}
 +
-+	return ec;
-+}
++	if (!ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		return -ENODEV;
++	}
 +
-+int surfacegen5_ec_consumer_register(struct device *consumer)
-+{
-+	u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
-+	struct surfacegen5_ec *ec;
-+	struct device_link *link;
++	switch (cmd) {
++	case DTX_CMD_LATCH_LOCK:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_LOCK);
++		break;
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		return -ENXIO;
-+	}
++	case DTX_CMD_LATCH_UNLOCK:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_UNLOCK);
++		break;
 +
-+	link = device_link_add(consumer, &ec->serdev->dev, flags);
-+	if (!link) {
-+		return -EFAULT;
++	case DTX_CMD_LATCH_REQUEST:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_REQUEST);
++		break;
++
++	case DTX_CMD_LATCH_OPEN:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_OPEN);
++		break;
++
++	case DTX_CMD_GET_OPMODE:
++		status = dtx_cmd_get_opmode((int __user *)arg);
++		break;
++
++	default:
++		status = -EINVAL;
++		break;
 +	}
 +
-+	surfacegen5_ec_release(ec);
-+	return 0;
++	mutex_unlock(&ddev->mutex);
++	return status;
 +}
 +
++static const struct file_operations surface_dtx_fops = {
++	.owner          = THIS_MODULE,
++	.open           = surface_dtx_open,
++	.release        = surface_dtx_release,
++	.read           = surface_dtx_read,
++	.poll           = surface_dtx_poll,
++	.fasync         = surface_dtx_fasync,
++	.unlocked_ioctl = surface_dtx_ioctl,
++	.llseek         = no_llseek,
++};
 +
-+inline static u16 surfacegen5_rqid_to_rqst(u16 rqid) {
-+	return rqid << SURFACEGEN5_RQID_EVENT_BITS;
-+}
++static struct surface_dtx_dev surface_dtx_dev = {
++	.mdev = {
++		.minor = MISC_DYNAMIC_MINOR,
++		.name = "surface_dtx",
++		.fops = &surface_dtx_fops,
++	},
++	.client_lock = __SPIN_LOCK_UNLOCKED(),
++	.input_lock = __SPIN_LOCK_UNLOCKED(),
++	.mutex  = __MUTEX_INITIALIZER(surface_dtx_dev.mutex),
++	.active = false,
++};
 +
-+inline static bool surfacegen5_rqid_is_event(u16 rqid) {
-+	const u16 mask = (1 << SURFACEGEN5_RQID_EVENT_BITS) - 1;
-+	return rqid != 0 && (rqid | mask) == mask;
-+}
 +
-+int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid)
++static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event)
 +{
-+	struct surfacegen5_ec *ec;
-+
-+	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
-+	u8 buf[1] = { 0x00 };
++	struct surface_dtx_client *client;
 +
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = 0x01,
-+		.iid = 0x00,
-+		.cid = 0x0b,
-+		.snc = 0x01,
-+		.cdl = 0x04,
-+		.pld = pld,
-+	};
++	rcu_read_lock();
++	list_for_each_entry_rcu(client, &ddev->client_list, node) {
++		spin_lock(&client->buffer_lock);
 +
-+	struct surfacegen5_buf result = {
-+		result.cap = ARRAY_SIZE(buf),
-+		result.len = 0,
-+		result.data = buf,
-+	};
++		client->buffer[client->buffer_head++] = *event;
++		client->buffer_head &= DTX_CLIENT_BUF_SIZE - 1;
 +
-+	int status;
++		if (unlikely(client->buffer_head == client->buffer_tail)) {
++			printk(DTX_WARN "event buffer overrun\n");
++			client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1);
++		}
 +
-+	// only allow RQIDs that lie within event spectrum
-+	if (!surfacegen5_rqid_is_event(rqid)) {
-+		return -EINVAL;
-+	}
++		spin_unlock(&client->buffer_lock);
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
-+		return -ENXIO;
++		kill_fasync(&client->fasync, SIGIO, POLL_IN);
 +	}
++	rcu_read_unlock();
 +
-+	if (ec->state == SG5_EC_SUSPENDED) {
-+		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
++	wake_up_interruptible(&ddev->waitq);
++}
 +
-+		surfacegen5_ec_release(ec);
-+		return -EPERM;
-+	}
 +
-+	status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
++static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev)
++{
++	struct surface_dtx_event event;
++	int opmode;
 +
-+	if (buf[0] != 0x00) {
-+		dev_warn(&ec->serdev->dev,
-+		         "unexpected result while enabling event source: 0x%02x\n",
-+			 buf[0]);
++	// get operation mode
++	opmode = surface_sam_query_opmpde();
++	if (opmode < 0) {
++		printk(DTX_ERR "EC request failed with error %d\n", opmode);
 +	}
 +
-+	surfacegen5_ec_release(ec);
-+	return status;
++	// send DTX event
++	event.type = 0x11;
++	event.code = 0x0D;
++	event.arg0 = opmode;
++	event.arg1 = 0x00;
++
++	surface_dtx_push_event(ddev, &event);
 +
++	// send SW_TABLET_MODE event
++	spin_lock(&ddev->input_lock);
++	input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00);
++	input_sync(ddev->input_dev);
++	spin_unlock(&ddev->input_lock);
 +}
 +
-+int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid)
++static int surface_dtx_evt_dtx(struct surface_sam_ssh_event *in_event, void *data)
 +{
-+	struct surfacegen5_ec *ec;
++	struct surface_dtx_dev *ddev = data;
++	struct surface_dtx_event event;
 +
-+	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
-+	u8 buf[1] = { 0x00 };
++	switch (in_event->cid) {
++	case SAM_EVENT_DTX_CID_CONNECTION:
++	case SAM_EVENT_DTX_CID_BUTTON:
++	case SAM_EVENT_DTX_CID_ERROR:
++	case SAM_EVENT_DTX_CID_LATCH_STATUS:
++		if (in_event->len > 2) {
++			printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n",
++			       in_event->cid, in_event->len);
++			return 0;
++		}
 +
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = 0x01,
-+		.iid = 0x00,
-+		.cid = 0x0c,
-+		.snc = 0x01,
-+		.cdl = 0x04,
-+		.pld = pld,
-+	};
++		event.type = in_event->tc;
++		event.code = in_event->cid;
++		event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00;
++		event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00;
++		surface_dtx_push_event(ddev, &event);
++		break;
 +
-+	struct surfacegen5_buf result = {
-+		result.cap = ARRAY_SIZE(buf),
-+		result.len = 0,
-+		result.data = buf,
-+	};
++	default:
++		printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid);
++	}
 +
-+	int status;
++	// update device mode
++	if (in_event->cid == SAM_EVENT_DTX_CID_CONNECTION) {
++		if (in_event->pld[0]) {
++			// Note: we're already in a workqueue task
++			msleep(DTX_CONNECT_OPMODE_DELAY);
++		}
 +
-+	// only allow RQIDs that lie within event spectrum
-+	if (!surfacegen5_rqid_is_event(rqid)) {
-+		return -EINVAL;
++		surface_dtx_update_opmpde(ddev);
 +	}
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
-+		return -ENXIO;
-+	}
++	return 0;
++}
 +
-+	if (ec->state == SG5_EC_SUSPENDED) {
-+		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
++static int surface_dtx_events_setup(struct surface_dtx_dev *ddev)
++{
++	int status;
 +
-+		surfacegen5_ec_release(ec);
-+		return -EPERM;
++	status = surface_sam_ssh_set_event_handler(SAM_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev);
++	if (status) {
++		goto err_handler;
 +	}
 +
-+	status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
-+
-+	if (buf[0] != 0x00) {
-+		dev_warn(&ec->serdev->dev,
-+		         "unexpected result while disabling event source: 0x%02x\n",
-+			 buf[0]);
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID);
++	if (status) {
++		goto err_source;
 +	}
 +
-+	surfacegen5_ec_release(ec);
++	return 0;
++
++err_source:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID);
++err_handler:
 +	return status;
 +}
 +
-+int surfacegen5_ec_set_delayed_event_handler(
-+		u16 rqid, surfacegen5_ec_event_handler_fn fn,
-+		surfacegen5_ec_event_handler_delay delay,
-+		void *data)
++static void surface_dtx_events_disable(void)
 +{
-+	struct surfacegen5_ec *ec;
-+	unsigned long flags;
++	surface_sam_ssh_disable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID);
++}
 +
-+	if (!surfacegen5_rqid_is_event(rqid)) {
-+		return -EINVAL;
-+	}
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		return -ENXIO;
++static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev)
++{
++	struct input_dev *input_dev;
++	int status;
++
++	input_dev = input_allocate_device();
++	if (!input_dev) {
++		return ERR_PTR(-ENOMEM);
 +	}
 +
-+	spin_lock_irqsave(&ec->events.lock, flags);
++	input_dev->name = DTX_INPUT_NAME;
++	input_dev->dev.parent = &pdev->dev;
++	input_dev->id.bustype = BUS_VIRTUAL;
++	input_dev->id.vendor  = USB_VENDOR_ID_MICROSOFT;
++	input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION;
 +
-+	// 0 is not a valid event RQID
-+	ec->events.handler[rqid - 1].handler = fn;
-+	ec->events.handler[rqid - 1].delay = delay;
-+	ec->events.handler[rqid - 1].data = data;
++	input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
 +
-+	spin_unlock_irqrestore(&ec->events.lock, flags);
-+	surfacegen5_ec_release(ec);
++	status = surface_sam_query_opmpde();
++	if (status < 0) {
++		input_free_device(input_dev);
++		return ERR_PTR(status);
++	}
 +
-+	return 0;
-+}
++	input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00);
 +
-+int surfacegen5_ec_set_event_handler(
-+		u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data)
-+{
-+	return surfacegen5_ec_set_delayed_event_handler(rqid, fn, NULL, data);
++	status = input_register_device(input_dev);
++	if (status) {
++		input_unregister_device(input_dev);
++		return ERR_PTR(status);
++	}
++
++	return input_dev;
 +}
 +
-+int surfacegen5_ec_remove_event_handler(u16 rqid)
++
++static int surface_sam_dtx_probe(struct platform_device *pdev)
 +{
-+	struct surfacegen5_ec *ec;
-+	unsigned long flags;
++	struct surface_dtx_dev *ddev = &surface_dtx_dev;
++	struct input_dev *input_dev;
++	int status;
 +
-+	if (!surfacegen5_rqid_is_event(rqid)) {
-+		return -EINVAL;
++	// link to ec
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
 +	}
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		return -ENXIO;
++	input_dev = surface_dtx_register_inputdev(pdev);
++	if (IS_ERR(input_dev)) {
++		return PTR_ERR(input_dev);
 +	}
 +
-+	spin_lock_irqsave(&ec->events.lock, flags);
++	// initialize device
++	mutex_lock(&ddev->mutex);
++	if (ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		status = -ENODEV;
++		goto err_register;
++	}
 +
-+	// 0 is not a valid event RQID
-+	ec->events.handler[rqid - 1].handler = NULL;
-+	ec->events.handler[rqid - 1].delay = NULL;
-+	ec->events.handler[rqid - 1].data = NULL;
++	INIT_LIST_HEAD(&ddev->client_list);
++	init_waitqueue_head(&ddev->waitq);
++	ddev->active = true;
++	ddev->input_dev = input_dev;
++	mutex_unlock(&ddev->mutex);
 +
-+	spin_unlock_irqrestore(&ec->events.lock, flags);
-+	surfacegen5_ec_release(ec);
++	status = misc_register(&ddev->mdev);
++	if (status) {
++		goto err_register;
++	}
 +
-+	/*
-+	 * Make sure that the handler is not in use any more after we've
-+	 * removed it.
-+	 */
-+	flush_workqueue(ec->events.queue_evt);
++	// enable events
++	status = surface_dtx_events_setup(ddev);
++	if (status) {
++		goto err_events_setup;
++	}
 +
 +	return 0;
-+}
 +
-+
-+inline static u16 surfacegen5_ssh_crc(const u8 *buf, size_t size)
-+{
-+	return crc_ccitt_false(0xffff, buf, size);
++err_events_setup:
++	misc_deregister(&ddev->mdev);
++err_register:
++	input_unregister_device(ddev->input_dev);
++	return status;
 +}
 +
-+inline static void surfacegen5_ssh_write_u16(struct surfacegen5_ec_writer *writer, u16 in)
++static int surface_sam_dtx_remove(struct platform_device *pdev)
 +{
-+	put_unaligned_le16(in, writer->ptr);
-+	writer->ptr += 2;
-+}
++	struct surface_dtx_dev *ddev = &surface_dtx_dev;
++	struct surface_dtx_client *client;
 +
-+inline static void surfacegen5_ssh_write_crc(struct surfacegen5_ec_writer *writer,
-+                                             const u8 *buf, size_t size)
-+{
-+	surfacegen5_ssh_write_u16(writer, surfacegen5_ssh_crc(buf, size));
-+}
++	mutex_lock(&ddev->mutex);
++	if (!ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		return 0;
++	}
 +
-+inline static void surfacegen5_ssh_write_syn(struct surfacegen5_ec_writer *writer)
-+{
-+	u8 *w = writer->ptr;
++	// mark as inactive
++	ddev->active = false;
++	mutex_unlock(&ddev->mutex);
 +
-+	*w++ = 0xaa;
-+	*w++ = 0x55;
++	// After this call we're guaranteed that no more input events will arive
++	surface_dtx_events_disable();
 +
-+	writer->ptr = w;
-+}
++	// wake up clients
++	spin_lock(&ddev->client_lock);
++	list_for_each_entry(client, &ddev->client_list, node) {
++		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
++	}
++	spin_unlock(&ddev->client_lock);
 +
-+inline static void surfacegen5_ssh_write_ter(struct surfacegen5_ec_writer *writer)
-+{
-+	u8 *w = writer->ptr;
++	wake_up_interruptible(&ddev->waitq);
 +
-+	*w++ = 0xff;
-+	*w++ = 0xff;
++	// unregister user-space devices
++	input_unregister_device(ddev->input_dev);
++	misc_deregister(&ddev->mdev);
 +
-+	writer->ptr = w;
++	return 0;
 +}
 +
-+inline static void surfacegen5_ssh_write_buf(struct surfacegen5_ec_writer *writer,
-+                                             u8 *in, size_t len)
-+{
-+	writer->ptr = memcpy(writer->ptr, in, len) + len;
-+}
 +
-+inline static void surfacegen5_ssh_write_hdr(struct surfacegen5_ec_writer *writer,
-+                                             const struct surfacegen5_rqst *rqst,
-+                                             struct surfacegen5_ec *ec)
-+{
-+	struct surfacegen5_frame_ctrl *hdr = (struct surfacegen5_frame_ctrl *)writer->ptr;
-+	u8 *begin = writer->ptr;
++static const struct acpi_device_id surface_sam_dtx_match[] = {
++	{ "MSHW0133", 0 },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match);
 +
-+	hdr->type = SG5_FRAME_TYPE_CMD;
-+	hdr->len  = SG5_BYTELEN_CMDFRAME + rqst->cdl;	// without CRC
-+	hdr->pad  = 0x00;
-+	hdr->seq  = ec->counter.seq;
++struct platform_driver surface_sam_dtx = {
++	.probe = surface_sam_dtx_probe,
++	.remove = surface_sam_dtx_remove,
++	.driver = {
++		.name = "surface_sam_dtx",
++		.acpi_match_table = ACPI_PTR(surface_sam_dtx_match),
++	},
++};
++module_platform_driver(surface_sam_dtx);
 +
-+	writer->ptr += sizeof(*hdr);
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.c b/drivers/platform/x86/surface_sam/surface_sam_san.c
+new file mode 100644
+index 000000000000..9da7843167ad
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_san.c
+@@ -0,0 +1,708 @@
++/*
++ * Surface ACPI Notify (SAN) and ACPI integration driver for SAM.
++ * Translates communication from ACPI to SSH and back.
++ */
 +
-+	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
-+}
++#include <linux/acpi.h>
++#include <linux/delay.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
 +
-+inline static void surfacegen5_ssh_write_cmd(struct surfacegen5_ec_writer *writer,
-+                                             const struct surfacegen5_rqst *rqst,
-+                                             struct surfacegen5_ec *ec)
-+{
-+	struct surfacegen5_frame_cmd *cmd = (struct surfacegen5_frame_cmd *)writer->ptr;
-+	u8 *begin = writer->ptr;
++#include "surface_sam_ssh.h"
 +
-+	u16 rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid);
-+	u8 rqid_lo = rqid & 0xFF;
-+	u8 rqid_hi = rqid >> 8;
 +
-+	cmd->type     = SG5_FRAME_TYPE_CMD;
-+	cmd->tc       = rqst->tc;
-+	cmd->unknown1 = 0x01;
-+	cmd->unknown2 = 0x00;
-+	cmd->iid      = rqst->iid;
-+	cmd->rqid_lo  = rqid_lo;
-+	cmd->rqid_hi  = rqid_hi;
-+	cmd->cid      = rqst->cid;
++#define SAN_RQST_RETRY				5
 +
-+	writer->ptr += sizeof(*cmd);
++#define SAN_DSM_REVISION			0
++#define SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT	0x09
 +
-+	surfacegen5_ssh_write_buf(writer, rqst->pld, rqst->cdl);
-+	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
-+}
++static const guid_t SAN_DSM_UUID =
++	GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d,
++	          0x48, 0x7c, 0x91, 0xab, 0x3c);
 +
-+inline static void surfacegen5_ssh_write_ack(struct surfacegen5_ec_writer *writer, u8 seq)
-+{
-+	struct surfacegen5_frame_ctrl *ack = (struct surfacegen5_frame_ctrl *)writer->ptr;
-+	u8 *begin = writer->ptr;
++#define SAM_EVENT_DELAY_PWR_STATE	msecs_to_jiffies(5000)
 +
-+	ack->type = SG5_FRAME_TYPE_ACK;
-+	ack->len  = 0x00;
-+	ack->pad  = 0x00;
-+	ack->seq  = seq;
++#define SAM_EVENT_PWR_TC		0x02
++#define SAM_EVENT_PWR_RQID		0x0002
++#define SAM_EVENT_PWR_CID_HWCHANGE	0x15
++#define SAM_EVENT_PWR_CID_CHARGING	0x16
++#define SAM_EVENT_PWR_CID_ADAPTER	0x17
++#define SAM_EVENT_PWR_CID_STATE		0x4f
 +
-+	writer->ptr += sizeof(*ack);
++#define SAM_EVENT_TEMP_TC		0x03
++#define SAM_EVENT_TEMP_RQID		0x0003
++#define SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT	0x0b
 +
-+	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
-+}
++#define SAN_RQST_TAG			"surface_sam_san_rqst: "
 +
-+inline static void surfacegen5_ssh_writer_reset(struct surfacegen5_ec_writer *writer)
-+{
-+	writer->ptr = writer->data;
-+}
++#define SAN_QUIRK_BASE_STATE_DELAY	1000
 +
-+inline static int surfacegen5_ssh_writer_flush(struct surfacegen5_ec *ec)
-+{
-+	struct surfacegen5_ec_writer *writer = &ec->writer;
-+	struct serdev_device *serdev = ec->serdev;
-+	int status;
 +
-+	size_t len = writer->ptr - writer->data;
++struct san_acpi_consumer {
++	char *path;
++	bool  required;
++	u32   flags;
++};
 +
-+	dev_dbg(&ec->serdev->dev, "sending message\n");
-+	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
-+	                     writer->data, writer->ptr - writer->data, false);
++struct san_opreg_context {
++	struct acpi_connection_info connection;
++	struct device *dev;
++};
 +
-+	status = serdev_device_write(serdev, writer->data, len, SG5_WRITE_TIMEOUT);
-+	return status >= 0 ? 0 : status;
-+}
++struct san_consumer_link {
++	const struct san_acpi_consumer *properties;
++	struct device_link                         *link;
++};
 +
-+inline static void surfacegen5_ssh_write_msg_cmd(struct surfacegen5_ec *ec,
-+                                                 const struct surfacegen5_rqst *rqst)
-+{
-+	surfacegen5_ssh_writer_reset(&ec->writer);
-+	surfacegen5_ssh_write_syn(&ec->writer);
-+	surfacegen5_ssh_write_hdr(&ec->writer, rqst, ec);
-+	surfacegen5_ssh_write_cmd(&ec->writer, rqst, ec);
-+}
++struct san_consumers {
++	u32                                   num;
++	struct san_consumer_link *links;
++};
 +
-+inline static void surfacegen5_ssh_write_msg_ack(struct surfacegen5_ec *ec, u8 seq)
-+{
-+	surfacegen5_ssh_writer_reset(&ec->writer);
-+	surfacegen5_ssh_write_syn(&ec->writer);
-+	surfacegen5_ssh_write_ack(&ec->writer, seq);
-+	surfacegen5_ssh_write_ter(&ec->writer);
-+}
++struct san_drvdata {
++	struct san_opreg_context opreg_ctx;
++	struct san_consumers     consumers;
++};
 +
-+inline static void surfacegen5_ssh_receiver_restart(struct surfacegen5_ec *ec,
-+                                                    const struct surfacegen5_rqst *rqst)
-+{
-+	unsigned long flags;
++struct gsb_data_in {
++	u8 cv;
++} __packed;
 +
-+	spin_lock_irqsave(&ec->receiver.lock, flags);
-+	reinit_completion(&ec->receiver.signal);
-+	ec->receiver.state = SG5_RCV_CONTROL;
-+	ec->receiver.expect.pld = rqst->snc;
-+	ec->receiver.expect.seq = ec->counter.seq;
-+	ec->receiver.expect.rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid);
-+	ec->receiver.eval_buf.len = 0;
-+	spin_unlock_irqrestore(&ec->receiver.lock, flags);
-+}
++struct gsb_data_rqsx {
++	u8 cv;				// command value (should be 0x01 or 0x03)
++	u8 tc;				// target controller
++	u8 tid;				// expected to be 0x01, could be revision
++	u8 iid;				// target sub-controller (e.g. primary vs. secondary battery)
++	u8 snc;				// expect-response-flag
++	u8 cid;				// command ID
++	u8 cdl;				// payload length
++	u8 _pad;			// padding
++	u8 pld[0];			// payload
++} __packed;
 +
-+inline static void surfacegen5_ssh_receiver_discard(struct surfacegen5_ec *ec)
-+{
-+	unsigned long flags;
++struct gsb_data_etwl {
++	u8 cv;				// command value (should be 0x02)
++	u8 etw3;			// ?
++	u8 etw4;			// ?
++	u8 msg[0];			// error message (ASCIIZ)
++} __packed;
 +
-+	spin_lock_irqsave(&ec->receiver.lock, flags);
-+	ec->receiver.state = SG5_RCV_DISCARD;
-+	ec->receiver.eval_buf.len = 0;
-+	kfifo_reset(&ec->receiver.fifo);
-+	spin_unlock_irqrestore(&ec->receiver.lock, flags);
-+}
++struct gsb_data_out {
++	u8 status;			// _SSH communication status
++	u8 len;				// _SSH payload length
++	u8 pld[0];			// _SSH payload
++} __packed;
 +
-+static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec,
-+                                 const struct surfacegen5_rqst *rqst,
-+				 struct surfacegen5_buf *result)
-+{
-+	struct device *dev = &ec->serdev->dev;
-+	struct surfacegen5_fifo_packet packet = {};
-+	int status;
-+	int try;
-+	unsigned int rem;
++union gsb_buffer_data {
++	struct gsb_data_in   in;	// common input
++	struct gsb_data_rqsx rqsx;	// RQSX input
++	struct gsb_data_etwl etwl;	// ETWL input
++	struct gsb_data_out  out;	// output
++};
 +
-+	if (rqst->cdl > SURFACEGEN5_MAX_RQST_PAYLOAD) {
-+		dev_err(dev, SG5_RQST_TAG "request payload too large\n");
-+		return -EINVAL;
-+	}
++struct gsb_buffer {
++	u8 status;			// GSB AttribRawProcess status
++	u8 len;				// GSB AttribRawProcess length
++	union gsb_buffer_data data;
++} __packed;
 +
-+	// write command in buffer, we may need it multiple times
-+	surfacegen5_ssh_write_msg_cmd(ec, rqst);
-+	surfacegen5_ssh_receiver_restart(ec, rqst);
 +
-+	// send command, try to get an ack response
-+	for (try = 0; try < SG5_NUM_RETRY; try++) {
-+		status = surfacegen5_ssh_writer_flush(ec);
-+		if (status) {
-+			goto ec_rqst_out;
-+		}
++enum san_pwr_event {
++	SAN_PWR_EVENT_BAT1_STAT	= 0x03,
++	SAN_PWR_EVENT_BAT1_INFO	= 0x04,
++	SAN_PWR_EVENT_ADP1_STAT	= 0x05,
++	SAN_PWR_EVENT_ADP1_INFO	= 0x06,
++	SAN_PWR_EVENT_BAT2_STAT	= 0x07,
++	SAN_PWR_EVENT_BAT2_INFO	= 0x08,
++};
 +
-+		rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT);
-+		if (rem) {
-+			// completion assures valid packet, thus ignore returned length
-+			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
 +
-+			if (packet.type == SG5_FRAME_TYPE_ACK) {
-+				break;
-+			}
-+		}
++static int san_acpi_notify_power_event(struct device *dev, enum san_pwr_event event)
++{
++	acpi_handle san = ACPI_HANDLE(dev);
++	union acpi_object *obj;
++
++	obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION,
++	                              (u8) event, NULL, ACPI_TYPE_BUFFER);
++
++	if (IS_ERR_OR_NULL(obj)) {
++		return obj ? PTR_ERR(obj) : -ENXIO;
 +	}
 +
-+	// check if we ran out of tries?
-+	if (try >= SG5_NUM_RETRY) {
-+		dev_err(dev, SG5_RQST_TAG "communication failed %d times, giving up\n", try);
-+		status = -EIO;
-+		goto ec_rqst_out;
++	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
++		dev_err(dev, "got unexpected result from _DSM\n");
++		return -EFAULT;
 +	}
 +
-+	ec->counter.seq  += 1;
-+	ec->counter.rqid += 1;
++	ACPI_FREE(obj);
++	return 0;
++}
 +
-+	// get command response/payload
-+	if (rqst->snc && result) {
-+		rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT);
-+		if (rem) {
-+			// completion assures valid packet, thus ignore returned length
-+			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
++static int san_acpi_notify_sensor_trip_point(struct device *dev, u8 iid)
++{
++	acpi_handle san = ACPI_HANDLE(dev);
++	union acpi_object *obj;
++	union acpi_object param;
 +
-+			if (result->cap < packet.len) {
-+				status = -EINVAL;
-+				goto ec_rqst_out;
-+			}
++	param.type = ACPI_TYPE_INTEGER;
++	param.integer.value = iid;
 +
-+			// completion assures valid packet, thus ignore returned length
-+			(void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len);
-+			result->len = packet.len;
-+		} else {
-+			dev_err(dev, SG5_RQST_TAG "communication timed out\n");
-+			status = -EIO;
-+			goto ec_rqst_out;
-+		}
++	obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION,
++	                              SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT,
++				      &param, ACPI_TYPE_BUFFER);
 +
-+		// send ACK
-+		surfacegen5_ssh_write_msg_ack(ec, packet.seq);
-+		status = surfacegen5_ssh_writer_flush(ec);
-+		if (status) {
-+			goto ec_rqst_out;
-+		}
++	if (IS_ERR_OR_NULL(obj)) {
++		return obj ? PTR_ERR(obj) : -ENXIO;
 +	}
 +
-+ec_rqst_out:
-+	surfacegen5_ssh_receiver_discard(ec);
-+	return status;
++	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
++		dev_err(dev, "got unexpected result from _DSM\n");
++		return -EFAULT;
++	}
++
++	ACPI_FREE(obj);
++	return 0;
 +}
 +
-+int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result)
++
++inline static int san_evt_power_adapter(struct device *dev, struct surface_sam_ssh_event *event)
 +{
-+	struct surfacegen5_ec *ec;
 +	int status;
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
-+		return -ENXIO;
-+	}
-+
-+	if (ec->state == SG5_EC_SUSPENDED) {
-+		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
-+
-+		surfacegen5_ec_release(ec);
-+		return -EPERM;
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_ADP1_STAT);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
 +	}
 +
-+	status = surfacegen5_ec_rqst_unlocked(ec, rqst, result);
-+
-+	surfacegen5_ec_release(ec);
-+	return status;
++	return 0;
 +}
 +
-+
-+static int surfacegen5_ssh_ec_resume(struct surfacegen5_ec *ec)
++inline static int san_evt_power_hwchange(struct device *dev, struct surface_sam_ssh_event *event)
 +{
-+	u8 buf[1] = { 0x00 };
-+
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = 0x01,
-+		.iid = 0x00,
-+		.cid = 0x16,
-+		.snc = 0x01,
-+		.cdl = 0x00,
-+		.pld = NULL,
-+	};
++	enum san_pwr_event evcode;
++	int status;
 +
-+	struct surfacegen5_buf result = {
-+		result.cap = ARRAY_SIZE(buf),
-+		result.len = 0,
-+		result.data = buf,
-+	};
++	if (event->iid == 0x02) {
++		evcode = SAN_PWR_EVENT_BAT2_INFO;
++	} else {
++		evcode = SAN_PWR_EVENT_BAT1_INFO;
++	}
 +
-+	int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
++	status = san_acpi_notify_power_event(dev, evcode);
 +	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
 +		return status;
 +	}
 +
-+	if (buf[0] != 0x00) {
-+		dev_warn(&ec->serdev->dev,
-+		         "unexpected result while trying to resume EC: 0x%02x\n",
-+			 buf[0]);
-+	}
-+
 +	return 0;
 +}
 +
-+static int surfacegen5_ssh_ec_suspend(struct surfacegen5_ec *ec)
++inline static int san_evt_power_state(struct device *dev, struct surface_sam_ssh_event *event)
 +{
-+	u8 buf[1] = { 0x00 };
-+
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = 0x01,
-+		.iid = 0x00,
-+		.cid = 0x15,
-+		.snc = 0x01,
-+		.cdl = 0x00,
-+		.pld = NULL,
-+	};
-+
-+	struct surfacegen5_buf result = {
-+		result.cap = ARRAY_SIZE(buf),
-+		result.len = 0,
-+		result.data = buf,
-+	};
++	int status;
 +
-+	int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT1_STAT);
 +	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
 +		return status;
 +	}
 +
-+	if (buf[0] != 0x00) {
-+		dev_warn(&ec->serdev->dev,
-+		         "unexpected result while trying to suspend EC: 0x%02x\n",
-+			 buf[0]);
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT2_STAT);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
 +	}
 +
 +	return 0;
 +}
 +
-+
-+inline static bool surfacegen5_ssh_is_valid_syn(const u8 *ptr)
-+{
-+	return ptr[0] == 0xaa && ptr[1] == 0x55;
-+}
-+
-+inline static bool surfacegen5_ssh_is_valid_ter(const u8 *ptr)
++static unsigned long san_evt_power_delay(struct surface_sam_ssh_event *event, void *data)
 +{
-+	return ptr[0] == 0xff && ptr[1] == 0xff;
-+}
++	switch (event->cid) {
++	case SAM_EVENT_PWR_CID_CHARGING:
++	case SAM_EVENT_PWR_CID_STATE:
++		return SAM_EVENT_DELAY_PWR_STATE;
 +
-+inline static bool surfacegen5_ssh_is_valid_crc(const u8 *begin, const u8 *end)
-+{
-+	u16 crc = surfacegen5_ssh_crc(begin, end - begin);
-+	return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8));
++	case SAM_EVENT_PWR_CID_ADAPTER:
++	case SAM_EVENT_PWR_CID_HWCHANGE:
++	default:
++		return 0;
++	}
 +}
 +
-+
-+static int surfacegen5_ssh_send_ack(struct surfacegen5_ec *ec, u8 seq)
++static int san_evt_power(struct surface_sam_ssh_event *event, void *data)
 +{
-+	int status;
-+	u8 buf[SG5_MSG_LEN_CTRL];
-+	u16 crc;
++	struct device *dev = (struct device *)data;
 +
-+	buf[0] = 0xaa;
-+	buf[1] = 0x55;
-+	buf[2] = 0x40;
-+	buf[3] = 0x00;
-+	buf[4] = 0x00;
-+	buf[5] = seq;
++	switch (event->cid) {
++	case SAM_EVENT_PWR_CID_HWCHANGE:
++		return san_evt_power_hwchange(dev, event);
 +
-+	crc = surfacegen5_ssh_crc(buf + SG5_FRAME_OFFS_CTRL, SG5_BYTELEN_CTRL);
-+	buf[6] = crc & 0xff;
-+	buf[7] = crc >> 8;
++	case SAM_EVENT_PWR_CID_ADAPTER:
++		return san_evt_power_adapter(dev, event);
 +
-+	buf[8] = 0xff;
-+	buf[9] = 0xff;
++	case SAM_EVENT_PWR_CID_CHARGING:
++	case SAM_EVENT_PWR_CID_STATE:
++		return san_evt_power_state(dev, event);
 +
-+	dev_dbg(&ec->serdev->dev, "sending message\n");
-+	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
-+	                     buf, SG5_MSG_LEN_CTRL, false);
++	default:
++		dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid);
++	}
 +
-+	status = serdev_device_write(ec->serdev, buf, SG5_MSG_LEN_CTRL, SG5_WRITE_TIMEOUT);
-+	return status >= 0 ? 0 : status;
++	return 0;
 +}
 +
-+static void surfacegen5_event_work_ack_handler(struct work_struct *_work)
++
++inline static int san_evt_thermal_notify(struct device *dev, struct surface_sam_ssh_event *event)
 +{
-+	struct surfacegen5_event_work *work;
-+	struct surfacegen5_event *event;
-+	struct surfacegen5_ec *ec;
-+	struct device *dev;
 +	int status;
 +
-+	work = container_of(_work, struct surfacegen5_event_work, work_ack);
-+	event = &work->event;
-+	ec = work->ec;
-+	dev = &ec->serdev->dev;
-+
-+	// make sure we load a fresh ec state
-+	smp_mb();
-+
-+	if (ec->state == SG5_EC_INITIALIZED) {
-+		status = surfacegen5_ssh_send_ack(ec, work->seq);
-+		if (status) {
-+			dev_err(dev, SG5_EVENT_TAG "failed to send ACK: %d\n", status);
-+		}
++	status = san_acpi_notify_sensor_trip_point(dev, event->iid);
++	if (status) {
++		dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid);
++		return status;
 +	}
 +
-+	if (refcount_dec_and_test(&work->refcount)) {
-+		kfree(work);
-+	}
++	return 0;
 +}
 +
-+static void surfacegen5_event_work_evt_handler(struct work_struct *_work)
++static int san_evt_thermal(struct surface_sam_ssh_event *event, void *data)
 +{
-+	struct delayed_work *dwork = (struct delayed_work *)_work;
-+	struct surfacegen5_event_work *work;
-+	struct surfacegen5_event *event;
-+	struct surfacegen5_ec *ec;
-+	struct device *dev;
-+	unsigned long flags;
++	struct device *dev = (struct device *)data;
 +
-+	surfacegen5_ec_event_handler_fn handler;
-+	void *handler_data;
++	switch (event->cid) {
++	case SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT:
++		return san_evt_thermal_notify(dev, event);
 +
-+	int status = 0;
++	default:
++		dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid);
++	}
 +
-+	work = container_of(dwork, struct surfacegen5_event_work, work_evt);
-+	event = &work->event;
-+	ec = work->ec;
-+	dev = &ec->serdev->dev;
++	return 0;
++}
 +
-+	spin_lock_irqsave(&ec->events.lock, flags);
-+	handler       = ec->events.handler[event->rqid - 1].handler;
-+	handler_data  = ec->events.handler[event->rqid - 1].data;
-+	spin_unlock_irqrestore(&ec->events.lock, flags);
 +
-+	/*
-+	 * During handler removal or driver release, we ensure every event gets
-+	 * handled before return of that function. Thus a handler obtained here is
-+	 * guaranteed to be valid at least until this function returns.
-+	 */
++static struct gsb_data_rqsx
++*san_validate_rqsx(struct device *dev, const char *type, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *rqsx = &buffer->data.rqsx;
 +
-+	if (handler) {
-+		status = handler(event, handler_data);
-+	} else {
-+		dev_warn(dev, SG5_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid);
++	if (buffer->len < 8) {
++		dev_err(dev, "invalid %s package (len = %d)\n",
++			type, buffer->len);
++		return NULL;
 +	}
 +
-+	if (status) {
-+		dev_err(dev, SG5_EVENT_TAG "error handling event: %d\n", status);
++	if (rqsx->cdl != buffer->len - 8) {
++		dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n",
++			type, buffer->len, rqsx->cdl);
++		return NULL;
 +	}
 +
-+	if (refcount_dec_and_test(&work->refcount)) {
-+		kfree(work);
++	if (rqsx->tid != 0x01) {
++		dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n",
++			 type, rqsx->tid);
++		return NULL;
 +	}
++
++	return rqsx;
 +}
 +
-+static void surfacegen5_ssh_handle_event(struct surfacegen5_ec *ec, const u8 *buf)
++static acpi_status
++san_etwl(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
 +{
-+	struct device *dev = &ec->serdev->dev;
-+	const struct surfacegen5_frame_ctrl *ctrl;
-+	const struct surfacegen5_frame_cmd *cmd;
-+	struct surfacegen5_event_work *work;
-+	unsigned long flags;
-+	u16 pld_len;
++	struct gsb_data_etwl *etwl = &buffer->data.etwl;
 +
-+	surfacegen5_ec_event_handler_delay delay_fn;
-+	void *handler_data;
-+	unsigned long delay = 0;
++	if (buffer->len < 3) {
++		dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len);
++		return AE_OK;
++	}
 +
-+	ctrl = (const struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL);
-+	cmd  = (const struct surfacegen5_frame_cmd  *)(buf + SG5_FRAME_OFFS_CMD);
++	dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n",
++		etwl->etw3, etwl->etw4,
++		buffer->len - 3, (char *)etwl->msg);
 +
-+	pld_len = ctrl->len - SG5_BYTELEN_CMDFRAME;
++	// indicate success
++	buffer->status = 0x00;
++	buffer->len = 0x00;
 +
-+	work = kzalloc(sizeof(struct surfacegen5_event_work) + pld_len, GFP_ATOMIC);
-+	if (!work) {
-+		dev_warn(dev, SG5_EVENT_TAG "failed to allocate memory, dropping event\n");
-+		return;
-+	}
++	return AE_OK;
++}
 +
-+	refcount_set(&work->refcount, 2);
-+	work->ec         = ec;
-+	work->seq        = ctrl->seq;
-+	work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo;
-+	work->event.tc   = cmd->tc;
-+	work->event.iid  = cmd->iid;
-+	work->event.cid  = cmd->cid;
-+	work->event.len  = pld_len;
-+	work->event.pld  = ((u8*) work) + sizeof(struct surfacegen5_event_work);
++static acpi_status
++san_rqst(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *gsb_rqst = san_validate_rqsx(ctx->dev, "RQST", buffer);
++	struct surface_sam_ssh_rqst rqst = {};
++	struct surface_sam_ssh_buf result = {};
++	int status = 0;
++	int try;
 +
-+	memcpy(work->event.pld, buf + SG5_FRAME_OFFS_CMD_PLD, pld_len);
++	if (!gsb_rqst) {
++		return AE_OK;
++	}
 +
-+	INIT_WORK(&work->work_ack, surfacegen5_event_work_ack_handler);
-+	queue_work(ec->events.queue_ack, &work->work_ack);
++	rqst.tc  = gsb_rqst->tc;
++	rqst.iid = gsb_rqst->iid;
++	rqst.cid = gsb_rqst->cid;
++	rqst.snc = gsb_rqst->snc;
++	rqst.cdl = gsb_rqst->cdl;
++	rqst.pld = &gsb_rqst->pld[0];
 +
-+	spin_lock_irqsave(&ec->events.lock, flags);
-+	handler_data = ec->events.handler[work->event.rqid - 1].data;
-+	delay_fn     = ec->events.handler[work->event.rqid - 1].delay;
-+	if (delay_fn) {
-+		delay = delay_fn(&work->event, handler_data);
++	result.cap  = SURFACE_SAM_SSH_MAX_RQST_RESPONSE;
++	result.len  = 0;
++	result.data = kzalloc(result.cap, GFP_KERNEL);
++
++	if (!result.data) {
++		return AE_NO_MEMORY;
 +	}
-+	spin_unlock_irqrestore(&ec->events.lock, flags);
 +
-+	// immediate execution for high priority events (e.g. keyboard)
-+	if (delay == SURFACEGEN5_EVENT_IMMEDIATE) {
-+		surfacegen5_event_work_evt_handler(&work->work_evt.work);
-+	} else {
-+		INIT_DELAYED_WORK(&work->work_evt, surfacegen5_event_work_evt_handler);
-+		queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay);
++	for (try = 0; try < SAN_RQST_RETRY; try++) {
++		if (try) {
++			dev_warn(ctx->dev, SAN_RQST_TAG "IO error occured, trying again\n");
++		}
++
++		status = surface_sam_ssh_rqst(&rqst, &result);
++		if (status != -EIO) break;
 +	}
-+}
 +
-+static int surfacegen5_ssh_receive_msg_ctrl(struct surfacegen5_ec *ec,
-+                                            const u8 *buf, size_t size)
-+{
-+	struct device *dev = &ec->serdev->dev;
-+	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
-+	const struct surfacegen5_frame_ctrl *ctrl;
-+	struct surfacegen5_fifo_packet packet;
++	if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) {
++		/* Base state quirk:
++		 * The base state may be queried from ACPI when the EC is still
++		 * suspended. In this case it will return '-EPERM'. This query
++		 * will only be triggered from the ACPI lid GPE interrupt, thus
++		 * we are either in laptop or studio mode (base status 0x01 or
++		 * 0x02). Furthermore, we will only get here if the device (and
++		 * EC) have been suspended.
++		 *
++		 * We now assume that the device is in laptop mode (0x01). This
++		 * has the drawback that it will wake the device when unfolding
++		 * it in studio mode, but it also allows us to avoid actively
++		 * waiting for the EC to wake up, which may incur a notable
++		 * delay.
++		 */
 +
-+	const u8 *ctrl_begin = buf + SG5_FRAME_OFFS_CTRL;
-+	const u8 *ctrl_end   = buf + SG5_FRAME_OFFS_CTRL_CRC;
++		buffer->status          = 0x00;
++		buffer->len             = 0x03;
++		buffer->data.out.status = 0x00;
++		buffer->data.out.len    = 0x01;
++		buffer->data.out.pld[0] = 0x01;
 +
-+	ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin);
++	} else if (!status) {		// success
++		buffer->status          = 0x00;
++		buffer->len             = result.len + 2;
++		buffer->data.out.status = 0x00;
++		buffer->data.out.len    = result.len;
++		memcpy(&buffer->data.out.pld[0], result.data, result.len);
 +
-+	// actual length check
-+	if (size < SG5_MSG_LEN_CTRL) {
-+		return 0;			// need more bytes
++	} else {			// failure
++		dev_err(ctx->dev, SAN_RQST_TAG "failed with error %d\n", status);
++		buffer->status          = 0x00;
++		buffer->len             = 0x02;
++		buffer->data.out.status = 0x01;		// indicate _SSH error
++		buffer->data.out.len    = 0x00;
 +	}
 +
-+	// validate TERM
-+	if (!surfacegen5_ssh_is_valid_ter(buf + SG5_FRAME_OFFS_TERM)) {
-+		dev_err(dev, SG5_RECV_TAG "invalid end of message\n");
-+		return size;			// discard everything
-+	}
++	kfree(result.data);
 +
-+	// validate CRC
-+	if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
-+		dev_err(dev, SG5_RECV_TAG "invalid checksum (ctrl)\n");
-+		return SG5_MSG_LEN_CTRL;	// only discard message
-+	}
++	return AE_OK;
++}
 +
-+	// check if we expect the message
-+	if (rcv->state != SG5_RCV_CONTROL) {
-+		dev_err(dev, SG5_RECV_TAG "discarding message: ctrl not expected\n");
-+		return SG5_MSG_LEN_CTRL;	// discard message
-+	}
++static acpi_status
++san_rqsg(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *rqsg = san_validate_rqsx(ctx->dev, "RQSG", buffer);
 +
-+	// check if it is for our request
-+	if (ctrl->type == SG5_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) {
-+		dev_err(dev, SG5_RECV_TAG "discarding message: ack does not match\n");
-+		return SG5_MSG_LEN_CTRL;	// discard message
++	if (!rqsg) {
++		return AE_OK;
 +	}
 +
-+	// we now have a valid & expected ACK/RETRY message
-+	dev_dbg(dev, SG5_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type);
++	// TODO: RQSG handler
 +
-+	packet.type = ctrl->type;
-+	packet.seq  = ctrl->seq;
-+	packet.len  = 0;
++	dev_warn(ctx->dev, "unsupported request: RQSG(0x%02x, 0x%02x, 0x%02x)\n",
++		 rqsg->tc, rqsg->cid, rqsg->iid);
 +
-+	if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) {
-+		kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet));
++	return AE_OK;
++}
 +
-+	} else {
-+		dev_warn(dev, SG5_RECV_TAG
-+			 "dropping frame: not enough space in fifo (type = %d)\n",
-+			 SG5_FRAME_TYPE_CMD);
 +
-+		return SG5_MSG_LEN_CTRL;	// discard message
++static acpi_status
++san_opreg_handler(u32 function, acpi_physical_address command,
++		  u32 bits, u64 *value64,
++		  void *opreg_context, void *region_context)
++{
++	struct san_opreg_context *context = opreg_context;
++	struct gsb_buffer *buffer = (struct gsb_buffer *)value64;
++	int accessor_type = (0xFFFF0000 & function) >> 16;
++
++	if (command != 0) {
++		dev_warn(context->dev, "unsupported command: 0x%02llx\n", command);
++		return AE_OK;
 +	}
 +
-+	// update decoder state
-+	if (ctrl->type == SG5_FRAME_TYPE_ACK) {
-+		rcv->state = rcv->expect.pld
-+			? SG5_RCV_COMMAND
-+			: SG5_RCV_DISCARD;
++	if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) {
++		dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type);
++		return AE_OK;
 +	}
 +
-+	complete(&rcv->signal);
-+	return SG5_MSG_LEN_CTRL;		// handled message
++	// buffer must have at least contain the command-value
++	if (buffer->len == 0) {
++		dev_err(context->dev, "request-package too small\n");
++		return AE_OK;
++	}
++
++	switch (buffer->data.in.cv) {
++	case 0x01:  return san_rqst(context, buffer);
++	case 0x02:  return san_etwl(context, buffer);
++	case 0x03:  return san_rqsg(context, buffer);
++	}
++
++	dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv);
++	return AE_OK;
 +}
 +
-+static int surfacegen5_ssh_receive_msg_cmd(struct surfacegen5_ec *ec,
-+                                           const u8 *buf, size_t size)
++static int san_enable_events(struct device *dev)
 +{
-+	struct device *dev = &ec->serdev->dev;
-+	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
-+	const struct surfacegen5_frame_ctrl *ctrl;
-+	const struct surfacegen5_frame_cmd *cmd;
-+	struct surfacegen5_fifo_packet packet;
-+
-+	const u8 *ctrl_begin     = buf + SG5_FRAME_OFFS_CTRL;
-+	const u8 *ctrl_end       = buf + SG5_FRAME_OFFS_CTRL_CRC;
-+	const u8 *cmd_begin      = buf + SG5_FRAME_OFFS_CMD;
-+	const u8 *cmd_begin_pld  = buf + SG5_FRAME_OFFS_CMD_PLD;
-+	const u8 *cmd_end;
-+
-+	size_t msg_len;
++	int status;
 +
-+	ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin);
-+	cmd  = (const struct surfacegen5_frame_cmd  *)(cmd_begin);
++	status = surface_sam_ssh_set_delayed_event_handler(
++			SAM_EVENT_PWR_RQID, san_evt_power,
++			san_evt_power_delay, dev);
++	if (status) {
++		goto err_handler_power;
++	}
 +
-+	// we need at least a full control frame
-+	if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL + SG5_BYTELEN_CRC)) {
-+		return 0;		// need more bytes
++	status = surface_sam_ssh_set_event_handler(
++			SAM_EVENT_TEMP_RQID, san_evt_thermal,
++			dev);
++	if (status) {
++		goto err_handler_thermal;
 +	}
 +
-+	// validate control-frame CRC
-+	if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
-+		dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-ctrl)\n");
-+		/*
-+		 * We can't be sure here if length is valid, thus
-+		 * discard everything.
-+		 */
-+		return size;
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++	if (status) {
++		goto err_source_power;
 +	}
 +
-+	// actual length check (ctrl->len contains command-frame but not crc)
-+	msg_len = SG5_MSG_LEN_CMD_BASE + ctrl->len;
-+	if (size < msg_len) {
-+		return 0;			// need more bytes
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID);
++	if (status) {
++		goto err_source_thermal;
 +	}
 +
-+	cmd_end = cmd_begin + ctrl->len;
++	return 0;
 +
-+	// validate command-frame type
-+	if (cmd->type != SG5_FRAME_TYPE_CMD) {
-+		dev_err(dev, SG5_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type);
-+		return size;			// discard everything
-+	}
++err_source_thermal:
++	surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++err_source_power:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID);
++err_handler_thermal:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID);
++err_handler_power:
++	return status;
++}
 +
-+	// validate command-frame CRC
-+	if (!surfacegen5_ssh_is_valid_crc(cmd_begin, cmd_end)) {
-+		dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-pld)\n");
++static void san_disable_events(void)
++{
++	surface_sam_ssh_disable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID);
++	surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID);
++}
 +
-+		/*
-+		 * The message length is provided in the control frame. As we
-+		 * already validated that, we can be sure here that it's
-+		 * correct, so we only need to discard the message.
-+		 */
-+		return msg_len;
-+	}
 +
-+	// check if we received an event notification
-+	if (surfacegen5_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) {
-+		surfacegen5_ssh_handle_event(ec, buf);
-+		return msg_len;			// handled message
-+	}
++static int san_consumers_link(struct platform_device *pdev,
++			      const struct san_acpi_consumer *cons,
++			      struct san_consumers *out)
++{
++	const struct san_acpi_consumer *con;
++	struct san_consumer_link *links, *link;
++	struct acpi_device *adev;
++	acpi_handle handle;
++	u32 max_links = 0;
++	int status;
 +
-+	// check if we expect the message
-+	if (rcv->state != SG5_RCV_COMMAND) {
-+		dev_dbg(dev, SG5_RECV_TAG "discarding message: command not expected\n");
-+		return msg_len;			// discard message
++	if (!cons) {
++		return 0;
 +	}
 +
-+	// check if response is for our request
-+	if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) {
-+		dev_dbg(dev, SG5_RECV_TAG "discarding message: command not a match\n");
-+		return msg_len;			// discard message
++	// count links
++	for (con = cons; con->path; ++con) {
++		max_links += 1;
 +	}
 +
-+	// we now have a valid & expected command message
-+	dev_dbg(dev, SG5_RECV_TAG "valid command message received\n");
++	// allocate
++	links = kzalloc(max_links * sizeof(struct san_consumer_link), GFP_KERNEL);
++	link = &links[0];
 +
-+	packet.type = ctrl->type;
-+	packet.seq = ctrl->seq;
-+	packet.len = cmd_end - cmd_begin_pld;
++	if (!links) {
++		return -ENOMEM;
++	}
 +
-+	if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) {
-+		kfifo_in(&rcv->fifo, &packet, sizeof(packet));
-+		kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len);
++	// create links
++	for (con = cons; con->path; ++con) {
++		status = acpi_get_handle(NULL, con->path, &handle);
++		if (status) {
++			if (con->required || status != AE_NOT_FOUND) {
++				status = -ENXIO;
++				goto cleanup;
++			} else {
++				continue;
++			}
++		}
 +
-+	} else {
-+		dev_warn(dev, SG5_RECV_TAG
-+			 "dropping frame: not enough space in fifo (type = %d)\n",
-+			 SG5_FRAME_TYPE_CMD);
++		status = acpi_bus_get_device(handle, &adev);
++		if (status) {
++			goto cleanup;
++		}
 +
-+		return SG5_MSG_LEN_CTRL;	// discard message
++		link->link = device_link_add(&adev->dev, &pdev->dev, con->flags);
++		if (!(link->link)) {
++			status = -EFAULT;
++			goto cleanup;
++		}
++		link->properties = con;
++
++		link += 1;
 +	}
 +
-+	rcv->state = SG5_RCV_DISCARD;
++	out->num = link - links;
++	out->links = links;
++
++	return 0;
 +
-+	complete(&rcv->signal);
-+	return msg_len;				// handled message
++cleanup:
++	for (link = link - 1; link >= links; --link) {
++		if (link->properties->flags & DL_FLAG_STATELESS) {
++			device_link_del(link->link);
++		}
++	}
++
++	return status;
 +}
 +
-+static int surfacegen5_ssh_eval_buf(struct surfacegen5_ec *ec,
-+                                    const u8 *buf, size_t size)
-+{
-+	struct device *dev = &ec->serdev->dev;
-+	struct surfacegen5_frame_ctrl *ctrl;
++static void san_consumers_unlink(struct san_consumers *consumers) {
++	u32 i;
 +
-+	// we need at least a control frame to check what to do
-+	if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL)) {
-+		return 0;		// need more bytes
++	if (!consumers) {
++		return;
 +	}
 +
-+	// make sure we're actually at the start of a new message
-+	if (!surfacegen5_ssh_is_valid_syn(buf)) {
-+		dev_err(dev, SG5_RECV_TAG "invalid start of message\n");
-+		return size;		// discard everything
++	for (i = 0; i < consumers->num; ++i) {
++		if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) {
++			device_link_del(consumers->links[i].link);
++		}
 +	}
 +
-+	// handle individual message types seperately
-+	ctrl = (struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL);
-+
-+	switch (ctrl->type) {
-+	case SG5_FRAME_TYPE_ACK:
-+	case SG5_FRAME_TYPE_RETRY:
-+		return surfacegen5_ssh_receive_msg_ctrl(ec, buf, size);
-+
-+	case SG5_FRAME_TYPE_CMD:
-+		return surfacegen5_ssh_receive_msg_cmd(ec, buf, size);
++	kfree(consumers->links);
 +
-+	default:
-+		dev_err(dev, SG5_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type);
-+		return size;		// discard everything
-+	}
++	consumers->num = 0;
++	consumers->links = NULL;
 +}
 +
-+static int surfacegen5_ssh_receive_buf(struct serdev_device *serdev,
-+                                       const unsigned char *buf, size_t size)
++static int surface_sam_san_probe(struct platform_device *pdev)
 +{
-+	struct surfacegen5_ec *ec = serdev_device_get_drvdata(serdev);
-+	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
-+	unsigned long flags;
-+	int offs = 0;
-+	int used, n;
-+
-+	dev_dbg(&serdev->dev, SG5_RECV_TAG "received buffer (size: %zu)\n", size);
-+	print_hex_dump_debug(SG5_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false);
++	const struct san_acpi_consumer *cons;
++	struct san_drvdata *drvdata;
++	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
++	int status;
 +
 +	/*
-+	 * The battery _BIX message gets a bit long, thus we have to add some
-+	 * additional buffering here.
++	 * Defer probe if the _SSH driver has not set up the controller yet. This
++	 * makes sure we do not fail any initial requests (e.g. _STA request without
++	 * which the battery does not get set up correctly). Otherwise register as
++	 * consumer to set up a device_link.
 +	 */
-+
-+	spin_lock_irqsave(&rcv->lock, flags);
-+
-+	// copy to eval-buffer
-+	used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len));
-+	memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used);
-+	rcv->eval_buf.len += used;
-+
-+	// evaluate buffer until we need more bytes or eval-buf is empty
-+	while (offs < rcv->eval_buf.len) {
-+		n = rcv->eval_buf.len - offs;
-+		n = surfacegen5_ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n);
-+		if (n <= 0) break;	// need more bytes
-+
-+		offs += n;
-+	}
-+
-+	// throw away the evaluated parts
-+	rcv->eval_buf.len -= offs;
-+	memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len);
-+
-+	spin_unlock_irqrestore(&rcv->lock, flags);
-+
-+	return used;
-+}
-+
-+
-+static acpi_status
-+surfacegen5_ssh_setup_from_resource(struct acpi_resource *resource, void *context)
-+{
-+	struct serdev_device *serdev = context;
-+	struct acpi_resource_common_serialbus *serial;
-+	struct acpi_resource_uart_serialbus *uart;
-+	int status = 0;
-+
-+	if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) {
-+		return AE_OK;
-+	}
-+
-+	serial = &resource->data.common_serial_bus;
-+	if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) {
-+		return AE_OK;
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
 +	}
 +
-+	uart = &resource->data.uart_serial_bus;
-+
-+	// set up serdev device
-+	serdev_device_set_baudrate(serdev, uart->default_baud_rate);
-+
-+	// serdev currently only supports RTSCTS flow control
-+	if (uart->flow_control & SG5_SUPPORTED_FLOW_CONTROL_MASK) {
-+		dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control);
++	drvdata = kzalloc(sizeof(struct san_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		return -ENOMEM;
 +	}
 +
-+	// set RTSCTS flow control
-+	serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW);
-+
-+	// serdev currently only supports EVEN/ODD parity
-+	switch (uart->parity) {
-+	case ACPI_UART_PARITY_NONE:
-+		status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
-+		break;
-+	case ACPI_UART_PARITY_EVEN:
-+		status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);
-+		break;
-+	case ACPI_UART_PARITY_ODD:
-+		status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);
-+		break;
-+	default:
-+		dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity);
-+		break;
-+	}
++	drvdata->opreg_ctx.dev = &pdev->dev;
 +
++	cons = acpi_device_get_match_data(&pdev->dev);
++	status = san_consumers_link(pdev, cons, &drvdata->consumers);
 +	if (status) {
-+		dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity);
-+		return status;
++		goto err_consumers;
 +	}
 +
-+	return AE_CTRL_TERMINATE;       // we've found the resource and are done
-+}
-+
-+
-+static bool surfacegen5_idma_filter(struct dma_chan *chan, void *param)
-+{
-+	// see dw8250_idma_filter
-+	return param == chan->device->dev;
-+}
-+
-+static int surfacegen5_ssh_check_dma(struct serdev_device *serdev)
-+{
-+	struct device *dev = serdev->ctrl->dev.parent;
-+	struct dma_chan *rx, *tx;
-+	dma_cap_mask_t mask;
-+	int status = 0;
-+
-+	/*
-+	 * The EC UART requires DMA for proper communication. If we don't use DMA,
-+	 * we'll drop bytes when the system has high load, e.g. during boot. This
-+	 * causes some ugly behaviour, i.e. battery information (_BIX) messages
-+	 * failing frequently. We're making sure the required DMA channels are
-+	 * available here so serial8250_do_startup is able to grab them later
-+	 * instead of silently falling back to a non-DMA approach.
-+	 */
++	platform_set_drvdata(pdev, drvdata);
 +
-+	dma_cap_zero(mask);
-+	dma_cap_set(DMA_SLAVE, mask);
++	status = acpi_install_address_space_handler(san,
++			ACPI_ADR_SPACE_GSBUS,
++			&san_opreg_handler,
++			NULL, &drvdata->opreg_ctx);
 +
-+	rx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "rx");
-+	if (IS_ERR_OR_NULL(rx)) {
-+		status = rx ? PTR_ERR(rx) : -EPROBE_DEFER;
-+		if (status != -EPROBE_DEFER) {
-+			dev_err(&serdev->dev, "sg5_dma: error requesting rx channel: %d\n", status);
-+		} else {
-+			dev_dbg(&serdev->dev, "sg5_dma: rx channel not found, deferring probe\n");
-+		}
-+		goto check_dma_out;
++	if (ACPI_FAILURE(status)) {
++		status = -ENODEV;
++		goto err_install_handler;
 +	}
 +
-+	tx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "tx");
-+	if (IS_ERR_OR_NULL(tx)) {
-+		status = tx ? PTR_ERR(tx) : -EPROBE_DEFER;
-+		if (status != -EPROBE_DEFER) {
-+			dev_err(&serdev->dev, "sg5_dma: error requesting tx channel: %d\n", status);
-+		} else {
-+			dev_dbg(&serdev->dev, "sg5_dma: tx channel not found, deferring probe\n");
-+		}
-+		goto check_dma_release_rx;
++	status = san_enable_events(&pdev->dev);
++	if (status) {
++		goto err_enable_events;
 +	}
 +
-+	dma_release_channel(tx);
-+check_dma_release_rx:
-+	dma_release_channel(rx);
-+check_dma_out:
++	acpi_walk_dep_device_list(san);
++	return 0;
++
++err_enable_events:
++	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler);
++err_install_handler:
++	platform_set_drvdata(san, NULL);
++	san_consumers_unlink(&drvdata->consumers);
++err_consumers:
++	kfree(drvdata);
 +	return status;
 +}
 +
-+
-+static int surfacegen5_ssh_suspend(struct device *dev)
++static int surface_sam_san_remove(struct platform_device *pdev)
 +{
-+	struct surfacegen5_ec *ec;
-+	int status = 0;
-+
-+	dev_dbg(dev, "suspending\n");
++	struct san_drvdata *drvdata = platform_get_drvdata(pdev);
++	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
++	acpi_status status = AE_OK;
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (ec) {
-+		status = surfacegen5_ssh_ec_suspend(ec);
-+		if (status) {
-+			dev_err(dev, "failed to suspend EC: %d\n", status);
-+		}
++	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler);
++	san_disable_events();
 +
-+		ec->state = SG5_EC_SUSPENDED;
-+		surfacegen5_ec_release(ec);
-+	}
++	san_consumers_unlink(&drvdata->consumers);
++	kfree(drvdata);
 +
++	platform_set_drvdata(pdev, NULL);
 +	return status;
 +}
 +
-+static int surfacegen5_ssh_resume(struct device *dev)
-+{
-+	struct surfacegen5_ec *ec;
-+	int status = 0;
 +
-+	dev_dbg(dev, "resuming\n");
++static const struct san_acpi_consumer san_mshw0091_consumers[] = {
++	{ "\\_SB.SRTC", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\ADP1",     true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\_SB.BAT1", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ },
++};
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (ec) {
-+		ec->state = SG5_EC_INITIALIZED;
++static const struct acpi_device_id surface_sam_san_match[] = {
++	{ "MSHW0091", (long unsigned int) san_mshw0091_consumers },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_san_match);
 +
-+		status = surfacegen5_ssh_ec_resume(ec);
-+		if (status) {
-+			dev_err(dev, "failed to resume EC: %d\n", status);
-+		}
++struct platform_driver surface_sam_san = {
++	.probe = surface_sam_san_probe,
++	.remove = surface_sam_san_remove,
++	.driver = {
++		.name = "surface_sam_san",
++		.acpi_match_table = ACPI_PTR(surface_sam_san_match),
++	},
++};
++module_platform_driver(surface_sam_san);
 +
-+		surfacegen5_ec_release(ec);
-+	}
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid.c b/drivers/platform/x86/surface_sam/surface_sam_sid.c
+new file mode 100644
+index 000000000000..ff440619bcf2
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_sid.c
+@@ -0,0 +1,483 @@
++/*
++ * Surface/System Integration Device (SID) driver.
++ * Intended for device-dependent configuration and minor functionality.
++ * Handles performance-modes and wakeup via lid open.
++ */
 +
-+	return status;
-+}
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/dmi.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/platform_device.h>
++#include <linux/sysfs.h>
 +
-+static SIMPLE_DEV_PM_OPS(surfacegen5_ssh_pm_ops, surfacegen5_ssh_suspend, surfacegen5_ssh_resume);
++#include "surface_sam_ssh.h"
 +
 +
-+static const struct serdev_device_ops surfacegen5_ssh_device_ops = {
-+	.receive_buf  = surfacegen5_ssh_receive_buf,
-+	.write_wakeup = serdev_device_write_wakeup,
++struct sid_lid_device {
++	const char *acpi_path;
++	const u32 gpe_number;
 +};
 +
-+static int surfacegen5_acpi_ssh_probe(struct serdev_device *serdev)
-+{
-+	struct surfacegen5_ec *ec;
-+	struct workqueue_struct *event_queue_ack;
-+	struct workqueue_struct *event_queue_evt;
-+	u8 *write_buf;
-+	u8 *read_buf;
-+	u8 *eval_buf;
-+	acpi_handle *ssh = ACPI_HANDLE(&serdev->dev);
-+	acpi_status status;
-+
-+	dev_dbg(&serdev->dev, "probing\n");
++struct sid_device_info {
++	const bool has_perf_mode;
++	const struct sid_lid_device *lid_device;
++};
 +
-+	// ensure DMA is ready before we set up the device
-+	status = surfacegen5_ssh_check_dma(serdev);
-+	if (status) {
-+		return status;
-+	}
 +
-+	// allocate buffers
-+	write_buf = kzalloc(SG5_WRITE_BUF_LEN, GFP_KERNEL);
-+	if (!write_buf) {
-+		status = -ENOMEM;
-+		goto err_probe_write_buf;
-+	}
++static const struct sid_lid_device lid_device_l17 = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x17,
++};
 +
-+	read_buf = kzalloc(SG5_READ_BUF_LEN, GFP_KERNEL);
-+	if (!read_buf) {
-+		status = -ENOMEM;
-+		goto err_probe_read_buf;
-+	}
++static const struct sid_lid_device lid_device_l4F = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x4F,
++};
 +
-+	eval_buf = kzalloc(SG5_EVAL_BUF_LEN, GFP_KERNEL);
-+	if (!eval_buf) {
-+		status = -ENOMEM;
-+		goto err_probe_eval_buf;
-+	}
++static const struct sid_lid_device lid_device_l57 = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x57,
++};
 +
-+	event_queue_ack = create_singlethread_workqueue("sg5_ackq");
-+	if (!event_queue_ack) {
-+		status = -ENOMEM;
-+		goto err_probe_ackq;
-+	}
 +
-+	event_queue_evt = create_workqueue("sg5_evtq");
-+	if (!event_queue_evt) {
-+		status = -ENOMEM;
-+		goto err_probe_evtq;
-+	}
++static const struct sid_device_info si_device_pro_4 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l17,
++};
 +
-+	// set up EC
-+	ec = surfacegen5_ec_acquire();
-+	if (ec->state != SG5_EC_UNINITIALIZED) {
-+		dev_err(&serdev->dev, "embedded controller already initialized\n");
-+		surfacegen5_ec_release(ec);
++static const struct sid_device_info si_device_pro_5 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l4F,
++};
 +
-+		status = -EBUSY;
-+		goto err_probe_busy;
-+	}
++static const struct sid_device_info si_device_pro_6 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l4F,
++};
 +
-+	ec->serdev      = serdev;
-+	ec->writer.data = write_buf;
-+	ec->writer.ptr  = write_buf;
++static const struct sid_device_info si_device_book_1 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l17,
++};
 +
-+	// initialize receiver
-+	init_completion(&ec->receiver.signal);
-+	kfifo_init(&ec->receiver.fifo, read_buf, SG5_READ_BUF_LEN);
-+	ec->receiver.eval_buf.ptr = eval_buf;
-+	ec->receiver.eval_buf.cap = SG5_EVAL_BUF_LEN;
-+	ec->receiver.eval_buf.len = 0;
++static const struct sid_device_info si_device_book_2 = {
++	.has_perf_mode = true,
++	.lid_device = &lid_device_l17,
++};
 +
-+	// initialize event handling
-+	ec->events.queue_ack = event_queue_ack;
-+	ec->events.queue_evt = event_queue_evt;
++static const struct sid_device_info si_device_laptop_1 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l57,
++};
 +
-+	ec->state = SG5_EC_INITIALIZED;
++static const struct sid_device_info si_device_laptop_2 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l57,
++};
 +
-+	serdev_device_set_drvdata(serdev, ec);
 +
-+	// ensure everything is properly set-up before we open the device
-+	smp_mb();
-+
-+	serdev_device_set_client_ops(serdev, &surfacegen5_ssh_device_ops);
-+	status = serdev_device_open(serdev);
-+	if (status) {
-+		goto err_probe_open;
-+	}
++static const struct dmi_system_id dmi_lid_device_table[] = {
++	{
++		.ident = "Surface Pro 4",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
++		},
++		.driver_data = (void *)&si_device_pro_4,
++	},
++	{
++		.ident = "Surface Pro 5",
++		.matches = {
++			/* match for SKU here due to generic product name "Surface Pro" */
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
++		},
++		.driver_data = (void *)&si_device_pro_5,
++	},
++	{
++		.ident = "Surface Pro 5 (LTE)",
++		.matches = {
++			/* match for SKU here due to generic product name "Surface Pro" */
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
++		},
++		.driver_data = (void *)&si_device_pro_5,
++	},
++	{
++		.ident = "Surface Pro 6",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
++		},
++		.driver_data = (void *)&si_device_pro_6,
++	},
++	{
++		.ident = "Surface Book 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
++		},
++		.driver_data = (void *)&si_device_book_1,
++	},
++	{
++		.ident = "Surface Book 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
++		},
++		.driver_data = (void *)&si_device_book_2,
++	},
++	{
++		.ident = "Surface Laptop 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
++		},
++		.driver_data = (void *)&si_device_laptop_1,
++	},
++	{
++		.ident = "Surface Laptop 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
++		},
++		.driver_data = (void *)&si_device_laptop_2,
++	},
++	{ }
++};
 +
-+	status = acpi_walk_resources(ssh, METHOD_NAME__CRS,
-+	                             surfacegen5_ssh_setup_from_resource, serdev);
-+	if (ACPI_FAILURE(status)) {
-+		goto err_probe_devinit;
-+	}
 +
-+	status = surfacegen5_ssh_ec_resume(ec);
-+	if (status) {
-+		goto err_probe_devinit;
-+	}
++#define SID_PARAM_PERM		(S_IRUGO | S_IWUSR)
 +
-+	status = surfacegen5_ssh_sysfs_register(&serdev->dev);
-+	if (status) {
-+		goto err_probe_devinit;
-+	}
++enum sam_perf_mode {
++	SAM_PERF_MODE_NORMAL   = 1,
++	SAM_PERF_MODE_BATTERY  = 2,
++	SAM_PERF_MODE_PERF1    = 3,
++	SAM_PERF_MODE_PERF2    = 4,
 +
-+	surfacegen5_ec_release(ec);
++	__SAM_PERF_MODE__START = 1,
++	__SAM_PERF_MODE__END   = 4,
++};
 +
-+	acpi_walk_dep_device_list(ssh);
++enum sid_param_perf_mode {
++	SID_PARAM_PERF_MODE_AS_IS    = 0,
++	SID_PARAM_PERF_MODE_NORMAL   = SAM_PERF_MODE_NORMAL,
++	SID_PARAM_PERF_MODE_BATTERY  = SAM_PERF_MODE_BATTERY,
++	SID_PARAM_PERF_MODE_PERF1    = SAM_PERF_MODE_PERF1,
++	SID_PARAM_PERF_MODE_PERF2    = SAM_PERF_MODE_PERF2,
 +
-+	return 0;
++	__SID_PARAM_PERF_MODE__START = 0,
++	__SID_PARAM_PERF_MODE__END   = 4,
++};
 +
-+err_probe_devinit:
-+	serdev_device_close(serdev);
-+err_probe_open:
-+	ec->state = SG5_EC_UNINITIALIZED;
-+	serdev_device_set_drvdata(serdev, NULL);
-+	surfacegen5_ec_release(ec);
-+err_probe_busy:
-+	destroy_workqueue(event_queue_evt);
-+err_probe_evtq:
-+	destroy_workqueue(event_queue_ack);
-+err_probe_ackq:
-+	kfree(eval_buf);
-+err_probe_eval_buf:
-+	kfree(read_buf);
-+err_probe_read_buf:
-+	kfree(write_buf);
-+err_probe_write_buf:
-+	return status;
-+}
 +
-+static void surfacegen5_acpi_ssh_remove(struct serdev_device *serdev)
++static int surface_sam_perf_mode_get(void)
 +{
-+	struct surfacegen5_ec *ec;
-+	unsigned long flags;
++	u8 result_buf[8] = { 0 };
 +	int status;
 +
-+	ec = surfacegen5_ec_acquire_init();
-+	if (!ec) {
-+		return;
-+	}
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x02,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
 +
-+	surfacegen5_ssh_sysfs_unregister(&serdev->dev);
++	struct surface_sam_ssh_buf result = {
++		.cap = ARRAY_SIZE(result_buf),
++		.len = 0,
++		.data = result_buf,
++	};
 +
-+	// suspend EC and disable events
-+	status = surfacegen5_ssh_ec_suspend(ec);
++	status = surface_sam_ssh_rqst(&rqst, &result);
 +	if (status) {
-+		dev_err(&serdev->dev, "failed to suspend EC: %d\n", status);
++		return status;
 +	}
 +
-+	// make sure all events (received up to now) have been properly handled
-+	flush_workqueue(ec->events.queue_ack);
-+	flush_workqueue(ec->events.queue_evt);
-+
-+	// remove event handlers
-+	spin_lock_irqsave(&ec->events.lock, flags);
-+	memset(ec->events.handler, 0,
-+	       sizeof(struct surfacegen5_ec_event_handler)
-+	        * SG5_NUM_EVENT_TYPES);
-+	spin_unlock_irqrestore(&ec->events.lock, flags);
-+
-+	// set device to deinitialized state
-+	ec->state  = SG5_EC_UNINITIALIZED;
-+	ec->serdev = NULL;
-+
-+	// ensure state and serdev get set before continuing
-+	smp_mb();
-+
-+	/*
-+	 * Flush any event that has not been processed yet to ensure we're not going to
-+	 * use the serial device any more (e.g. for ACKing).
-+	 */
-+	flush_workqueue(ec->events.queue_ack);
-+	flush_workqueue(ec->events.queue_evt);
-+
-+	serdev_device_close(serdev);
-+
-+	/*
-+         * Only at this point, no new events can be received. Destroying the
-+         * workqueue here flushes all remaining events. Those events will be
-+         * silently ignored and neither ACKed nor any handler gets called.
-+	 */
-+	destroy_workqueue(ec->events.queue_ack);
-+	destroy_workqueue(ec->events.queue_evt);
-+
-+	// free writer
-+	kfree(ec->writer.data);
-+	ec->writer.data = NULL;
-+	ec->writer.ptr  = NULL;
-+
-+	// free receiver
-+	spin_lock_irqsave(&ec->receiver.lock, flags);
-+	ec->receiver.state = SG5_RCV_DISCARD;
-+	kfifo_free(&ec->receiver.fifo);
-+
-+	kfree(ec->receiver.eval_buf.ptr);
-+	ec->receiver.eval_buf.ptr = NULL;
-+	ec->receiver.eval_buf.cap = 0;
-+	ec->receiver.eval_buf.len = 0;
-+	spin_unlock_irqrestore(&ec->receiver.lock, flags);
++	if (result.len != 8) {
++		return -EFAULT;
++	}
 +
-+	serdev_device_set_drvdata(serdev, NULL);
-+	surfacegen5_ec_release(ec);
++	return get_unaligned_le32(&result.data[0]);
 +}
 +
++static int surface_sam_perf_mode_set(int perf_mode)
++{
++	u8 payload[4] = { 0 };
 +
-+static const struct acpi_device_id surfacegen5_acpi_ssh_match[] = {
-+	{ "MSHW0084", 0 },
-+	{ },
-+};
-+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_ssh_match);
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x03,
++		.snc = 0x00,
++		.cdl = ARRAY_SIZE(payload),
++		.pld = payload,
++	};
 +
-+struct serdev_device_driver surfacegen5_acpi_ssh = {
-+	.probe = surfacegen5_acpi_ssh_probe,
-+	.remove = surfacegen5_acpi_ssh_remove,
-+	.driver = {
-+		.name = "surfacegen5_acpi_ssh",
-+		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_ssh_match),
-+		.pm = &surfacegen5_ssh_pm_ops,
-+	},
-+};
++	if (perf_mode < __SAM_PERF_MODE__START || perf_mode > __SAM_PERF_MODE__END) {
++		return -EINVAL;
++	}
 +
-+inline int surfacegen5_acpi_ssh_register(void)
-+{
-+	return serdev_device_driver_register(&surfacegen5_acpi_ssh);
++	put_unaligned_le32(perf_mode, &rqst.pld[0]);
++	return surface_sam_ssh_rqst(&rqst, NULL);
 +}
 +
-+inline void surfacegen5_acpi_ssh_unregister(void)
++
++static int param_perf_mode_set(const char *val, const struct kernel_param *kp)
 +{
-+	serdev_device_driver_unregister(&surfacegen5_acpi_ssh);
-+}
++	int perf_mode;
++	int status;
 +
-+#else /* CONFIG_SURFACE_ACPI_SSH */
++	status = kstrtoint(val, 0, &perf_mode);
++	if (status) {
++		return status;
++	}
 +
-+inline int surfacegen5_acpi_ssh_register(void)
-+{
-+	return 0;
-+}
++	if (perf_mode < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_PARAM_PERF_MODE__END) {
++		return -EINVAL;
++	}
 +
-+inline void surfacegen5_acpi_ssh_unregister(void)
-+{
++	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,
++};
 +
-+#endif /* CONFIG_SURFACE_ACPI_SSH */
-+
++static int param_perf_mode_init = SID_PARAM_PERF_MODE_AS_IS;
++static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS;
 +
-+/*************************************************************************
-+ * Surface Serial Hub Debug Device (private implementation)
-+ */
++module_param_cb(perf_mode_init, &param_perf_mode_ops, &param_perf_mode_init, SID_PARAM_PERM);
++module_param_cb(perf_mode_exit, &param_perf_mode_ops, &param_perf_mode_exit, SID_PARAM_PERM);
 +
-+#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE
++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 char sg5_ssh_debug_rqst_buf_sysfs[SURFACEGEN5_MAX_RQST_RESPONSE + 1] = { 0 };
-+static char sg5_ssh_debug_rqst_buf_pld[SURFACEGEN5_MAX_RQST_PAYLOAD] = { 0 };
-+static char sg5_ssh_debug_rqst_buf_res[SURFACEGEN5_MAX_RQST_RESPONSE] = { 0 };
 +
-+static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
-+                         char *buf, loff_t offs, size_t count)
++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data)
 +{
-+	if (offs < 0 || count + offs > SURFACEGEN5_MAX_RQST_RESPONSE) {
-+		return -EINVAL;
++	int perf_mode;
++
++	perf_mode = surface_sam_perf_mode_get();
++	if (perf_mode < 0) {
++		dev_err(dev, "failed to get current performance mode: %d", perf_mode);
++		return -EIO;
 +	}
 +
-+	memcpy(buf, sg5_ssh_debug_rqst_buf_sysfs + offs, count);
-+	return count;
++	return sprintf(data, "%d\n", perf_mode);
 +}
 +
-+static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
-+			  char *buf, loff_t offs, size_t count)
++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr,
++                               const char *data, size_t count)
 +{
-+	struct surfacegen5_rqst rqst = {};
-+	struct surfacegen5_buf result = {};
++	int perf_mode;
 +	int status;
 +
-+	// check basic write constriants
-+	if (offs != 0 || count > SURFACEGEN5_MAX_RQST_PAYLOAD + 5) {
-+		return -EINVAL;
-+	}
-+
-+	// payload length should be consistent with data provided
-+	if (buf[4] + 5 != count) {
-+		return -EINVAL;
++	status = kstrtoint(data, 0, &perf_mode);
++	if (status) {
++		return status;
 +	}
 +
-+	rqst.tc  = buf[0];
-+	rqst.iid = buf[1];
-+	rqst.cid = buf[2];
-+	rqst.snc = buf[3];
-+	rqst.cdl = buf[4];
-+	rqst.pld = sg5_ssh_debug_rqst_buf_pld;
-+	memcpy(sg5_ssh_debug_rqst_buf_pld, buf + 5, count - 5);
-+
-+	result.cap = SURFACEGEN5_MAX_RQST_RESPONSE;
-+	result.len = 0;
-+	result.data = sg5_ssh_debug_rqst_buf_res;
-+
-+	status = surfacegen5_ec_rqst(&rqst, &result);
++	status = surface_sam_perf_mode_set(perf_mode);
 +	if (status) {
 +		return status;
 +	}
 +
-+	sg5_ssh_debug_rqst_buf_sysfs[0] = result.len;
-+	memcpy(sg5_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len);
-+	memset(sg5_ssh_debug_rqst_buf_sysfs + result.len + 1, 0,
-+	       SURFACEGEN5_MAX_RQST_RESPONSE + 1 - result.len);
++	// 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;
 +}
 +
-+static const BIN_ATTR_RW(rqst, SURFACEGEN5_MAX_RQST_RESPONSE + 1);
++const static DEVICE_ATTR_RW(perf_mode);
 +
 +
-+inline int surfacegen5_ssh_sysfs_register(struct device *dev)
++static int sid_perf_mode_setup(struct platform_device *pdev, const struct sid_device_info *info)
 +{
-+	return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst);
-+}
-+
-+inline void surfacegen5_ssh_sysfs_unregister(struct device *dev)
-+{
-+	sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst);
-+}
-+
-+#elif defined(CONFIG_SURFACE_ACPI_SSH)
-+
-+inline int surfacegen5_ssh_sysfs_register(struct device *dev)
-+{
-+	return 0;
-+}
++	int status;
 +
-+inline void surfacegen5_ssh_sysfs_unregister(struct device *dev)
-+{
-+}
++	if (!info->has_perf_mode)
++		return 0;
 +
-+#endif /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE*/
++	// link to ec
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
++	}
 +
++	// set initial perf_mode
++	if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) {
++		status = surface_sam_perf_mode_set(param_perf_mode_init);
++		if (status) {
++			return status;
++		}
++	}
 +
-+/*************************************************************************
-+ * Surface ACPI Notify driver
-+ */
++	// register perf_mode attribute
++	status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++	if (status) {
++		goto err_sysfs;
++	}
 +
-+#ifdef CONFIG_SURFACE_ACPI_SAN
++	return 0;
 +
-+#define SG5_SAN_RQST_RETRY		5
++err_sysfs:
++	surface_sam_perf_mode_set(param_perf_mode_exit);
++	return status;
++}
 +
-+#define SG5_SAN_DSM_REVISION		0
-+#define SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT	0x09
++static void sid_perf_mode_remove(struct platform_device *pdev, const struct sid_device_info *info)
++{
++	if (!info->has_perf_mode)
++		return;
 +
-+static const guid_t SG5_SAN_DSM_UUID =
-+	GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d,
-+	          0x48, 0x7c, 0x91, 0xab, 0x3c);
++	// remove perf_mode attribute
++	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
 +
-+#define SG5_EVENT_DELAY_POWER		msecs_to_jiffies(5000)
++	// set exit perf_mode
++	surface_sam_perf_mode_set(param_perf_mode_exit);
++}
 +
-+#define SG5_EVENT_PWR_TC		0x02
-+#define SG5_EVENT_PWR_RQID		0x0002
-+#define SG5_EVENT_PWR_CID_HWCHANGE	0x15
-+#define SG5_EVENT_PWR_CID_CHARGING	0x16
-+#define SG5_EVENT_PWR_CID_ADAPTER	0x17
-+#define SG5_EVENT_PWR_CID_STATE		0x4f
 +
-+#define SG5_EVENT_TEMP_TC		0x03
-+#define SG5_EVENT_TEMP_RQID		0x0003
-+#define SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT	0x0b
++static int sid_lid_enable_wakeup(const struct sid_device_info *info, bool enable)
++{
++	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
++	int status;
 +
-+#define SG5_SAN_RQST_TAG            	"surfacegen5_ec_rqst: "
++	if (!info->lid_device)
++		return 0;
 +
-+#define SG5_QUIRK_BASE_STATE_DELAY	1000
++	status = acpi_set_gpe_wake_mask(NULL, info->lid_device->gpe_number, action);
++	if (status)
++		return -EFAULT;
 +
++	return 0;
++}
 +
-+struct surfacegen5_san_acpi_consumer {
-+	char *path;
-+	bool  required;
-+	u32   flags;
-+};
++static int sid_lid_device_setup(const struct sid_device_info *info)
++{
++	acpi_handle lid_handle;
++	int status;
 +
-+struct surfacegen5_san_opreg_context {
-+	struct acpi_connection_info connection;
-+	struct device *dev;
-+};
++	if (!info->lid_device)
++		return 0;
 +
-+struct surfacegen5_san_consumer_link {
-+	const struct surfacegen5_san_acpi_consumer *properties;
-+	struct device_link                         *link;
-+};
++	status = acpi_get_handle(NULL, (acpi_string)info->lid_device->acpi_path, &lid_handle);
++	if (status)
++		return -EFAULT;
 +
-+struct surfacegen5_san_consumers {
-+	u32                                   num;
-+	struct surfacegen5_san_consumer_link *links;
-+};
++	status = acpi_setup_gpe_for_wake(lid_handle, NULL, info->lid_device->gpe_number);
++	if (status)
++		return -EFAULT;
 +
-+struct surfacegen5_san_drvdata {
-+	struct surfacegen5_san_opreg_context opreg_ctx;
-+	struct surfacegen5_san_consumers     consumers;
-+};
++	status = acpi_enable_gpe(NULL, info->lid_device->gpe_number);
++	if (status)
++		return -EFAULT;
 +
-+struct gsb_data_in {
-+	u8 cv;
-+} __packed;
++	return sid_lid_enable_wakeup(info, false);
++}
 +
-+struct gsb_data_rqsx {
-+	u8 cv;				// command value (should be 0x01 or 0x03)
-+	u8 tc;				// target controller
-+	u8 tid;				// expected to be 0x01, could be revision
-+	u8 iid;				// target sub-controller (e.g. primary vs. secondary battery)
-+	u8 snc;				// expect-response-flag
-+	u8 cid;				// command ID
-+	u8 cdl;				// payload length
-+	u8 _pad;			// padding
-+	u8 pld[0];			// payload
-+} __packed;
++static void sid_lid_device_remove(const struct sid_device_info *info)
++{
++	/* restore default behavior without this module */
++	sid_lid_enable_wakeup(info, false);
++}
 +
-+struct gsb_data_etwl {
-+	u8 cv;				// command value (should be 0x02)
-+	u8 etw3;			// ?
-+	u8 etw4;			// ?
-+	u8 msg[0];			// error message (ASCIIZ)
-+} __packed;
 +
-+struct gsb_data_out {
-+	u8 status;			// _SSH communication status
-+	u8 len;				// _SSH payload length
-+	u8 pld[0];			// _SSH payload
-+} __packed;
++static int surface_sam_sid_suspend(struct device *dev)
++{
++	const struct sid_device_info *info = dev_get_drvdata(dev);
++	return sid_lid_enable_wakeup(info, true);
++}
 +
-+union gsb_buffer_data {
-+	struct gsb_data_in   in;	// common input
-+	struct gsb_data_rqsx rqsx;	// RQSX input
-+	struct gsb_data_etwl etwl;	// ETWL input
-+	struct gsb_data_out  out;	// output
-+};
++static int surface_sam_sid_resume(struct device *dev)
++{
++	const struct sid_device_info *info = dev_get_drvdata(dev);
++	return sid_lid_enable_wakeup(info, false);
++}
 +
-+struct gsb_buffer {
-+	u8 status;			// GSB AttribRawProcess status
-+	u8 len;				// GSB AttribRawProcess length
-+	union gsb_buffer_data data;
-+} __packed;
++static SIMPLE_DEV_PM_OPS(surface_sam_sid_pm, surface_sam_sid_suspend, surface_sam_sid_resume);
 +
 +
-+enum surfacegen5_pwr_event {
-+	SURFACEGEN5_PWR_EVENT_BAT1_STAT	= 0x03,
-+	SURFACEGEN5_PWR_EVENT_BAT1_INFO	= 0x04,
-+	SURFACEGEN5_PWR_EVENT_ADP1_STAT	= 0x05,
-+	SURFACEGEN5_PWR_EVENT_ADP1_INFO	= 0x06,
-+	SURFACEGEN5_PWR_EVENT_BAT2_STAT	= 0x07,
-+	SURFACEGEN5_PWR_EVENT_BAT2_INFO	= 0x08,
-+};
++static int surface_sam_sid_probe(struct platform_device *pdev)
++{
++	const struct dmi_system_id *dmi_match;
++	struct sid_device_info *info;
++	int status;
 +
++	dmi_match = dmi_first_match(dmi_lid_device_table);
++	if (!dmi_match)
++		return -ENODEV;
 +
-+static int surfacegen5_acpi_notify_power_event(struct device *dev, enum surfacegen5_pwr_event event)
-+{
-+	acpi_handle san = ACPI_HANDLE(dev);
-+	union acpi_object *obj;
++	info = dmi_match->driver_data;
 +
-+	obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION,
-+	                              (u8) event, NULL, ACPI_TYPE_BUFFER);
++	platform_set_drvdata(pdev, info);
 +
-+	if (IS_ERR_OR_NULL(obj)) {
-+		return obj ? PTR_ERR(obj) : -ENXIO;
-+	}
++	status = sid_perf_mode_setup(pdev, info);
++	if (status)
++		goto err_perf_mode;
 +
-+	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
-+		dev_err(dev, "got unexpected result from _DSM\n");
-+		return -EFAULT;
-+	}
++	status = sid_lid_device_setup(info);
++	if (status)
++		goto err_lid;
 +
-+	ACPI_FREE(obj);
 +	return 0;
++
++err_lid:
++	sid_perf_mode_remove(pdev, info);
++err_perf_mode:
++	return status;
 +}
 +
-+static int surfacegen5_acpi_notify_sensor_trip_point(struct device *dev, u8 iid)
++static int surface_sam_sid_remove(struct platform_device *pdev)
 +{
-+	acpi_handle san = ACPI_HANDLE(dev);
-+	union acpi_object *obj;
-+	union acpi_object param;
-+
-+	param.type = ACPI_TYPE_INTEGER;
-+	param.integer.value = iid;
-+
-+	obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION,
-+	                              SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT,
-+				      &param, ACPI_TYPE_BUFFER);
-+
-+	if (IS_ERR_OR_NULL(obj)) {
-+		return obj ? PTR_ERR(obj) : -ENXIO;
-+	}
++	const struct sid_device_info *info = platform_get_drvdata(pdev);
 +
-+	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
-+		dev_err(dev, "got unexpected result from _DSM\n");
-+		return -EFAULT;
-+	}
++	sid_perf_mode_remove(pdev, info);
++	sid_lid_device_remove(info);
 +
-+	ACPI_FREE(obj);
++	platform_set_drvdata(pdev, NULL);
 +	return 0;
 +}
 +
 +
-+inline static int surfacegen5_evt_power_adapter(struct device *dev, struct surfacegen5_event *event)
-+{
-+	int status;
++static const struct acpi_device_id surface_sam_sid_match[] = {
++	{ "MSHW0081", },	/* Surface Pro 4, 5, and 6 */
++	{ "MSHW0080", },	/* Surface Book 1 */
++	{ "MSHW0107", },	/* Surface Book 2 */
++	{ "MSHW0086", },	/* Surface Laptop 1 */
++	{ "MSHW0112", },	/* Surface Laptop 2 */
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_sid_match);
 +
-+	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_ADP1_STAT);
-+	if (status) {
-+		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
-+		return status;
-+	}
++struct platform_driver surface_sam_sid = {
++	.probe = surface_sam_sid_probe,
++	.remove = surface_sam_sid_remove,
++	.driver = {
++		.name = "surface_sam_sid",
++		.acpi_match_table = ACPI_PTR(surface_sam_sid_match),
++		.pm = &surface_sam_sid_pm,
++	},
++};
++module_platform_driver(surface_sam_sid);
 +
-+	return 0;
-+}
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Integration Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.c b/drivers/platform/x86/surface_sam/surface_sam_ssh.c
+new file mode 100644
+index 000000000000..f190efcf8705
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.c
+@@ -0,0 +1,1691 @@
++/*
++ * Surface Serial Hub (SSH) driver for communication with the Surface/System
++ * Aggregator Module.
++ */
 +
-+inline static int surfacegen5_evt_power_hwchange(struct device *dev, struct surfacegen5_event *event)
-+{
-+	enum surfacegen5_pwr_event evcode;
-+	int status;
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/completion.h>
++#include <linux/crc-ccitt.h>
++#include <linux/device.h>
++#include <linux/dmaengine.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/kfifo.h>
++#include <linux/mutex.h>
++#include <linux/pm.h>
++#include <linux/refcount.h>
++#include <linux/serdev.h>
++#include <linux/spinlock.h>
++#include <linux/workqueue.h>
 +
-+	if (event->iid == 0x02) {
-+		evcode = SURFACEGEN5_PWR_EVENT_BAT2_INFO;
-+	} else {
-+		evcode = SURFACEGEN5_PWR_EVENT_BAT1_INFO;
-+	}
++#include "surface_sam_ssh.h"
 +
-+	status = surfacegen5_acpi_notify_power_event(dev, evcode);
-+	if (status) {
-+		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
-+		return status;
-+	}
 +
-+	return 0;
-+}
++#define SSH_RQST_TAG_FULL			"surface_sam_ssh_rqst: "
++#define SSH_RQST_TAG				"rqst: "
++#define SSH_EVENT_TAG				"event: "
++#define SSH_RECV_TAG				"recv: "
 +
-+inline static int surfacegen5_evt_power_state(struct device *dev, struct surfacegen5_event *event)
-+{
-+	int status;
++#define SSH_SUPPORTED_FLOW_CONTROL_MASK		(~((u8) ACPI_UART_FLOW_CONTROL_HW))
 +
-+	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT1_STAT);
-+	if (status) {
-+		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
-+		return status;
-+	}
++#define SSH_BYTELEN_SYNC			2
++#define SSH_BYTELEN_TERM			2
++#define SSH_BYTELEN_CRC				2
++#define SSH_BYTELEN_CTRL			4	// command-header, ACK, or RETRY
++#define SSH_BYTELEN_CMDFRAME			8	// without payload
 +
-+	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT2_STAT);
-+	if (status) {
-+		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
-+		return status;
-+	}
++#define SSH_MAX_WRITE (				\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_CMDFRAME			\
++	+ SURFACE_SAM_SSH_MAX_RQST_PAYLOAD	\
++	+ SSH_BYTELEN_CRC			\
++)
 +
-+	return 0;
-+}
++#define SSH_MSG_LEN_CTRL (			\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_TERM			\
++)
 +
-+static unsigned long surfacegen5_evt_power_delay(struct surfacegen5_event *event, void *data)
-+{
-+	switch (event->cid) {
-+	case SG5_EVENT_PWR_CID_CHARGING:
-+	case SG5_EVENT_PWR_CID_STATE:
-+		return SG5_EVENT_DELAY_POWER;
++#define SSH_MSG_LEN_CMD_BASE (			\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_CRC			\
++)	// without payload and command-frame
 +
-+	case SG5_EVENT_PWR_CID_ADAPTER:
-+	case SG5_EVENT_PWR_CID_HWCHANGE:
-+	default:
-+		return 0;
-+	}
-+}
++#define SSH_WRITE_TIMEOUT		msecs_to_jiffies(1000)
++#define SSH_READ_TIMEOUT		msecs_to_jiffies(1000)
++#define SSH_NUM_RETRY			3
 +
-+static int surfacegen5_evt_power(struct surfacegen5_event *event, void *data)
-+{
-+	struct device *dev = (struct device *)data;
++#define SSH_WRITE_BUF_LEN		SSH_MAX_WRITE
++#define SSH_READ_BUF_LEN		512		// must be power of 2
++#define SSH_EVAL_BUF_LEN		SSH_MAX_WRITE	// also works for reading
 +
-+	switch (event->cid) {
-+	case SG5_EVENT_PWR_CID_HWCHANGE:
-+		return surfacegen5_evt_power_hwchange(dev, event);
++#define SSH_FRAME_TYPE_CMD		0x80
++#define SSH_FRAME_TYPE_ACK		0x40
++#define SSH_FRAME_TYPE_RETRY		0x04
 +
-+	case SG5_EVENT_PWR_CID_ADAPTER:
-+		return surfacegen5_evt_power_adapter(dev, event);
++#define SSH_FRAME_OFFS_CTRL		SSH_BYTELEN_SYNC
++#define SSH_FRAME_OFFS_CTRL_CRC		(SSH_FRAME_OFFS_CTRL + SSH_BYTELEN_CTRL)
++#define SSH_FRAME_OFFS_TERM		(SSH_FRAME_OFFS_CTRL_CRC + SSH_BYTELEN_CRC)
++#define SSH_FRAME_OFFS_CMD		SSH_FRAME_OFFS_TERM	// either TERM or CMD
++#define SSH_FRAME_OFFS_CMD_PLD		(SSH_FRAME_OFFS_CMD + SSH_BYTELEN_CMDFRAME)
 +
-+	case SG5_EVENT_PWR_CID_CHARGING:
-+	case SG5_EVENT_PWR_CID_STATE:
-+		return surfacegen5_evt_power_state(dev, event);
++/*
++ * A note on Request IDs (RQIDs):
++ *	0x0000 is not a valid RQID
++ *	0x0001 is valid, but reserved for Surface Laptop keyboard events
++ */
++#define SAM_NUM_EVENT_TYPES		((1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1)
 +
-+	default:
-+		dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid);
-+	}
++/*
++ * Sync:			aa 55
++ * Terminate:			ff ff
++ *
++ * Request Message:		sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame)
++ * Ack Message:			sync ack crc(ack) terminate
++ * Retry Message:		sync retry crc(retry) terminate
++ * Response Message:		sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame)
++ *
++ * Command Header:		80 LEN 00 SEQ
++ * Ack:				40 00 00 SEQ
++ * Retry:			04 00 00 00
++ * Command Request Frame:	80 RTC 01 00 RIID RQID RCID PLD
++ * Command Response Frame:	80 RTC 00 01 RIID RQID RCID PLD
++ */
 +
-+	return 0;
-+}
++struct ssh_frame_ctrl {
++	u8 type;
++	u8 len;			// without crc
++	u8 pad;
++	u8 seq;
++} __packed;
 +
++struct ssh_frame_cmd {
++	u8 type;
++	u8 tc;
++	u8 outgoing;		// assumed to be 0x01 for SSH to SAM messages, 0x00 otherwise
++	u8 incoming;		// assumed to be 0x01 for SAM to SSH messages, 0x00 otherwise
++	u8 iid;
++	u8 rqid_lo;		// id for request/response matching (low byte)
++	u8 rqid_hi;		// id for request/response matching (high byte)
++	u8 cid;
++} __packed;
 +
-+inline static int surfacegen5_evt_thermal_notify(struct device *dev, struct surfacegen5_event *event)
-+{
-+	int status;
 +
-+	status = surfacegen5_acpi_notify_sensor_trip_point(dev, event->iid);
-+	if (status) {
-+		dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid);
-+		return status;
-+	}
++enum ssh_ec_state {
++	SSH_EC_UNINITIALIZED,
++	SSH_EC_INITIALIZED,
++	SSH_EC_SUSPENDED,
++};
 +
-+	return 0;
-+}
++struct ssh_counters {
++	u8  seq;		// control sequence id
++	u16 rqid;		// id for request/response matching
++};
 +
-+static int surfacegen5_evt_thermal(struct surfacegen5_event *event, void *data)
-+{
-+	struct device *dev = (struct device *)data;
++struct ssh_writer {
++	u8 *data;
++	u8 *ptr;
++} __packed;
 +
-+	switch (event->cid) {
-+	case SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT:
-+		return surfacegen5_evt_thermal_notify(dev, event);
++enum ssh_receiver_state {
++	SSH_RCV_DISCARD,
++	SSH_RCV_CONTROL,
++	SSH_RCV_COMMAND,
++};
 +
-+	default:
-+		dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid);
-+	}
++struct ssh_receiver {
++	spinlock_t lock;
++	enum ssh_receiver_state state;
++	struct completion signal;
++	struct kfifo fifo;
++	struct {
++		bool pld;
++		u8 seq;
++		u16 rqid;
++	} expect;
++	struct {
++		u16 cap;
++		u16 len;
++		u8 *ptr;
++	} eval_buf;
++};
 +
-+	return 0;
-+}
++struct ssh_event_handler {
++	surface_sam_ssh_event_handler_fn handler;
++	surface_sam_ssh_event_handler_delay delay;
++	void *data;
++};
 +
++struct ssh_events {
++	spinlock_t lock;
++	struct workqueue_struct *queue_ack;
++	struct workqueue_struct *queue_evt;
++	struct ssh_event_handler handler[SAM_NUM_EVENT_TYPES];
++};
 +
-+static struct gsb_data_rqsx *surfacegen5_san_validate_rqsx(
-+	struct device *dev, const char *type, struct gsb_buffer *buffer)
-+{
-+	struct gsb_data_rqsx *rqsx = &buffer->data.rqsx;
++struct sam_ssh_ec {
++	struct mutex lock;
++	enum ssh_ec_state state;
++	struct serdev_device *serdev;
++	struct ssh_counters counter;
++	struct ssh_writer writer;
++	struct ssh_receiver receiver;
++	struct ssh_events events;
++};
 +
-+	if (buffer->len < 8) {
-+		dev_err(dev, "invalid %s package (len = %d)\n",
-+			type, buffer->len);
-+		return NULL;
-+	}
++struct ssh_fifo_packet {
++	u8 type;	// packet type (ACK/RETRY/CMD)
++	u8 seq;
++	u8 len;
++};
 +
-+	if (rqsx->cdl != buffer->len - 8) {
-+		dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n",
-+			type, buffer->len, rqsx->cdl);
-+		return NULL;
-+	}
++struct ssh_event_work {
++	refcount_t refcount;
++	struct sam_ssh_ec *ec;
++	struct work_struct work_ack;
++	struct delayed_work work_evt;
++	struct surface_sam_ssh_event event;
++	u8 seq;
++};
 +
-+	if (rqsx->tid != 0x01) {
-+		dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n",
-+			 type, rqsx->tid);
-+		return NULL;
++
++static struct sam_ssh_ec ssh_ec = {
++	.lock   = __MUTEX_INITIALIZER(ssh_ec.lock),
++	.state  = SSH_EC_UNINITIALIZED,
++	.serdev = NULL,
++	.counter = {
++		.seq  = 0,
++		.rqid = 0,
++	},
++	.writer = {
++		.data = NULL,
++		.ptr  = NULL,
++	},
++	.receiver = {
++		.lock = __SPIN_LOCK_UNLOCKED(),
++		.state = SSH_RCV_DISCARD,
++		.expect = {},
++	},
++	.events = {
++		.lock = __SPIN_LOCK_UNLOCKED(),
++		.handler = {},
 +	}
++};
 +
-+	return rqsx;
-+}
 +
-+static acpi_status
-+surfacegen5_san_etwl(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
-+{
-+	struct gsb_data_etwl *etwl = &buffer->data.etwl;
++static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec,
++					 const struct surface_sam_ssh_rqst *rqst,
++					 struct surface_sam_ssh_buf *result);
 +
-+	if (buffer->len < 3) {
-+		dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len);
-+		return AE_OK;
-+	}
 +
-+	dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n",
-+		etwl->etw3, etwl->etw4,
-+		buffer->len - 3, (char *)etwl->msg);
++inline static struct sam_ssh_ec *surface_sam_ssh_acquire(void)
++{
++	struct sam_ssh_ec *ec = &ssh_ec;
 +
-+	// indicate success
-+	buffer->status = 0x00;
-+	buffer->len = 0x00;
++	mutex_lock(&ec->lock);
++	return ec;
++}
 +
-+	return AE_OK;
++inline static void surface_sam_ssh_release(struct sam_ssh_ec *ec)
++{
++	mutex_unlock(&ec->lock);
 +}
 +
-+static acpi_status
-+surfacegen5_san_rqst(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
++inline static struct sam_ssh_ec *surface_sam_ssh_acquire_init(void)
 +{
-+	struct gsb_data_rqsx *gsb_rqst = surfacegen5_san_validate_rqsx(ctx->dev, "RQST", buffer);
-+	struct surfacegen5_rqst rqst = {};
-+	struct surfacegen5_buf result = {};
-+	int status = 0;
-+	int try;
++	struct sam_ssh_ec *ec = surface_sam_ssh_acquire();
 +
-+	if (!gsb_rqst) {
-+		return AE_OK;
++	if (ec->state == SSH_EC_UNINITIALIZED) {
++		surface_sam_ssh_release(ec);
++		return NULL;
 +	}
 +
-+	rqst.tc  = gsb_rqst->tc;
-+	rqst.iid = gsb_rqst->iid;
-+	rqst.cid = gsb_rqst->cid;
-+	rqst.snc = gsb_rqst->snc;
-+	rqst.cdl = gsb_rqst->cdl;
-+	rqst.pld = &gsb_rqst->pld[0];
++	return ec;
++}
 +
-+	result.cap  = SURFACEGEN5_MAX_RQST_RESPONSE;
-+	result.len  = 0;
-+	result.data = kzalloc(result.cap, GFP_KERNEL);
++int surface_sam_ssh_consumer_register(struct device *consumer)
++{
++	u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
++	struct sam_ssh_ec *ec;
++	struct device_link *link;
 +
-+	if (!result.data) {
-+		return AE_NO_MEMORY;
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
 +	}
 +
-+	for (try = 0; try < SG5_SAN_RQST_RETRY; try++) {
-+		if (try) {
-+			dev_warn(ctx->dev, SG5_SAN_RQST_TAG "IO error occured, trying again\n");
-+		}
-+
-+		status = surfacegen5_ec_rqst(&rqst, &result);
-+		if (status != -EIO) break;
++	link = device_link_add(consumer, &ec->serdev->dev, flags);
++	if (!link) {
++		return -EFAULT;
 +	}
 +
-+	if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) {
-+		/* Base state quirk:
-+		 * The base state may be queried from ACPI when the EC is still
-+		 * suspended. In this case it will return '-EPERM'. This query
-+		 * will only be triggered from the ACPI lid GPE interrupt, thus
-+		 * we are either in laptop or studio mode (base status 0x01 or
-+		 * 0x02). Furthermore, we will only get here if the device (and
-+		 * EC) have been suspended.
-+		 *
-+		 * We now assume that the device is in laptop mode (0x01). This
-+		 * has the drawback that it will wake the device when unfolding
-+		 * it in studio mode, but it also allows us to avoid actively
-+		 * waiting for the EC to wake up, which may incur a notable
-+		 * delay.
-+		 */
-+
-+		buffer->status          = 0x00;
-+		buffer->len             = 0x03;
-+		buffer->data.out.status = 0x00;
-+		buffer->data.out.len    = 0x01;
-+		buffer->data.out.pld[0] = 0x01;
-+
-+	} else if (!status) {		// success
-+		buffer->status          = 0x00;
-+		buffer->len             = result.len + 2;
-+		buffer->data.out.status = 0x00;
-+		buffer->data.out.len    = result.len;
-+		memcpy(&buffer->data.out.pld[0], result.data, result.len);
++	surface_sam_ssh_release(ec);
++	return 0;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_consumer_register);
 +
-+	} else {			// failure
-+		dev_err(ctx->dev, SG5_SAN_RQST_TAG "failed with error %d\n", status);
-+		buffer->status          = 0x00;
-+		buffer->len             = 0x02;
-+		buffer->data.out.status = 0x01;		// indicate _SSH error
-+		buffer->data.out.len    = 0x00;
-+	}
 +
-+	kfree(result.data);
++inline static u16 sam_rqid_to_rqst(u16 rqid) {
++	return rqid << SURFACE_SAM_SSH_RQID_EVENT_BITS;
++}
 +
-+	return AE_OK;
++inline static bool sam_rqid_is_event(u16 rqid) {
++	const u16 mask = (1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1;
++	return rqid != 0 && (rqid | mask) == mask;
 +}
 +
-+static acpi_status
-+surfacegen5_san_rqsg(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid)
 +{
-+	struct gsb_data_rqsx *rqsg = surfacegen5_san_validate_rqsx(ctx->dev, "RQSG", buffer);
-+
-+	if (!rqsg) {
-+		return AE_OK;
-+	}
-+
-+	// TODO: RQSG handler
++	struct sam_ssh_ec *ec;
 +
-+	dev_warn(ctx->dev, "unsupported request: RQSG(0x%02x, 0x%02x, 0x%02x)\n",
-+		 rqsg->tc, rqsg->cid, rqsg->iid);
++	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
++	u8 buf[1] = { 0x00 };
 +
-+	return AE_OK;
-+}
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x0b,
++		.snc = 0x01,
++		.cdl = 0x04,
++		.pld = pld,
++	};
 +
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
 +
-+static acpi_status
-+surfacegen5_san_opreg_handler(u32 function, acpi_physical_address command,
-+                              u32 bits, u64 *value64,
-+                              void *opreg_context, void *region_context)
-+{
-+	struct surfacegen5_san_opreg_context *context = opreg_context;
-+	struct gsb_buffer *buffer = (struct gsb_buffer *)value64;
-+	int accessor_type = (0xFFFF0000 & function) >> 16;
++	int status;
 +
-+	if (command != 0) {
-+		dev_warn(context->dev, "unsupported command: 0x%02llx\n", command);
-+		return AE_OK;
++	// only allow RQIDs that lie within event spectrum
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
 +	}
 +
-+	if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) {
-+		dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type);
-+		return AE_OK;
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
 +	}
 +
-+	// buffer must have at least contain the command-value
-+	if (buffer->len == 0) {
-+		dev_err(context->dev, "request-package too small\n");
-+		return AE_OK;
-+	}
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
 +
-+	switch (buffer->data.in.cv) {
-+	case 0x01:  return surfacegen5_san_rqst(context, buffer);
-+	case 0x02:  return surfacegen5_san_etwl(context, buffer);
-+	case 0x03:  return surfacegen5_san_rqsg(context, buffer);
++		surface_sam_ssh_release(ec);
++		return -EPERM;
 +	}
 +
-+	dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv);
-+	return AE_OK;
-+}
++	status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
 +
-+static int surfacegen5_san_enable_events(struct device *dev)
-+{
-+	int status;
-+
-+	status = surfacegen5_ec_set_delayed_event_handler(
-+			SG5_EVENT_PWR_RQID, surfacegen5_evt_power,
-+			surfacegen5_evt_power_delay, dev);
-+	if (status) {
-+		goto err_event_handler_power;
-+	}
-+
-+	status = surfacegen5_ec_set_event_handler(
-+			SG5_EVENT_TEMP_RQID, surfacegen5_evt_thermal,
-+			dev);
-+	if (status) {
-+		goto err_event_handler_thermal;
-+	}
-+
-+	status = surfacegen5_ec_enable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
-+	if (status) {
-+		goto err_event_source_power;
-+	}
-+
-+	status = surfacegen5_ec_enable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID);
-+	if (status) {
-+		goto err_event_source_thermal;
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while enabling event source: 0x%02x\n",
++			 buf[0]);
 +	}
 +
-+	return 0;
-+
-+err_event_source_thermal:
-+	surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
-+err_event_source_power:
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID);
-+err_event_handler_thermal:
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID);
-+err_event_handler_power:
++	surface_sam_ssh_release(ec);
 +	return status;
-+}
 +
-+static void surfacegen5_san_disable_events(void)
-+{
-+	surfacegen5_ec_disable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID);
-+	surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID);
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID);
 +}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_enable_event_source);
 +
-+
-+static int surfacegen5_san_consumers_link(struct platform_device *pdev,
-+                                          const struct surfacegen5_san_acpi_consumer *cons,
-+                                          struct surfacegen5_san_consumers *out)
++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid)
 +{
-+	const struct surfacegen5_san_acpi_consumer *con;
-+	struct surfacegen5_san_consumer_link *links, *link;
-+	struct acpi_device *adev;
-+	acpi_handle handle;
-+	u32 max_links = 0;
-+	int status;
++	struct sam_ssh_ec *ec;
 +
-+	if (!cons) {
-+		return 0;
-+	}
++	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
++	u8 buf[1] = { 0x00 };
 +
-+	// count links
-+	for (con = cons; con->path; ++con) {
-+		max_links += 1;
-+	}
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x0c,
++		.snc = 0x01,
++		.cdl = 0x04,
++		.pld = pld,
++	};
 +
-+	// allocate
-+	links = kzalloc(max_links * sizeof(struct surfacegen5_san_consumer_link), GFP_KERNEL);
-+	link = &links[0];
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
 +
-+	if (!links) {
-+		return -ENOMEM;
-+	}
++	int status;
 +
-+	// create links
-+	for (con = cons; con->path; ++con) {
-+		status = acpi_get_handle(NULL, con->path, &handle);
-+		if (status) {
-+			if (con->required || status != AE_NOT_FOUND) {
-+				status = -ENXIO;
-+				goto consumers_link_cleanup;
-+			} else {
-+				continue;
-+			}
-+		}
++	// only allow RQIDs that lie within event spectrum
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
++	}
 +
-+		status = acpi_bus_get_device(handle, &adev);
-+		if (status) {
-+			goto consumers_link_cleanup;
-+		}
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
++	}
 +
-+		link->link = device_link_add(&adev->dev, &pdev->dev, con->flags);
-+		if (!(link->link)) {
-+			status = -EFAULT;
-+			goto consumers_link_cleanup;
-+		}
-+		link->properties = con;
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
 +
-+		link += 1;
++		surface_sam_ssh_release(ec);
++		return -EPERM;
 +	}
 +
-+	out->num = link - links;
-+	out->links = links;
-+
-+	return 0;
++	status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
 +
-+consumers_link_cleanup:
-+	for (link = link - 1; link >= links; --link) {
-+		if (link->properties->flags & DL_FLAG_STATELESS) {
-+			device_link_del(link->link);
-+		}
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while disabling event source: 0x%02x\n",
++			 buf[0]);
 +	}
 +
++	surface_sam_ssh_release(ec);
 +	return status;
 +}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_disable_event_source);
 +
-+static void surfacegen5_san_consumers_unlink(struct surfacegen5_san_consumers *consumers) {
-+	u32 i;
++int surface_sam_ssh_set_delayed_event_handler(
++		u16 rqid, surface_sam_ssh_event_handler_fn fn,
++		surface_sam_ssh_event_handler_delay delay,
++		void *data)
++{
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
 +
-+	if (!consumers) {
-+		return;
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
 +	}
 +
-+	for (i = 0; i < consumers->num; ++i) {
-+		if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) {
-+			device_link_del(consumers->links[i].link);
-+		}
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
 +	}
 +
-+	kfree(consumers->links);
++	spin_lock_irqsave(&ec->events.lock, flags);
 +
-+	consumers->num = 0;
-+	consumers->links = NULL;
++	// 0 is not a valid event RQID
++	ec->events.handler[rqid - 1].handler = fn;
++	ec->events.handler[rqid - 1].delay = delay;
++	ec->events.handler[rqid - 1].data = data;
++
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++	surface_sam_ssh_release(ec);
++
++	return 0;
 +}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_set_delayed_event_handler);
 +
-+static int surfacegen5_acpi_san_probe(struct platform_device *pdev)
++int surface_sam_ssh_remove_event_handler(u16 rqid)
 +{
-+	const struct surfacegen5_san_acpi_consumer *cons;
-+	struct surfacegen5_san_drvdata *drvdata;
-+	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
-+	int status;
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
 +
-+	/*
-+	 * Defer probe if the _SSH driver has not set up the controller yet. This
-+	 * makes sure we do not fail any initial requests (e.g. _STA request without
-+	 * which the battery does not get set up correctly). Otherwise register as
-+	 * consumer to set up a device_link.
-+	 */
-+	status = surfacegen5_ec_consumer_register(&pdev->dev);
-+	if (status) {
-+		return status == -ENXIO ? -EPROBE_DEFER : status;
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
 +	}
 +
-+	drvdata = kzalloc(sizeof(struct surfacegen5_san_drvdata), GFP_KERNEL);
-+	if (!drvdata) {
-+		return -ENOMEM;
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
 +	}
 +
-+	drvdata->opreg_ctx.dev = &pdev->dev;
++	spin_lock_irqsave(&ec->events.lock, flags);
 +
-+	cons = acpi_device_get_match_data(&pdev->dev);
-+	status = surfacegen5_san_consumers_link(pdev, cons, &drvdata->consumers);
-+	if (status) {
-+		goto err_probe_consumers;
-+	}
++	// 0 is not a valid event RQID
++	ec->events.handler[rqid - 1].handler = NULL;
++	ec->events.handler[rqid - 1].delay = NULL;
++	ec->events.handler[rqid - 1].data = NULL;
 +
-+	platform_set_drvdata(pdev, drvdata);
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++	surface_sam_ssh_release(ec);
 +
-+	status = acpi_install_address_space_handler(san,
-+			ACPI_ADR_SPACE_GSBUS,
-+			&surfacegen5_san_opreg_handler,
-+			NULL, &drvdata->opreg_ctx);
++	/*
++	 * Make sure that the handler is not in use any more after we've
++	 * removed it.
++	 */
++	flush_workqueue(ec->events.queue_evt);
 +
-+	if (ACPI_FAILURE(status)) {
-+		status = -ENODEV;
-+		goto err_probe_install_handler;
-+	}
++	return 0;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_remove_event_handler);
 +
-+	status = surfacegen5_san_enable_events(&pdev->dev);
-+	if (status) {
-+		goto err_probe_enable_events;
-+	}
 +
-+	acpi_walk_dep_device_list(san);
-+	return 0;
++inline static u16 ssh_crc(const u8 *buf, size_t size)
++{
++	return crc_ccitt_false(0xffff, buf, size);
++}
 +
-+err_probe_enable_events:
-+	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler);
-+err_probe_install_handler:
-+	platform_set_drvdata(san, NULL);
-+	surfacegen5_san_consumers_unlink(&drvdata->consumers);
-+err_probe_consumers:
-+	kfree(drvdata);
-+	return status;
++inline static void ssh_write_u16(struct ssh_writer *writer, u16 in)
++{
++	put_unaligned_le16(in, writer->ptr);
++	writer->ptr += 2;
 +}
 +
-+static int surfacegen5_acpi_san_remove(struct platform_device *pdev)
++inline static void ssh_write_crc(struct ssh_writer *writer,
++				 const u8 *buf, size_t size)
 +{
-+	struct surfacegen5_san_drvdata *drvdata = platform_get_drvdata(pdev);
-+	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
-+	acpi_status status = AE_OK;
++	ssh_write_u16(writer, ssh_crc(buf, size));
++}
 +
-+	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler);
-+	surfacegen5_san_disable_events();
++inline static void ssh_write_syn(struct ssh_writer *writer)
++{
++	u8 *w = writer->ptr;
 +
-+	surfacegen5_san_consumers_unlink(&drvdata->consumers);
-+	kfree(drvdata);
++	*w++ = 0xaa;
++	*w++ = 0x55;
 +
-+	platform_set_drvdata(pdev, NULL);
-+	return status;
++	writer->ptr = w;
 +}
 +
++inline static void ssh_write_ter(struct ssh_writer *writer)
++{
++	u8 *w = writer->ptr;
 +
-+static const struct surfacegen5_san_acpi_consumer surfacegen5_mshw0091_consumers[] = {
-+	{ "\\_SB.SRTC", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
-+	{ "\\ADP1",     true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
-+	{ "\\_SB.BAT1", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
-+	{ "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
-+	{ },
-+};
-+
-+static const struct acpi_device_id surfacegen5_acpi_san_match[] = {
-+	{ "MSHW0091", (long unsigned int) surfacegen5_mshw0091_consumers },
-+	{ },
-+};
-+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_san_match);
-+
-+struct platform_driver surfacegen5_acpi_san = {
-+	.probe = surfacegen5_acpi_san_probe,
-+	.remove = surfacegen5_acpi_san_remove,
-+	.driver = {
-+		.name = "surfacegen5_acpi_san",
-+		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_san_match),
-+	},
-+};
++	*w++ = 0xff;
++	*w++ = 0xff;
 +
++	writer->ptr = w;
++}
 +
-+inline int surfacegen5_acpi_san_register(void)
++inline static void ssh_write_buf(struct ssh_writer *writer,
++				 u8 *in, size_t len)
 +{
-+	return platform_driver_register(&surfacegen5_acpi_san);
++	writer->ptr = memcpy(writer->ptr, in, len) + len;
 +}
 +
-+inline void surfacegen5_acpi_san_unregister(void)
++inline static void ssh_write_hdr(struct ssh_writer *writer,
++				 const struct surface_sam_ssh_rqst *rqst,
++				 struct sam_ssh_ec *ec)
 +{
-+	platform_driver_unregister(&surfacegen5_acpi_san);
-+}
++	struct ssh_frame_ctrl *hdr = (struct ssh_frame_ctrl *)writer->ptr;
++	u8 *begin = writer->ptr;
 +
-+#else /* CONFIG_SURFACE_ACPI_SAN */
++	hdr->type = SSH_FRAME_TYPE_CMD;
++	hdr->len  = SSH_BYTELEN_CMDFRAME + rqst->cdl;	// without CRC
++	hdr->pad  = 0x00;
++	hdr->seq  = ec->counter.seq;
 +
-+inline int surfacegen5_acpi_san_register(void)
-+{
-+	return 0;
++	writer->ptr += sizeof(*hdr);
++
++	ssh_write_crc(writer, begin, writer->ptr - begin);
 +}
 +
-+inline void surfacegen5_acpi_san_unregister(void)
++inline static void ssh_write_cmd(struct ssh_writer *writer,
++				 const struct surface_sam_ssh_rqst *rqst,
++				 struct sam_ssh_ec *ec)
 +{
++	struct ssh_frame_cmd *cmd = (struct ssh_frame_cmd *)writer->ptr;
++	u8 *begin = writer->ptr;
++
++	u16 rqid = sam_rqid_to_rqst(ec->counter.rqid);
++	u8 rqid_lo = rqid & 0xFF;
++	u8 rqid_hi = rqid >> 8;
++
++	cmd->type     = SSH_FRAME_TYPE_CMD;
++	cmd->tc       = rqst->tc;
++	cmd->outgoing = 0x01;
++	cmd->incoming = 0x00;
++	cmd->iid      = rqst->iid;
++	cmd->rqid_lo  = rqid_lo;
++	cmd->rqid_hi  = rqid_hi;
++	cmd->cid      = rqst->cid;
++
++	writer->ptr += sizeof(*cmd);
++
++	ssh_write_buf(writer, rqst->pld, rqst->cdl);
++	ssh_write_crc(writer, begin, writer->ptr - begin);
 +}
 +
-+#endif /* CONFIG_SURFACE_ACPI_SAN */
++inline static void ssh_write_ack(struct ssh_writer *writer, u8 seq)
++{
++	struct ssh_frame_ctrl *ack = (struct ssh_frame_ctrl *)writer->ptr;
++	u8 *begin = writer->ptr;
 +
++	ack->type = SSH_FRAME_TYPE_ACK;
++	ack->len  = 0x00;
++	ack->pad  = 0x00;
++	ack->seq  = seq;
 +
-+/*************************************************************************
-+ * Virtual HID Framework driver
-+ */
++	writer->ptr += sizeof(*ack);
 +
-+#ifdef CONFIG_SURFACE_ACPI_VHF
++	ssh_write_crc(writer, begin, writer->ptr - begin);
++}
 +
-+#define SG5_VHF_INPUT_NAME	"Microsoft Virtual HID Framework Device"
++inline static void ssh_writer_reset(struct ssh_writer *writer)
++{
++	writer->ptr = writer->data;
++}
 +
-+/*
-+ * Request ID for VHF events. This value is based on the output of the Surface
-+ * EC and should not be changed.
-+ */
-+#define SG5_EVENT_VHF_RQID	0x0001
-+#define SG5_EVENT_VHF_TC	0x08
++inline static int ssh_writer_flush(struct sam_ssh_ec *ec)
++{
++	struct ssh_writer *writer = &ec->writer;
++	struct serdev_device *serdev = ec->serdev;
++	int status;
 +
++	size_t len = writer->ptr - writer->data;
 +
-+struct surfacegen5_vhf_evtctx {
-+	struct device     *dev;
-+	struct hid_device *hid;
-+};
++	dev_dbg(&ec->serdev->dev, "sending message\n");
++	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
++	                     writer->data, writer->ptr - writer->data, false);
 +
-+struct surfacegen5_vhf_drvdata {
-+	struct surfacegen5_vhf_evtctx event_ctx;
-+};
++	status = serdev_device_write(serdev, writer->data, len, SSH_WRITE_TIMEOUT);
++	return status >= 0 ? 0 : status;
++}
 +
++inline static void ssh_write_msg_cmd(struct sam_ssh_ec *ec,
++				     const struct surface_sam_ssh_rqst *rqst)
++{
++	ssh_writer_reset(&ec->writer);
++	ssh_write_syn(&ec->writer);
++	ssh_write_hdr(&ec->writer, rqst, ec);
++	ssh_write_cmd(&ec->writer, rqst, ec);
++}
 +
-+/*
-+ * These report descriptors have been extracted from a Surface Book 2.
-+ * They seems to be similar enough to be usable on the Surface Laptop.
-+ */
-+static const u8 vhf_hid_desc[] = {
-+	// keyboard descriptor (event command ID 0x03)
-+	0x05, 0x01,             /*  Usage Page (Desktop),                   */
-+	0x09, 0x06,             /*  Usage (Keyboard),                       */
-+	0xA1, 0x01,             /*  Collection (Application),               */
-+	0x85, 0x01,             /*      Report ID (1),                      */
-+	0x15, 0x00,             /*      Logical Minimum (0),                */
-+	0x25, 0x01,             /*      Logical Maximum (1),                */
-+	0x75, 0x01,             /*      Report Size (1),                    */
-+	0x95, 0x08,             /*      Report Count (8),                   */
-+	0x05, 0x07,             /*      Usage Page (Keyboard),              */
-+	0x19, 0xE0,             /*      Usage Minimum (KB Leftcontrol),     */
-+	0x29, 0xE7,             /*      Usage Maximum (KB Right GUI),       */
-+	0x81, 0x02,             /*      Input (Variable),                   */
-+	0x75, 0x08,             /*      Report Size (8),                    */
-+	0x95, 0x0A,             /*      Report Count (10),                  */
-+	0x19, 0x00,             /*      Usage Minimum (None),               */
-+	0x29, 0x91,             /*      Usage Maximum (KB LANG2),           */
-+	0x26, 0xFF, 0x00,       /*      Logical Maximum (255),              */
-+	0x81, 0x00,             /*      Input,                              */
-+	0x05, 0x0C,             /*      Usage Page (Consumer),              */
-+	0x0A, 0xC0, 0x02,       /*      Usage (02C0h),                      */
-+	0xA1, 0x02,             /*      Collection (Logical),               */
-+	0x1A, 0xC1, 0x02,       /*          Usage Minimum (02C1h),          */
-+	0x2A, 0xC6, 0x02,       /*          Usage Maximum (02C6h),          */
-+	0x95, 0x06,             /*          Report Count (6),               */
-+	0xB1, 0x03,             /*          Feature (Constant, Variable),   */
-+	0xC0,                   /*      End Collection,                     */
-+	0x05, 0x08,             /*      Usage Page (LED),                   */
-+	0x19, 0x01,             /*      Usage Minimum (01h),                */
-+	0x29, 0x03,             /*      Usage Maximum (03h),                */
-+	0x75, 0x01,             /*      Report Size (1),                    */
-+	0x95, 0x03,             /*      Report Count (3),                   */
-+	0x25, 0x01,             /*      Logical Maximum (1),                */
-+	0x91, 0x02,             /*      Output (Variable),                  */
-+	0x95, 0x05,             /*      Report Count (5),                   */
-+	0x91, 0x01,             /*      Output (Constant),                  */
-+	0xC0,                   /*  End Collection,                         */
-+
-+	// media key descriptor (event command ID 0x04)
-+	0x05, 0x0C,             /*  Usage Page (Consumer),                  */
-+	0x09, 0x01,             /*  Usage (Consumer Control),               */
-+	0xA1, 0x01,             /*  Collection (Application),               */
-+	0x85, 0x03,             /*      Report ID (3),                      */
-+	0x75, 0x10,             /*      Report Size (16),                   */
-+	0x15, 0x00,             /*      Logical Minimum (0),                */
-+	0x26, 0xFF, 0x03,       /*      Logical Maximum (1023),             */
-+	0x19, 0x00,             /*      Usage Minimum (00h),                */
-+	0x2A, 0xFF, 0x03,       /*      Usage Maximum (03FFh),              */
-+	0x81, 0x00,             /*      Input,                              */
-+	0xC0,                   /*  End Collection,                         */
-+};
-+
-+
-+static int vhf_hid_start(struct hid_device *hid)
++inline static void ssh_write_msg_ack(struct sam_ssh_ec *ec, u8 seq)
 +{
-+	hid_dbg(hid, "%s\n", __func__);
-+	return 0;
++	ssh_writer_reset(&ec->writer);
++	ssh_write_syn(&ec->writer);
++	ssh_write_ack(&ec->writer, seq);
++	ssh_write_ter(&ec->writer);
 +}
 +
-+static void vhf_hid_stop(struct hid_device *hid)
++inline static void ssh_receiver_restart(struct sam_ssh_ec *ec,
++					const struct surface_sam_ssh_rqst *rqst)
 +{
-+	hid_dbg(hid, "%s\n", __func__);
-+}
++	unsigned long flags;
 +
-+static int vhf_hid_open(struct hid_device *hid)
-+{
-+	hid_dbg(hid, "%s\n", __func__);
-+	return 0;
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	reinit_completion(&ec->receiver.signal);
++	ec->receiver.state = SSH_RCV_CONTROL;
++	ec->receiver.expect.pld = rqst->snc;
++	ec->receiver.expect.seq = ec->counter.seq;
++	ec->receiver.expect.rqid = sam_rqid_to_rqst(ec->counter.rqid);
++	ec->receiver.eval_buf.len = 0;
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
 +}
 +
-+static void vhf_hid_close(struct hid_device *hid)
++inline static void ssh_receiver_discard(struct sam_ssh_ec *ec)
 +{
-+	hid_dbg(hid, "%s\n", __func__);
-+}
++	unsigned long flags;
 +
-+static int vhf_hid_parse(struct hid_device *hid)
-+{
-+	return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc));
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	ec->receiver.state = SSH_RCV_DISCARD;
++	ec->receiver.eval_buf.len = 0;
++	kfifo_reset(&ec->receiver.fifo);
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
 +}
 +
-+static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
-+			       u8 *buf, size_t len, unsigned char rtype,
-+			       int reqtype)
++static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec,
++					 const struct surface_sam_ssh_rqst *rqst,
++					 struct surface_sam_ssh_buf *result)
 +{
-+	hid_dbg(hid, "%s\n", __func__);
-+	return 0;
-+}
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_fifo_packet packet = {};
++	int status;
++	int try;
++	unsigned int rem;
 +
-+static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len)
-+{
-+	hid_dbg(hid, "%s\n", __func__);
-+	print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false);
++	if (rqst->cdl > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD) {
++		dev_err(dev, SSH_RQST_TAG "request payload too large\n");
++		return -EINVAL;
++	}
 +
-+	return len;
-+}
++	// write command in buffer, we may need it multiple times
++	ssh_write_msg_cmd(ec, rqst);
++	ssh_receiver_restart(ec, rqst);
 +
-+static struct hid_ll_driver vhf_hid_ll_driver = {
-+	.start         = vhf_hid_start,
-+	.stop          = vhf_hid_stop,
-+	.open          = vhf_hid_open,
-+	.close         = vhf_hid_close,
-+	.parse         = vhf_hid_parse,
-+	.raw_request   = vhf_hid_raw_request,
-+	.output_report = vhf_hid_output_report,
-+};
++	// send command, try to get an ack response
++	for (try = 0; try < SSH_NUM_RETRY; try++) {
++		status = ssh_writer_flush(ec);
++		if (status) {
++			goto out;
++		}
 +
++		rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT);
++		if (rem) {
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
 +
-+static struct hid_device *surfacegen5_vhf_create_hid_device(struct platform_device *pdev)
-+{
-+	struct hid_device *hid;
++			if (packet.type == SSH_FRAME_TYPE_ACK) {
++				break;
++			}
++		}
++	}
 +
-+	hid = hid_allocate_device();
-+	if (IS_ERR(hid)) {
-+		return hid;
++	// check if we ran out of tries?
++	if (try >= SSH_NUM_RETRY) {
++		dev_err(dev, SSH_RQST_TAG "communication failed %d times, giving up\n", try);
++		status = -EIO;
++		goto out;
 +	}
 +
-+	hid->dev.parent = &pdev->dev;
++	ec->counter.seq  += 1;
++	ec->counter.rqid += 1;
 +
-+	hid->bus     = BUS_VIRTUAL;
-+	hid->vendor  = USB_VENDOR_ID_MICROSOFT;
-+	hid->product = USB_DEVICE_ID_MS_VHF;
++	// get command response/payload
++	if (rqst->snc && result) {
++		rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT);
++		if (rem) {
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
 +
-+	hid->ll_driver = &vhf_hid_ll_driver;
++			if (result->cap < packet.len) {
++				status = -EINVAL;
++				goto out;
++			}
++
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len);
++			result->len = packet.len;
++		} else {
++			dev_err(dev, SSH_RQST_TAG "communication timed out\n");
++			status = -EIO;
++			goto out;
++		}
 +
-+	sprintf(hid->name, "%s", SG5_VHF_INPUT_NAME);
++		// send ACK
++		ssh_write_msg_ack(ec, packet.seq);
++		status = ssh_writer_flush(ec);
++		if (status) {
++			goto out;
++		}
++	}
 +
-+	return hid;
++out:
++	ssh_receiver_discard(ec);
++	return status;
 +}
 +
-+static int surfacegen5_vhf_event_handler(struct surfacegen5_event *event, void *data)
++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result)
 +{
-+	struct surfacegen5_vhf_evtctx *ctx = (struct surfacegen5_vhf_evtctx *)data;
++	struct sam_ssh_ec *ec;
++	int status;
 +
-+	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
-+		return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1);
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
 +	}
 +
-+	dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid);
-+	return 0;
-+}
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
 +
-+static unsigned long surfacegen5_vhf_event_delay(struct surfacegen5_event *event, void *data)
-+{
-+	// high priority immediate execution for keyboard events
-+	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
-+		return SURFACEGEN5_EVENT_IMMEDIATE;
++		surface_sam_ssh_release(ec);
++		return -EPERM;
 +	}
 +
-+	return 0;
++	status = surface_sam_ssh_rqst_unlocked(ec, rqst, result);
++
++	surface_sam_ssh_release(ec);
++	return status;
 +}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_rqst);
 +
-+static int surfacegen5_acpi_vhf_probe(struct platform_device *pdev)
++
++static int surface_sam_ssh_ec_resume(struct sam_ssh_ec *ec)
 +{
-+	struct surfacegen5_vhf_drvdata *drvdata;
-+	struct hid_device *hid;
-+	int status;
++	u8 buf[1] = { 0x00 };
 +
-+	// add device link to EC
-+	status = surfacegen5_ec_consumer_register(&pdev->dev);
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x16,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
++
++	int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
 +	if (status) {
-+		return status == -ENXIO ? -EPROBE_DEFER : status;
++		return status;
 +	}
 +
-+	drvdata = kzalloc(sizeof(struct surfacegen5_vhf_drvdata), GFP_KERNEL);
-+	if (!drvdata) {
-+		return -ENOMEM;
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while trying to resume EC: 0x%02x\n",
++			 buf[0]);
 +	}
 +
-+	hid = surfacegen5_vhf_create_hid_device(pdev);
-+	if (IS_ERR(hid)) {
-+		status = PTR_ERR(hid);
-+		goto err_probe_hid;
-+	}
++	return 0;
++}
 +
-+	status = hid_add_device(hid);
-+	if (status) {
-+		goto err_add_hid;
-+	}
++static int surface_sam_ssh_ec_suspend(struct sam_ssh_ec *ec)
++{
++	u8 buf[1] = { 0x00 };
 +
-+	drvdata->event_ctx.dev = &pdev->dev;
-+	drvdata->event_ctx.hid = hid;
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x15,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
 +
-+	platform_set_drvdata(pdev, drvdata);
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
 +
-+	/*
-+         * Set event hanlder for VHF events. They seem to be enabled by
-+         * default, thus there should be no need to explicitly enable them.
-+	 */
-+	status = surfacegen5_ec_set_delayed_event_handler(
-+			SG5_EVENT_VHF_RQID,
-+	                surfacegen5_vhf_event_handler,
-+	                surfacegen5_vhf_event_delay,
-+			&drvdata->event_ctx);
++	int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
 +	if (status) {
-+		goto err_add_hid;
++		return status;
 +	}
 +
-+	status = surfacegen5_ec_enable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID);
-+	if (status) {
-+		goto err_event_source;
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while trying to suspend EC: 0x%02x\n",
++			 buf[0]);
 +	}
 +
 +	return 0;
-+
-+err_event_source:
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID);
-+err_add_hid:
-+	hid_destroy_device(hid);
-+	platform_set_drvdata(pdev, NULL);
-+err_probe_hid:
-+	kfree(drvdata);
-+	return status;
 +}
 +
-+static int surfacegen5_acpi_vhf_remove(struct platform_device *pdev)
-+{
-+	struct surfacegen5_vhf_drvdata *drvdata = platform_get_drvdata(pdev);
-+
-+	surfacegen5_ec_disable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID);
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID);
 +
-+	hid_destroy_device(drvdata->event_ctx.hid);
-+	kfree(drvdata);
++inline static bool ssh_is_valid_syn(const u8 *ptr)
++{
++	return ptr[0] == 0xaa && ptr[1] == 0x55;
++}
 +
-+	platform_set_drvdata(pdev, NULL);
-+	return 0;
++inline static bool ssh_is_valid_ter(const u8 *ptr)
++{
++	return ptr[0] == 0xff && ptr[1] == 0xff;
 +}
 +
++inline static bool ssh_is_valid_crc(const u8 *begin, const u8 *end)
++{
++	u16 crc = ssh_crc(begin, end - begin);
++	return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8));
++}
 +
-+static const struct acpi_device_id surfacegen5_acpi_vhf_match[] = {
-+	{ "MSHW0096" },
-+	{ },
-+};
-+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_vhf_match);
 +
-+struct platform_driver surfacegen5_acpi_vhf = {
-+	.probe = surfacegen5_acpi_vhf_probe,
-+	.remove = surfacegen5_acpi_vhf_remove,
-+	.driver = {
-+		.name = "surfacegen5_acpi_vhf",
-+		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_vhf_match),
-+	},
-+};
++static int surface_sam_ssh_send_ack(struct sam_ssh_ec *ec, u8 seq)
++{
++	int status;
++	u8 buf[SSH_MSG_LEN_CTRL];
++	u16 crc;
 +
++	buf[0] = 0xaa;
++	buf[1] = 0x55;
++	buf[2] = 0x40;
++	buf[3] = 0x00;
++	buf[4] = 0x00;
++	buf[5] = seq;
 +
-+inline int surfacegen5_acpi_vhf_register(void)
-+{
-+	return platform_driver_register(&surfacegen5_acpi_vhf);
-+}
++	crc = ssh_crc(buf + SSH_FRAME_OFFS_CTRL, SSH_BYTELEN_CTRL);
++	buf[6] = crc & 0xff;
++	buf[7] = crc >> 8;
 +
-+inline void surfacegen5_acpi_vhf_unregister(void)
-+{
-+	platform_driver_unregister(&surfacegen5_acpi_vhf);
-+}
++	buf[8] = 0xff;
++	buf[9] = 0xff;
 +
-+#else /* CONFIG_SURFACE_ACPI_VHF */
++	dev_dbg(&ec->serdev->dev, "sending message\n");
++	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
++	                     buf, SSH_MSG_LEN_CTRL, false);
 +
-+inline int surfacegen5_acpi_vhf_register(void)
-+{
-+	return 0;
++	status = serdev_device_write(ec->serdev, buf, SSH_MSG_LEN_CTRL, SSH_WRITE_TIMEOUT);
++	return status >= 0 ? 0 : status;
 +}
 +
-+inline void surfacegen5_acpi_vhf_unregister(void)
++static void surface_sam_ssh_event_work_ack_handler(struct work_struct *_work)
 +{
-+}
-+
-+#endif /* CONFIG_SURFACE_ACPI_VHF */
-+
++	struct surface_sam_ssh_event *event;
++	struct ssh_event_work *work;
++	struct sam_ssh_ec *ec;
++	struct device *dev;
++	int status;
 +
-+/*************************************************************************
-+ * Detachment System Driver (DTX)
-+ */
++	work = container_of(_work, struct ssh_event_work, work_ack);
++	event = &work->event;
++	ec = work->ec;
++	dev = &ec->serdev->dev;
 +
-+#ifdef CONFIG_SURFACE_ACPI_DTX
++	// make sure we load a fresh ec state
++	smp_mb();
 +
-+#define SG5_DTX_INPUT_NAME	"Microsoft Surface Base 2 Integration Device"
++	if (ec->state == SSH_EC_INITIALIZED) {
++		status = surface_sam_ssh_send_ack(ec, work->seq);
++		if (status) {
++			dev_err(dev, SSH_EVENT_TAG "failed to send ACK: %d\n", status);
++		}
++	}
 +
-+#define DTX_CMD_LATCH_LOCK				_IO(0x11, 0x01)
-+#define DTX_CMD_LATCH_UNLOCK				_IO(0x11, 0x02)
-+#define DTX_CMD_LATCH_REQUEST				_IO(0x11, 0x03)
-+#define DTX_CMD_LATCH_OPEN				_IO(0x11, 0x04)
-+#define DTX_CMD_GET_OPMODE				_IOR(0x11, 0x05, int)
++	if (refcount_dec_and_test(&work->refcount)) {
++		kfree(work);
++	}
++}
 +
-+#define SG5_RQST_DTX_TC					0x11
-+#define SG5_RQST_DTX_CID_LATCH_LOCK			0x06
-+#define SG5_RQST_DTX_CID_LATCH_UNLOCK			0x07
-+#define SG5_RQST_DTX_CID_LATCH_REQUEST			0x08
-+#define SG5_RQST_DTX_CID_LATCH_OPEN			0x09
-+#define SG5_RQST_DTX_CID_GET_OPMODE			0x0D
++static void surface_sam_ssh_event_work_evt_handler(struct work_struct *_work)
++{
++	struct delayed_work *dwork = (struct delayed_work *)_work;
++	struct ssh_event_work *work;
++	struct surface_sam_ssh_event *event;
++	struct sam_ssh_ec *ec;
++	struct device *dev;
++	unsigned long flags;
 +
-+#define SG5_EVENT_DTX_TC				0x11
-+#define SG5_EVENT_DTX_RQID				0x0011
-+#define SG5_EVENT_DTX_CID_CONNECTION			0x0c
-+#define SG5_EVENT_DTX_CID_BUTTON			0x0e
-+#define SG5_EVENT_DTX_CID_ERROR				0x0f
-+#define SG5_EVENT_DTX_CID_LATCH_STATUS			0x11
++	surface_sam_ssh_event_handler_fn handler;
++	void *handler_data;
 +
-+#define DTX_OPMODE_TABLET				0x00
-+#define DTX_OPMODE_LAPTOP				0x01
-+#define DTX_OPMODE_STUDIO				0x02
++	int status = 0;
 +
-+#define DTX_LATCH_CLOSED				0x00
-+#define DTX_LATCH_OPENED				0x01
++	work = container_of(dwork, struct ssh_event_work, work_evt);
++	event = &work->event;
++	ec = work->ec;
++	dev = &ec->serdev->dev;
 +
-+// Warning: This must always be a power of 2!
-+#define SURFACE_DTX_CLIENT_BUF_SIZE             	16
++	spin_lock_irqsave(&ec->events.lock, flags);
++	handler       = ec->events.handler[event->rqid - 1].handler;
++	handler_data  = ec->events.handler[event->rqid - 1].data;
++	spin_unlock_irqrestore(&ec->events.lock, flags);
 +
-+#define SG5_DTX_CONNECT_OPMODE_DELAY			1000
++	/*
++	 * During handler removal or driver release, we ensure every event gets
++	 * handled before return of that function. Thus a handler obtained here is
++	 * guaranteed to be valid at least until this function returns.
++	 */
 +
-+#define DTX_ERR		KERN_ERR "surfacegen5_acpi_dtx: "
-+#define DTX_WARN	KERN_WARNING "surfacegen5_acpi_dtx: "
++	if (handler) {
++		status = handler(event, handler_data);
++	} else {
++		dev_warn(dev, SSH_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid);
++	}
 +
++	if (status) {
++		dev_err(dev, SSH_EVENT_TAG "error handling event: %d\n", status);
++	}
 +
-+struct surface_dtx_event {
-+	u8 type;
-+	u8 code;
-+	u8 arg0;
-+	u8 arg1;
-+} __packed;
++	if (refcount_dec_and_test(&work->refcount)) {
++		kfree(work);
++	}
++}
 +
-+struct surface_dtx_dev {
-+	wait_queue_head_t waitq;
-+	struct miscdevice mdev;
-+	spinlock_t client_lock;
-+	struct list_head client_list;
-+	struct mutex mutex;
-+	bool active;
-+	spinlock_t input_lock;
-+	struct input_dev *input_dev;
-+};
++static void ssh_handle_event(struct sam_ssh_ec *ec, const u8 *buf)
++{
++	struct device *dev = &ec->serdev->dev;
++	const struct ssh_frame_ctrl *ctrl;
++	const struct ssh_frame_cmd *cmd;
++	struct ssh_event_work *work;
++	unsigned long flags;
++	u16 pld_len;
 +
-+struct surface_dtx_client {
-+	struct list_head node;
-+	struct surface_dtx_dev *ddev;
-+	struct fasync_struct *fasync;
-+	spinlock_t buffer_lock;
-+	unsigned int buffer_head;
-+	unsigned int buffer_tail;
-+	struct surface_dtx_event buffer[SURFACE_DTX_CLIENT_BUF_SIZE];
-+};
++	surface_sam_ssh_event_handler_delay delay_fn;
++	void *handler_data;
++	unsigned long delay = 0;
 +
++	ctrl = (const struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL);
++	cmd  = (const struct ssh_frame_cmd  *)(buf + SSH_FRAME_OFFS_CMD);
 +
-+static struct surface_dtx_dev surface_dtx_dev;
++	pld_len = ctrl->len - SSH_BYTELEN_CMDFRAME;
 +
++	work = kzalloc(sizeof(struct ssh_event_work) + pld_len, GFP_ATOMIC);
++	if (!work) {
++		dev_warn(dev, SSH_EVENT_TAG "failed to allocate memory, dropping event\n");
++		return;
++	}
 +
-+static int sg5_ec_query_opmpde(void)
-+{
-+	u8 result_buf[1];
-+	int status;
++	refcount_set(&work->refcount, 2);
++	work->ec         = ec;
++	work->seq        = ctrl->seq;
++	work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo;
++	work->event.tc   = cmd->tc;
++	work->event.iid  = cmd->iid;
++	work->event.cid  = cmd->cid;
++	work->event.len  = pld_len;
++	work->event.pld  = ((u8*) work) + sizeof(struct ssh_event_work);
 +
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = SG5_RQST_DTX_TC,
-+		.iid = 0,
-+		.cid = SG5_RQST_DTX_CID_GET_OPMODE,
-+		.snc = 1,
-+		.cdl = 0,
-+		.pld = NULL,
-+	};
++	memcpy(work->event.pld, buf + SSH_FRAME_OFFS_CMD_PLD, pld_len);
 +
-+	struct surfacegen5_buf result = {
-+		.cap = 1,
-+		.len = 0,
-+		.data = result_buf,
-+	};
++	INIT_WORK(&work->work_ack, surface_sam_ssh_event_work_ack_handler);
++	queue_work(ec->events.queue_ack, &work->work_ack);
 +
-+	status = surfacegen5_ec_rqst(&rqst, &result);
-+	if (status) {
-+		return status;
++	spin_lock_irqsave(&ec->events.lock, flags);
++	handler_data = ec->events.handler[work->event.rqid - 1].data;
++	delay_fn     = ec->events.handler[work->event.rqid - 1].delay;
++	if (delay_fn) {
++		delay = delay_fn(&work->event, handler_data);
 +	}
++	spin_unlock_irqrestore(&ec->events.lock, flags);
 +
-+	if (result.len != 1) {
-+		return -EFAULT;
++	// immediate execution for high priority events (e.g. keyboard)
++	if (delay == SURFACE_SAM_SSH_EVENT_IMMEDIATE) {
++		surface_sam_ssh_event_work_evt_handler(&work->work_evt.work);
++	} else {
++		INIT_DELAYED_WORK(&work->work_evt, surface_sam_ssh_event_work_evt_handler);
++		queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay);
 +	}
-+
-+	return result.data[0];
 +}
 +
-+
-+static int dtx_cmd_simple(u8 cid)
++static int ssh_receive_msg_ctrl(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
 +{
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = SG5_RQST_DTX_TC,
-+		.iid = 0,
-+		.cid = cid,
-+		.snc = 0,
-+		.cdl = 0,
-+		.pld = NULL,
-+	};
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_receiver *rcv = &ec->receiver;
++	const struct ssh_frame_ctrl *ctrl;
++	struct ssh_fifo_packet packet;
 +
-+	return surfacegen5_ec_rqst(&rqst, NULL);
-+}
++	const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL;
++	const u8 *ctrl_end   = buf + SSH_FRAME_OFFS_CTRL_CRC;
 +
-+static int dtx_cmd_get_opmode(int __user *buf)
-+{
-+	int opmode = sg5_ec_query_opmpde();
-+	if (opmode < 0) {
-+		return opmode;
-+	}
++	ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin);
 +
-+	if (put_user(opmode, buf)) {
-+		return -EACCES;
++	// actual length check
++	if (size < SSH_MSG_LEN_CTRL) {
++		return 0;			// need more bytes
 +	}
 +
-+	return 0;
-+}
-+
++	// validate TERM
++	if (!ssh_is_valid_ter(buf + SSH_FRAME_OFFS_TERM)) {
++		dev_err(dev, SSH_RECV_TAG "invalid end of message\n");
++		return size;			// discard everything
++	}
 +
-+static int surface_dtx_open(struct inode *inode, struct file *file)
-+{
-+	struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev);
-+	struct surface_dtx_client *client;
++	// validate CRC
++	if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (ctrl)\n");
++		return SSH_MSG_LEN_CTRL;	// only discard message
++	}
 +
-+	// initialize client
-+	client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL);
-+	if (!client) {
-+		return -ENOMEM;
++	// check if we expect the message
++	if (rcv->state != SSH_RCV_CONTROL) {
++		dev_err(dev, SSH_RECV_TAG "discarding message: ctrl not expected\n");
++		return SSH_MSG_LEN_CTRL;	// discard message
 +	}
 +
-+	spin_lock_init(&client->buffer_lock);
-+	client->buffer_head = 0;
-+	client->buffer_tail = 0;
-+	client->ddev = ddev;
++	// check if it is for our request
++	if (ctrl->type == SSH_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) {
++		dev_err(dev, SSH_RECV_TAG "discarding message: ack does not match\n");
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
 +
-+	// attach client
-+	spin_lock(&ddev->client_lock);
-+	list_add_tail_rcu(&client->node, &ddev->client_list);
-+	spin_unlock(&ddev->client_lock);
++	// we now have a valid & expected ACK/RETRY message
++	dev_dbg(dev, SSH_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type);
 +
-+	file->private_data = client;
-+	nonseekable_open(inode, file);
++	packet.type = ctrl->type;
++	packet.seq  = ctrl->seq;
++	packet.len  = 0;
 +
-+	return 0;
-+}
++	if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) {
++		kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet));
 +
-+static int surface_dtx_release(struct inode *inode, struct file *file)
-+{
-+	struct surface_dtx_client *client = file->private_data;
++	} else {
++		dev_warn(dev, SSH_RECV_TAG
++			 "dropping frame: not enough space in fifo (type = %d)\n",
++			 SSH_FRAME_TYPE_CMD);
 +
-+	// detach client
-+	spin_lock(&client->ddev->client_lock);
-+	list_del_rcu(&client->node);
-+	spin_unlock(&client->ddev->client_lock);
-+	synchronize_rcu();
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
 +
-+	kfree(client);
-+	file->private_data = NULL;
++	// update decoder state
++	if (ctrl->type == SSH_FRAME_TYPE_ACK) {
++		rcv->state = rcv->expect.pld
++			? SSH_RCV_COMMAND
++			: SSH_RCV_DISCARD;
++	}
 +
-+	return 0;
++	complete(&rcv->signal);
++	return SSH_MSG_LEN_CTRL;		// handled message
 +}
 +
-+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
++static int ssh_receive_msg_cmd(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
 +{
-+	struct surface_dtx_client *client = file->private_data;
-+	struct surface_dtx_dev *ddev = client->ddev;
-+	struct surface_dtx_event event;
-+	size_t read = 0;
-+	int status = 0;
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_receiver *rcv = &ec->receiver;
++	const struct ssh_frame_ctrl *ctrl;
++	const struct ssh_frame_cmd *cmd;
++	struct ssh_fifo_packet packet;
++
++	const u8 *ctrl_begin     = buf + SSH_FRAME_OFFS_CTRL;
++	const u8 *ctrl_end       = buf + SSH_FRAME_OFFS_CTRL_CRC;
++	const u8 *cmd_begin      = buf + SSH_FRAME_OFFS_CMD;
++	const u8 *cmd_begin_pld  = buf + SSH_FRAME_OFFS_CMD_PLD;
++	const u8 *cmd_end;
 +
-+	if (count != 0 && count < sizeof(struct surface_dtx_event)) {
-+		return -EINVAL;
-+	}
++	size_t msg_len;
 +
-+	if (!ddev->active) {
-+		return -ENODEV;
++	ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin);
++	cmd  = (const struct ssh_frame_cmd  *)(cmd_begin);
++
++	// we need at least a full control frame
++	if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL + SSH_BYTELEN_CRC)) {
++		return 0;		// need more bytes
 +	}
 +
-+	// check availability
-+	if (client->buffer_head == client->buffer_tail){
-+		if (file->f_flags & O_NONBLOCK) {
-+			return -EAGAIN;
-+		}
++	// validate control-frame CRC
++	if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-ctrl)\n");
++		/*
++		 * We can't be sure here if length is valid, thus
++		 * discard everything.
++		 */
++		return size;
++	}
 +
-+		status = wait_event_interruptible(ddev->waitq,
-+				client->buffer_head != client->buffer_tail ||
-+				!ddev->active);
-+		if (status) {
-+			return status;
-+		}
++	// actual length check (ctrl->len contains command-frame but not crc)
++	msg_len = SSH_MSG_LEN_CMD_BASE + ctrl->len;
++	if (size < msg_len) {
++		return 0;			// need more bytes
++	}
 +
-+		if (!ddev->active) {
-+			return -ENODEV;
-+		}
++	cmd_end = cmd_begin + ctrl->len;
++
++	// validate command-frame type
++	if (cmd->type != SSH_FRAME_TYPE_CMD) {
++		dev_err(dev, SSH_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type);
++		return size;			// discard everything
 +	}
 +
-+	// copy events one by one
-+	while (read + sizeof(struct surface_dtx_event) <= count) {
-+		spin_lock_irq(&client->buffer_lock);
++	// validate command-frame CRC
++	if (!ssh_is_valid_crc(cmd_begin, cmd_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-pld)\n");
 +
-+		if(client->buffer_head == client->buffer_tail) {
-+			spin_unlock_irq(&client->buffer_lock);
-+			break;
-+		}
++		/*
++		 * The message length is provided in the control frame. As we
++		 * already validated that, we can be sure here that it's
++		 * correct, so we only need to discard the message.
++		 */
++		return msg_len;
++	}
 +
-+		// get one event
-+		event = client->buffer[client->buffer_tail];
-+		client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1);
-+		spin_unlock_irq(&client->buffer_lock);
++	// check if we received an event notification
++	if (sam_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) {
++		ssh_handle_event(ec, buf);
++		return msg_len;			// handled message
++	}
 +
-+		// copy to userspace
-+		if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) {
-+			return -EFAULT;
-+		}
++	// check if we expect the message
++	if (rcv->state != SSH_RCV_COMMAND) {
++		dev_dbg(dev, SSH_RECV_TAG "discarding message: command not expected\n");
++		return msg_len;			// discard message
++	}
 +
-+		read += sizeof(struct surface_dtx_event);
++	// check if response is for our request
++	if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) {
++		dev_dbg(dev, SSH_RECV_TAG "discarding message: command not a match\n");
++		return msg_len;			// discard message
 +	}
 +
-+	return read;
-+}
++	// we now have a valid & expected command message
++	dev_dbg(dev, SSH_RECV_TAG "valid command message received\n");
 +
-+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
-+{
-+	struct surface_dtx_client *client = file->private_data;
-+	int mask;
++	packet.type = ctrl->type;
++	packet.seq = ctrl->seq;
++	packet.len = cmd_end - cmd_begin_pld;
 +
-+	poll_wait(file, &client->ddev->waitq, pt);
++	if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) {
++		kfifo_in(&rcv->fifo, &packet, sizeof(packet));
++		kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len);
 +
-+	if (client->ddev->active) {
-+		mask = EPOLLOUT | EPOLLWRNORM;
 +	} else {
-+		mask = EPOLLHUP | EPOLLERR;
-+	}
++		dev_warn(dev, SSH_RECV_TAG
++			 "dropping frame: not enough space in fifo (type = %d)\n",
++			 SSH_FRAME_TYPE_CMD);
 +
-+	if (client->buffer_head != client->buffer_tail) {
-+		mask |= EPOLLIN | EPOLLRDNORM;
++		return SSH_MSG_LEN_CTRL;	// discard message
 +	}
 +
-+	return mask;
-+}
-+
-+static int surface_dtx_fasync(int fd, struct file *file, int on)
-+{
-+	struct surface_dtx_client *client = file->private_data;
++	rcv->state = SSH_RCV_DISCARD;
 +
-+	return fasync_helper(fd, file, on, &client->fasync);
++	complete(&rcv->signal);
++	return msg_len;				// handled message
 +}
 +
-+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++static int ssh_eval_buf(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
 +{
-+	struct surface_dtx_client *client = file->private_data;
-+	struct surface_dtx_dev *ddev = client->ddev;
-+	int status;
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_frame_ctrl *ctrl;
 +
-+	status = mutex_lock_interruptible(&ddev->mutex);
-+	if (status) {
-+		return status;
++	// we need at least a control frame to check what to do
++	if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL)) {
++		return 0;		// need more bytes
 +	}
 +
-+	if (!ddev->active) {
-+		mutex_unlock(&ddev->mutex);
-+		return -ENODEV;
++	// make sure we're actually at the start of a new message
++	if (!ssh_is_valid_syn(buf)) {
++		dev_err(dev, SSH_RECV_TAG "invalid start of message\n");
++		return size;		// discard everything
 +	}
 +
-+	switch (cmd) {
-+	case DTX_CMD_LATCH_LOCK:
-+		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_LOCK);
-+		break;
-+
-+	case DTX_CMD_LATCH_UNLOCK:
-+		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_UNLOCK);
-+		break;
-+
-+	case DTX_CMD_LATCH_REQUEST:
-+		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_REQUEST);
-+		break;
++	// handle individual message types seperately
++	ctrl = (struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL);
 +
-+	case DTX_CMD_LATCH_OPEN:
-+		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_OPEN);
-+		break;
++	switch (ctrl->type) {
++	case SSH_FRAME_TYPE_ACK:
++	case SSH_FRAME_TYPE_RETRY:
++		return ssh_receive_msg_ctrl(ec, buf, size);
 +
-+	case DTX_CMD_GET_OPMODE:
-+		status = dtx_cmd_get_opmode((int __user *)arg);
-+		break;
++	case SSH_FRAME_TYPE_CMD:
++		return ssh_receive_msg_cmd(ec, buf, size);
 +
 +	default:
-+		status = -EINVAL;
-+		break;
++		dev_err(dev, SSH_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type);
++		return size;		// discard everything
 +	}
-+
-+	mutex_unlock(&ddev->mutex);
-+	return status;
 +}
 +
-+static const struct file_operations surface_dtx_fops = {
-+	.owner          = THIS_MODULE,
-+	.open           = surface_dtx_open,
-+	.release        = surface_dtx_release,
-+	.read           = surface_dtx_read,
-+	.poll           = surface_dtx_poll,
-+	.fasync         = surface_dtx_fasync,
-+	.unlocked_ioctl = surface_dtx_ioctl,
-+	.llseek         = no_llseek,
-+};
-+
-+static struct surface_dtx_dev surface_dtx_dev = {
-+	.mdev = {
-+		.minor = MISC_DYNAMIC_MINOR,
-+		.name = "surface_dtx",
-+		.fops = &surface_dtx_fops,
-+	},
-+	.client_lock = __SPIN_LOCK_UNLOCKED(),
-+	.input_lock = __SPIN_LOCK_UNLOCKED(),
-+	.mutex  = __MUTEX_INITIALIZER(surface_dtx_dev.mutex),
-+	.active = false,
-+};
-+
-+
-+static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event)
++static int ssh_receive_buf(struct serdev_device *serdev,
++			   const unsigned char *buf, size_t size)
 +{
-+	struct surface_dtx_client *client;
-+
-+	rcu_read_lock();
-+	list_for_each_entry_rcu(client, &ddev->client_list, node) {
-+		spin_lock(&client->buffer_lock);
-+
-+		client->buffer[client->buffer_head++] = *event;
-+		client->buffer_head &= SURFACE_DTX_CLIENT_BUF_SIZE - 1;
-+
-+		if (unlikely(client->buffer_head == client->buffer_tail)) {
-+			printk(DTX_WARN "event buffer overrun\n");
-+			client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1);
-+		}
++	struct sam_ssh_ec *ec = serdev_device_get_drvdata(serdev);
++	struct ssh_receiver *rcv = &ec->receiver;
++	unsigned long flags;
++	int offs = 0;
++	int used, n;
 +
-+		spin_unlock(&client->buffer_lock);
++	dev_dbg(&serdev->dev, SSH_RECV_TAG "received buffer (size: %zu)\n", size);
++	print_hex_dump_debug(SSH_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false);
 +
-+		kill_fasync(&client->fasync, SIGIO, POLL_IN);
-+	}
-+	rcu_read_unlock();
++	/*
++	 * The battery _BIX message gets a bit long, thus we have to add some
++	 * additional buffering here.
++	 */
 +
-+	wake_up_interruptible(&ddev->waitq);
-+}
++	spin_lock_irqsave(&rcv->lock, flags);
 +
++	// copy to eval-buffer
++	used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len));
++	memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used);
++	rcv->eval_buf.len += used;
 +
-+static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev)
-+{
-+	struct surface_dtx_event event;
-+	int opmode;
++	// evaluate buffer until we need more bytes or eval-buf is empty
++	while (offs < rcv->eval_buf.len) {
++		n = rcv->eval_buf.len - offs;
++		n = ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n);
++		if (n <= 0) break;	// need more bytes
 +
-+	// get operation mode
-+	opmode = sg5_ec_query_opmpde();
-+	if (opmode < 0) {
-+		printk(DTX_ERR "EC request failed with error %d\n", opmode);
++		offs += n;
 +	}
 +
-+	// send DTX event
-+	event.type = 0x11;
-+	event.code = 0x0D;
-+	event.arg0 = opmode;
-+	event.arg1 = 0x00;
++	// throw away the evaluated parts
++	rcv->eval_buf.len -= offs;
++	memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len);
 +
-+	surface_dtx_push_event(ddev, &event);
++	spin_unlock_irqrestore(&rcv->lock, flags);
 +
-+	// send SW_TABLET_MODE event
-+	spin_lock(&ddev->input_lock);
-+	input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00);
-+	input_sync(ddev->input_dev);
-+	spin_unlock(&ddev->input_lock);
++	return used;
 +}
 +
-+static int surface_dtx_evt_dtx(struct surfacegen5_event *in_event, void *data)
-+{
-+	struct surface_dtx_dev *ddev = data;
-+	struct surface_dtx_event event;
 +
-+	switch (in_event->cid) {
-+	case SG5_EVENT_DTX_CID_CONNECTION:
-+	case SG5_EVENT_DTX_CID_BUTTON:
-+	case SG5_EVENT_DTX_CID_ERROR:
-+	case SG5_EVENT_DTX_CID_LATCH_STATUS:
-+		if (in_event->len > 2) {
-+			printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n",
-+			       in_event->cid, in_event->len);
-+			return 0;
-+		}
++#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE
 +
-+		event.type = in_event->tc;
-+		event.code = in_event->cid;
-+		event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00;
-+		event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00;
-+		surface_dtx_push_event(ddev, &event);
-+		break;
++#include <linux/sysfs.h>
 +
-+	default:
-+		printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid);
-+	}
++static char sam_ssh_debug_rqst_buf_sysfs[SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1] = { 0 };
++static char sam_ssh_debug_rqst_buf_pld[SURFACE_SAM_SSH_MAX_RQST_PAYLOAD] = { 0 };
++static char sam_ssh_debug_rqst_buf_res[SURFACE_SAM_SSH_MAX_RQST_RESPONSE] = { 0 };
 +
-+	// update device mode
-+	if (in_event->cid == SG5_EVENT_DTX_CID_CONNECTION) {
-+		if (in_event->pld[0]) {
-+			// Note: we're already in a workqueue task
-+			msleep(SG5_DTX_CONNECT_OPMODE_DELAY);
-+		}
 +
-+		surface_dtx_update_opmpde(ddev);
++static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
++                         char *buf, loff_t offs, size_t count)
++{
++	if (offs < 0 || count + offs > SURFACE_SAM_SSH_MAX_RQST_RESPONSE) {
++		return -EINVAL;
 +	}
 +
-+	return 0;
++	memcpy(buf, sam_ssh_debug_rqst_buf_sysfs + offs, count);
++	return count;
 +}
 +
-+static int surface_dtx_events_setup(struct surface_dtx_dev *ddev)
++static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
++			  char *buf, loff_t offs, size_t count)
 +{
++	struct surface_sam_ssh_rqst rqst = {};
++	struct surface_sam_ssh_buf result = {};
 +	int status;
 +
-+	status = surfacegen5_ec_set_event_handler(SG5_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev);
-+	if (status) {
-+		goto err_event_handler;
++	// check basic write constriants
++	if (offs != 0 || count > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD + 5) {
++		return -EINVAL;
++	}
++
++	// payload length should be consistent with data provided
++	if (buf[4] + 5 != count) {
++		return -EINVAL;
 +	}
 +
-+	status = surfacegen5_ec_enable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID);
++	rqst.tc  = buf[0];
++	rqst.iid = buf[1];
++	rqst.cid = buf[2];
++	rqst.snc = buf[3];
++	rqst.cdl = buf[4];
++	rqst.pld = sam_ssh_debug_rqst_buf_pld;
++	memcpy(sam_ssh_debug_rqst_buf_pld, buf + 5, count - 5);
++
++	result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE;
++	result.len = 0;
++	result.data = sam_ssh_debug_rqst_buf_res;
++
++	status = surface_sam_ssh_rqst(&rqst, &result);
 +	if (status) {
-+		goto err_event_source;
++		return status;
 +	}
 +
-+	return 0;
++	sam_ssh_debug_rqst_buf_sysfs[0] = result.len;
++	memcpy(sam_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len);
++	memset(sam_ssh_debug_rqst_buf_sysfs + result.len + 1, 0,
++	       SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1 - result.len);
 +
-+err_event_source:
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID);
-+err_event_handler:
-+	return status;
++	return count;
 +}
 +
-+static void surface_dtx_events_disable(void)
-+{
-+	surfacegen5_ec_disable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID);
-+	surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID);
-+}
++static const BIN_ATTR_RW(rqst, SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1);
 +
 +
-+static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev)
++int surface_sam_ssh_sysfs_register(struct device *dev)
 +{
-+	struct input_dev *input_dev;
-+	int status;
-+
-+	input_dev = input_allocate_device();
-+	if (!input_dev) {
-+		return ERR_PTR(-ENOMEM);
-+	}
-+
-+	input_dev->name = SG5_DTX_INPUT_NAME;
-+	input_dev->dev.parent = &pdev->dev;
-+	input_dev->id.bustype = BUS_VIRTUAL;
-+	input_dev->id.vendor  = USB_VENDOR_ID_MICROSOFT;
-+	input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION;
-+
-+	input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
++	return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst);
++}
 +
-+	status = sg5_ec_query_opmpde();
-+	if (status < 0) {
-+		input_free_device(input_dev);
-+		return ERR_PTR(status);
-+	}
++void surface_sam_ssh_sysfs_unregister(struct device *dev)
++{
++	sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst);
++}
 +
-+	input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00);
++#else	/* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */
 +
-+	status = input_register_device(input_dev);
-+	if (status) {
-+		input_unregister_device(input_dev);
-+		return ERR_PTR(status);
-+	}
++int surface_sam_ssh_sysfs_register(struct device *dev)
++{
++	return 0;
++}
 +
-+	return input_dev;
++void surface_sam_ssh_sysfs_unregister(struct device *dev)
++{
 +}
 +
++#endif	/* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */
++
 +
-+static int surfacegen5_acpi_dtx_probe(struct platform_device *pdev)
++static acpi_status
++ssh_setup_from_resource(struct acpi_resource *resource, void *context)
 +{
-+	struct surface_dtx_dev *ddev = &surface_dtx_dev;
-+	struct input_dev *input_dev;
-+	int status;
++	struct serdev_device *serdev = context;
++	struct acpi_resource_common_serialbus *serial;
++	struct acpi_resource_uart_serialbus *uart;
++	int status = 0;
 +
-+	// link to ec
-+	status = surfacegen5_ec_consumer_register(&pdev->dev);
-+	if (status) {
-+		return status == -ENXIO ? -EPROBE_DEFER : status;
++	if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) {
++		return AE_OK;
 +	}
 +
-+	input_dev = surface_dtx_register_inputdev(pdev);
-+	if (IS_ERR(input_dev)) {
-+		return PTR_ERR(input_dev);
++	serial = &resource->data.common_serial_bus;
++	if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) {
++		return AE_OK;
 +	}
 +
-+	// initialize device
-+	mutex_lock(&ddev->mutex);
-+	if (ddev->active) {
-+		mutex_unlock(&ddev->mutex);
-+		status = -ENODEV;
-+		goto err_register;
++	uart = &resource->data.uart_serial_bus;
++
++	// set up serdev device
++	serdev_device_set_baudrate(serdev, uart->default_baud_rate);
++
++	// serdev currently only supports RTSCTS flow control
++	if (uart->flow_control & SSH_SUPPORTED_FLOW_CONTROL_MASK) {
++		dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control);
 +	}
 +
-+	INIT_LIST_HEAD(&ddev->client_list);
-+	init_waitqueue_head(&ddev->waitq);
-+	ddev->active = true;
-+	ddev->input_dev = input_dev;
-+	mutex_unlock(&ddev->mutex);
++	// set RTSCTS flow control
++	serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW);
 +
-+	status = misc_register(&ddev->mdev);
-+	if (status) {
-+		goto err_register;
++	// serdev currently only supports EVEN/ODD parity
++	switch (uart->parity) {
++	case ACPI_UART_PARITY_NONE:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
++		break;
++	case ACPI_UART_PARITY_EVEN:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);
++		break;
++	case ACPI_UART_PARITY_ODD:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);
++		break;
++	default:
++		dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity);
++		break;
 +	}
 +
-+	// enable events
-+	status = surface_dtx_events_setup(ddev);
 +	if (status) {
-+		goto err_events_setup;
++		dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity);
++		return status;
 +	}
 +
-+	return 0;
-+
-+err_events_setup:
-+	misc_deregister(&ddev->mdev);
-+err_register:
-+	input_unregister_device(ddev->input_dev);
-+	return status;
++	return AE_CTRL_TERMINATE;       // we've found the resource and are done
 +}
 +
-+static int surfacegen5_acpi_dtx_remove(struct platform_device *pdev)
++
++static bool ssh_idma_filter(struct dma_chan *chan, void *param)
 +{
-+	struct surface_dtx_dev *ddev = &surface_dtx_dev;
-+	struct surface_dtx_client *client;
++	// see dw8250_idma_filter
++	return param == chan->device->dev;
++}
 +
-+	mutex_lock(&ddev->mutex);
-+	if (!ddev->active) {
-+		mutex_unlock(&ddev->mutex);
-+		return 0;
-+	}
++static int ssh_check_dma(struct serdev_device *serdev)
++{
++	struct device *dev = serdev->ctrl->dev.parent;
++	struct dma_chan *rx, *tx;
++	dma_cap_mask_t mask;
++	int status = 0;
 +
-+	// mark as inactive
-+	ddev->active = false;
-+	mutex_unlock(&ddev->mutex);
++	/*
++	 * The EC UART requires DMA for proper communication. If we don't use DMA,
++	 * we'll drop bytes when the system has high load, e.g. during boot. This
++	 * causes some ugly behaviour, i.e. battery information (_BIX) messages
++	 * failing frequently. We're making sure the required DMA channels are
++	 * available here so serial8250_do_startup is able to grab them later
++	 * instead of silently falling back to a non-DMA approach.
++	 */
 +
-+	// After this call we're guaranteed that no more input events will arive
-+	surface_dtx_events_disable();
++	dma_cap_zero(mask);
++	dma_cap_set(DMA_SLAVE, mask);
 +
-+	// wake up clients
-+	spin_lock(&ddev->client_lock);
-+	list_for_each_entry(client, &ddev->client_list, node) {
-+		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
++	rx = dma_request_slave_channel_compat(mask, ssh_idma_filter, dev->parent, dev, "rx");
++	if (IS_ERR_OR_NULL(rx)) {
++		status = rx ? PTR_ERR(rx) : -EPROBE_DEFER;
++		goto out;
 +	}
-+	spin_unlock(&ddev->client_lock);
-+
-+	wake_up_interruptible(&ddev->waitq);
 +
-+	// unregister user-space devices
-+	input_unregister_device(ddev->input_dev);
-+	misc_deregister(&ddev->mdev);
++	tx = dma_request_slave_channel_compat(mask, ssh_idma_filter, dev->parent, dev, "tx");
++	if (IS_ERR_OR_NULL(tx)) {
++		status = tx ? PTR_ERR(tx) : -EPROBE_DEFER;
++		goto release_rx;
++	}
 +
-+	return 0;
++	dma_release_channel(tx);
++release_rx:
++	dma_release_channel(rx);
++out:
++	return status;
 +}
 +
 +
-+static const struct acpi_device_id surfacegen5_acpi_dtx_match[] = {
-+	{ "MSHW0133", 0 },
-+	{ },
-+};
-+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_dtx_match);
-+
-+struct platform_driver surfacegen5_acpi_dtx = {
-+	.probe = surfacegen5_acpi_dtx_probe,
-+	.remove = surfacegen5_acpi_dtx_remove,
-+	.driver = {
-+		.name = "surfacegen5_acpi_dtx",
-+		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_dtx_match),
-+	},
-+};
-+
-+
-+inline int surfacegen5_acpi_dtx_register(void)
++static int surface_sam_ssh_suspend(struct device *dev)
 +{
-+	return platform_driver_register(&surfacegen5_acpi_dtx);
-+}
++	struct sam_ssh_ec *ec;
++	int status = 0;
 +
-+inline void surfacegen5_acpi_dtx_unregister(void)
-+{
-+	platform_driver_unregister(&surfacegen5_acpi_dtx);
-+}
++	dev_dbg(dev, "suspending\n");
 +
-+#else /* CONFIG_SURFACE_ACPI_DTX */
++	ec = surface_sam_ssh_acquire_init();
++	if (ec) {
++		status = surface_sam_ssh_ec_suspend(ec);
++		if (status) {
++			dev_err(dev, "failed to suspend EC: %d\n", status);
++		}
 +
-+inline int surfacegen5_acpi_dtx_register(void)
-+{
-+	return 0;
++		ec->state = SSH_EC_SUSPENDED;
++		surface_sam_ssh_release(ec);
++	}
++
++	return status;
 +}
 +
-+inline void surfacegen5_acpi_dtx_unregister(void)
++static int surface_sam_ssh_resume(struct device *dev)
 +{
-+}
++	struct sam_ssh_ec *ec;
++	int status = 0;
 +
-+#endif /* CONFIG_SURFACE_ACPI_DTX */
++	dev_dbg(dev, "resuming\n");
 +
++	ec = surface_sam_ssh_acquire_init();
++	if (ec) {
++		ec->state = SSH_EC_INITIALIZED;
 +
-+/*************************************************************************
-+ * Surface Platform Integration Driver
-+ */
++		status = surface_sam_ssh_ec_resume(ec);
++		if (status) {
++			dev_err(dev, "failed to resume EC: %d\n", status);
++		}
 +
-+#ifdef CONFIG_SURFACE_ACPI_SID
++		surface_sam_ssh_release(ec);
++	}
 +
-+struct si_lid_device {
-+	const char *acpi_path;
-+	const u32 gpe_number;
-+};
++	return status;
++}
 +
-+struct si_device_info {
-+	const bool has_perf_mode;
-+	const struct si_lid_device *lid_device;
-+};
++static SIMPLE_DEV_PM_OPS(surface_sam_ssh_pm_ops, surface_sam_ssh_suspend, surface_sam_ssh_resume);
 +
 +
-+static const struct si_lid_device lid_device_l17 = {
-+	.acpi_path = "\\_SB.LID0",
-+	.gpe_number = 0x17,
++static const struct serdev_device_ops ssh_device_ops = {
++	.receive_buf  = ssh_receive_buf,
++	.write_wakeup = serdev_device_write_wakeup,
 +};
 +
-+static const struct si_lid_device lid_device_l4F = {
-+	.acpi_path = "\\_SB.LID0",
-+	.gpe_number = 0x4F,
-+};
 +
-+static const struct si_lid_device lid_device_l57 = {
-+	.acpi_path = "\\_SB.LID0",
-+	.gpe_number = 0x57,
-+};
++int surface_sam_ssh_sysfs_register(struct device *dev);
++void surface_sam_ssh_sysfs_unregister(struct device *dev);
 +
++static int surface_sam_ssh_probe(struct serdev_device *serdev)
++{
++	struct sam_ssh_ec *ec;
++	struct workqueue_struct *event_queue_ack;
++	struct workqueue_struct *event_queue_evt;
++	u8 *write_buf;
++	u8 *read_buf;
++	u8 *eval_buf;
++	acpi_handle *ssh = ACPI_HANDLE(&serdev->dev);
++	acpi_status status;
 +
-+static const struct si_device_info si_device_pro_4 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l17,
-+};
++	dev_dbg(&serdev->dev, "probing\n");
 +
-+static const struct si_device_info si_device_pro_5 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l4F,
-+};
++	// ensure DMA is ready before we set up the device
++	status = ssh_check_dma(serdev);
++	if (status) {
++		return status;
++	}
 +
-+static const struct si_device_info si_device_pro_6 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l4F,
-+};
++	// allocate buffers
++	write_buf = kzalloc(SSH_WRITE_BUF_LEN, GFP_KERNEL);
++	if (!write_buf) {
++		status = -ENOMEM;
++		goto err_write_buf;
++	}
 +
-+static const struct si_device_info si_device_book_1 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l17,
-+};
++	read_buf = kzalloc(SSH_READ_BUF_LEN, GFP_KERNEL);
++	if (!read_buf) {
++		status = -ENOMEM;
++		goto err_read_buf;
++	}
 +
-+static const struct si_device_info si_device_book_2 = {
-+	.has_perf_mode = true,
-+	.lid_device = &lid_device_l17,
-+};
++	eval_buf = kzalloc(SSH_EVAL_BUF_LEN, GFP_KERNEL);
++	if (!eval_buf) {
++		status = -ENOMEM;
++		goto err_eval_buf;
++	}
 +
-+static const struct si_device_info si_device_laptop_1 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l57,
-+};
++	event_queue_ack = create_singlethread_workqueue("surface_sh_ackq");
++	if (!event_queue_ack) {
++		status = -ENOMEM;
++		goto err_ackq;
++	}
 +
-+static const struct si_device_info si_device_laptop_2 = {
-+	.has_perf_mode = false,
-+	.lid_device = &lid_device_l57,
-+};
++	event_queue_evt = create_workqueue("surface_sh_evtq");
++	if (!event_queue_evt) {
++		status = -ENOMEM;
++		goto err_evtq;
++	}
 +
++	// set up EC
++	ec = surface_sam_ssh_acquire();
++	if (ec->state != SSH_EC_UNINITIALIZED) {
++		dev_err(&serdev->dev, "embedded controller already initialized\n");
++		surface_sam_ssh_release(ec);
 +
-+static const struct dmi_system_id dmi_lid_device_table[] = {
-+	{
-+		.ident = "Surface Pro 4",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
-+		},
-+		.driver_data = (void *)&si_device_pro_4,
-+	},
-+	{
-+		.ident = "Surface Pro 5",
-+		.matches = {
-+			/* match for SKU here due to generic product name "Surface Pro" */
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
-+		},
-+		.driver_data = (void *)&si_device_pro_5,
-+	},
-+	{
-+		.ident = "Surface Pro 5 (LTE)",
-+		.matches = {
-+			/* match for SKU here due to generic product name "Surface Pro" */
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
-+		},
-+		.driver_data = (void *)&si_device_pro_5,
-+	},
-+	{
-+		.ident = "Surface Pro 6",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
-+		},
-+		.driver_data = (void *)&si_device_pro_6,
-+	},
-+	{
-+		.ident = "Surface Book 1",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
-+		},
-+		.driver_data = (void *)&si_device_book_1,
-+	},
-+	{
-+		.ident = "Surface Book 2",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
-+		},
-+		.driver_data = (void *)&si_device_book_2,
-+	},
-+	{
-+		.ident = "Surface Laptop 1",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
-+		},
-+		.driver_data = (void *)&si_device_laptop_1,
-+	},
-+	{
-+		.ident = "Surface Laptop 2",
-+		.matches = {
-+			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
-+			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
-+		},
-+		.driver_data = (void *)&si_device_laptop_2,
-+	},
-+	{ }
-+};
-+
-+#define SG5_PARAM_PERM		(S_IRUGO | S_IWUSR)
++		status = -EBUSY;
++		goto err_busy;
++	}
 +
-+enum sg5_perf_mode {
-+	SG5_PERF_MODE_NORMAL   = 1,
-+	SG5_PERF_MODE_BATTERY  = 2,
-+	SG5_PERF_MODE_PERF1    = 3,
-+	SG5_PERF_MODE_PERF2    = 4,
++	ec->serdev      = serdev;
++	ec->writer.data = write_buf;
++	ec->writer.ptr  = write_buf;
 +
-+	__SG5_PERF_MODE__START = 1,
-+	__SG5_PERF_MODE__END   = 4,
-+};
++	// initialize receiver
++	init_completion(&ec->receiver.signal);
++	kfifo_init(&ec->receiver.fifo, read_buf, SSH_READ_BUF_LEN);
++	ec->receiver.eval_buf.ptr = eval_buf;
++	ec->receiver.eval_buf.cap = SSH_EVAL_BUF_LEN;
++	ec->receiver.eval_buf.len = 0;
 +
-+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,
++	// initialize event handling
++	ec->events.queue_ack = event_queue_ack;
++	ec->events.queue_evt = event_queue_evt;
 +
-+	__SG5_PARAM_PERF_MODE__START = 0,
-+	__SG5_PARAM_PERF_MODE__END   = 4,
-+};
++	ec->state = SSH_EC_INITIALIZED;
 +
++	serdev_device_set_drvdata(serdev, ec);
 +
-+static int sg5_ec_perf_mode_get(void)
-+{
-+	u8 result_buf[8] = { 0 };
-+	int status;
++	// ensure everything is properly set-up before we open the device
++	smp_mb();
 +
-+	struct surfacegen5_rqst rqst = {
-+		.tc  = 0x03,
-+		.iid = 0x00,
-+		.cid = 0x02,
-+		.snc = 0x01,
-+		.cdl = 0x00,
-+		.pld = NULL,
-+	};
++	serdev_device_set_client_ops(serdev, &ssh_device_ops);
++	status = serdev_device_open(serdev);
++	if (status) {
++		goto err_open;
++	}
 +
-+	struct surfacegen5_buf result = {
-+		.cap = ARRAY_SIZE(result_buf),
-+		.len = 0,
-+		.data = result_buf,
-+	};
++	status = acpi_walk_resources(ssh, METHOD_NAME__CRS,
++	                             ssh_setup_from_resource, serdev);
++	if (ACPI_FAILURE(status)) {
++		goto err_devinit;
++	}
 +
-+	status = surfacegen5_ec_rqst(&rqst, &result);
++	status = surface_sam_ssh_ec_resume(ec);
 +	if (status) {
-+		return status;
++		goto err_devinit;
 +	}
 +
-+	if (result.len != 8) {
-+		return -EFAULT;
++	status = surface_sam_ssh_sysfs_register(&serdev->dev);
++	if (status) {
++		goto err_devinit;
 +	}
 +
-+	return get_unaligned_le32(&result.data[0]);
-+}
++	surface_sam_ssh_release(ec);
 +
-+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,
-+	};
++	acpi_walk_dep_device_list(ssh);
 +
-+	if (perf_mode < __SG5_PERF_MODE__START || perf_mode > __SG5_PERF_MODE__END) {
-+		return -EINVAL;
-+	}
++	return 0;
 +
-+	put_unaligned_le32(perf_mode, &rqst.pld[0]);
-+	return surfacegen5_ec_rqst(&rqst, NULL);
++err_devinit:
++	serdev_device_close(serdev);
++err_open:
++	ec->state = SSH_EC_UNINITIALIZED;
++	serdev_device_set_drvdata(serdev, NULL);
++	surface_sam_ssh_release(ec);
++err_busy:
++	destroy_workqueue(event_queue_evt);
++err_evtq:
++	destroy_workqueue(event_queue_ack);
++err_ackq:
++	kfree(eval_buf);
++err_eval_buf:
++	kfree(read_buf);
++err_read_buf:
++	kfree(write_buf);
++err_write_buf:
++	return status;
 +}
 +
-+
-+static int param_perf_mode_set(const char *val, const struct kernel_param *kp)
++static void surface_sam_ssh_remove(struct serdev_device *serdev)
 +{
-+	int perf_mode;
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
 +	int status;
 +
-+	status = kstrtoint(val, 0, &perf_mode);
-+	if (status) {
-+		return status;
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return;
 +	}
 +
-+	if (perf_mode < __SG5_PARAM_PERF_MODE__START || perf_mode > __SG5_PARAM_PERF_MODE__END) {
-+		return -EINVAL;
++	surface_sam_ssh_sysfs_unregister(&serdev->dev);
++
++	// suspend EC and disable events
++	status = surface_sam_ssh_ec_suspend(ec);
++	if (status) {
++		dev_err(&serdev->dev, "failed to suspend EC: %d\n", status);
 +	}
 +
-+	return param_set_int(val, kp);
++	// make sure all events (received up to now) have been properly handled
++	flush_workqueue(ec->events.queue_ack);
++	flush_workqueue(ec->events.queue_evt);
++
++	// remove event handlers
++	spin_lock_irqsave(&ec->events.lock, flags);
++	memset(ec->events.handler, 0,
++	       sizeof(struct ssh_event_handler)
++	        * SAM_NUM_EVENT_TYPES);
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++
++	// set device to deinitialized state
++	ec->state  = SSH_EC_UNINITIALIZED;
++	ec->serdev = NULL;
++
++	// ensure state and serdev get set before continuing
++	smp_mb();
++
++	/*
++	 * Flush any event that has not been processed yet to ensure we're not going to
++	 * use the serial device any more (e.g. for ACKing).
++	 */
++	flush_workqueue(ec->events.queue_ack);
++	flush_workqueue(ec->events.queue_evt);
++
++	serdev_device_close(serdev);
++
++	/*
++         * Only at this point, no new events can be received. Destroying the
++         * workqueue here flushes all remaining events. Those events will be
++         * silently ignored and neither ACKed nor any handler gets called.
++	 */
++	destroy_workqueue(ec->events.queue_ack);
++	destroy_workqueue(ec->events.queue_evt);
++
++	// free writer
++	kfree(ec->writer.data);
++	ec->writer.data = NULL;
++	ec->writer.ptr  = NULL;
++
++	// free receiver
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	ec->receiver.state = SSH_RCV_DISCARD;
++	kfifo_free(&ec->receiver.fifo);
++
++	kfree(ec->receiver.eval_buf.ptr);
++	ec->receiver.eval_buf.ptr = NULL;
++	ec->receiver.eval_buf.cap = 0;
++	ec->receiver.eval_buf.len = 0;
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
++
++	serdev_device_set_drvdata(serdev, NULL);
++	surface_sam_ssh_release(ec);
 +}
 +
-+static const struct kernel_param_ops param_perf_mode_ops = {
-+	.set = param_perf_mode_set,
-+	.get = param_get_int,
++
++static const struct acpi_device_id surface_sam_ssh_match[] = {
++	{ "MSHW0084", 0 },
++	{ },
 +};
++MODULE_DEVICE_TABLE(acpi, surface_sam_ssh_match);
 +
-+static int param_perf_mode_init = SG5_PARAM_PERF_MODE_AS_IS;
-+static int param_perf_mode_exit = SG5_PARAM_PERF_MODE_AS_IS;
++struct serdev_device_driver surface_sam_ssh = {
++	.probe = surface_sam_ssh_probe,
++	.remove = surface_sam_ssh_remove,
++	.driver = {
++		.name = "surface_sam_ssh",
++		.acpi_match_table = ACPI_PTR(surface_sam_ssh_match),
++		.pm = &surface_sam_ssh_pm_ops,
++	},
++};
++module_serdev_device_driver(surface_sam_ssh);
 +
-+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_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.h b/drivers/platform/x86/surface_sam/surface_sam_ssh.h
+new file mode 100644
+index 000000000000..89407ec2d4a4
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.h
+@@ -0,0 +1,91 @@
++/*
++ * Interface for Surface Serial Hub (SSH).
++ *
++ * The SSH is the main communication hub for communication between host and
++ * the Surface/System Aggregator Module (SAM) on newer Microsoft Surface
++ * devices (Book 2, Pro 5, Laptops, ...). Also referred to as SAM-over-SSH.
++ * Older devices (Book 1, Pro 4) use SAM-over-I2C.
++ */
 +
-+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");
++#ifndef _SURFACE_SAM_SSH_H
++#define _SURFACE_SAM_SSH_H
 +
-+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data)
-+{
-+	int perf_mode;
++#include <linux/types.h>
++#include <linux/device.h>
 +
-+	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);
-+}
++/*
++ * Maximum request payload size in bytes.
++ * Value based on ACPI (255 bytes minus header/status bytes).
++ */
++#define SURFACE_SAM_SSH_MAX_RQST_PAYLOAD	(255 - 10)
 +
-+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr,
-+                               const char *data, size_t count)
-+{
-+	int perf_mode;
-+	int status;
++/*
++ * Maximum response payload size in bytes.
++ * Value based on ACPI (255 bytes minus header/status bytes).
++ */
++#define SURFACE_SAM_SSH_MAX_RQST_RESPONSE	(255 - 4)
 +
-+	status = kstrtoint(data, 0, &perf_mode);
-+	if (status) {
-+		return status;
-+	}
++/*
++ * The number of (lower) bits of the request ID (RQID) reserved for events.
++ * These bits may only be used exclusively for events sent from the EC to the
++ * host.
++ */
++#define SURFACE_SAM_SSH_RQID_EVENT_BITS		5
 +
-+	status = sg5_ec_perf_mode_set(perf_mode);
-+	if (status) {
-+		return status;
-+	}
++/*
++ * Special event-handler delay value indicating that the corresponding event
++ * should be handled immediately in the interrupt and not be relayed through
++ * the workqueue. Intended for low-latency events, such as keyboard events.
++ */
++#define SURFACE_SAM_SSH_EVENT_IMMEDIATE		((unsigned long) -1)
 +
-+	// 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;
-+}
++struct surface_sam_ssh_buf {
++	u8 cap;
++	u8 len;
++	u8 *data;
++};
 +
-+const static DEVICE_ATTR_RW(perf_mode);
++struct surface_sam_ssh_rqst {
++	u8 tc;
++	u8 iid;
++	u8 cid;
++	u8 snc;
++	u8 cdl;
++	u8 *pld;
++};
++
++struct surface_sam_ssh_event {
++	u16 rqid;
++	u8  tc;
++	u8  iid;
++	u8  cid;
++	u8  len;
++	u8 *pld;
++};
++
++
++typedef int (*surface_sam_ssh_event_handler_fn)(struct surface_sam_ssh_event *event, void *data);
++typedef unsigned long (*surface_sam_ssh_event_handler_delay)(struct surface_sam_ssh_event *event, void *data);
++
++int surface_sam_ssh_consumer_register(struct device *consumer);
++
++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result);
++
++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid);
++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid);
++int surface_sam_ssh_remove_event_handler(u16 rqid);
++
++int surface_sam_ssh_set_delayed_event_handler(u16 rqid,
++		surface_sam_ssh_event_handler_fn fn,
++		surface_sam_ssh_event_handler_delay delay,
++		void *data);
 +
-+static int sid_perf_mode_setup(struct platform_device *pdev, const struct si_device_info *info)
++static inline int surface_sam_ssh_set_event_handler(u16 rqid, surface_sam_ssh_event_handler_fn fn, void *data)
 +{
-+	int status;
++	return surface_sam_ssh_set_delayed_event_handler(rqid, fn, NULL, data);
++}
 +
-+	if (!info->has_perf_mode)
-+		return 0;
 +
-+	// link to ec
-+	status = surfacegen5_ec_consumer_register(&pdev->dev);
-+	if (status) {
-+		return status == -ENXIO ? -EPROBE_DEFER : status;
-+	}
++#endif /* _SURFACE_SAM_SSH_H */
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_vhf.c
+new file mode 100644
+index 000000000000..38851a07ea80
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_vhf.c
+@@ -0,0 +1,286 @@
++/*
++ * Virtual HID Framwork (VHF) driver for input events via SAM.
++ * Used for keyboard input events on the Surface Laptops.
++ */
++
++#include <linux/acpi.h>
++#include <linux/hid.h>
++#include <linux/input.h>
++#include <linux/platform_device.h>
++#include <linux/types.h>
++
++#include "surface_sam_ssh.h"
 +
-+	// 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) {
-+			return status;
-+		}
-+	}
 +
-+	// register perf_mode attribute
-+	status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
-+	if (status) {
-+		goto err_sysfs;
-+	}
++#define USB_VENDOR_ID_MICROSOFT		0x045e
++#define USB_DEVICE_ID_MS_VHF		0xf001
 +
-+	return 0;
++#define VHF_INPUT_NAME			"Microsoft Virtual HID Framework Device"
 +
-+err_sysfs:
-+	sg5_ec_perf_mode_set(param_perf_mode_exit);
-+	return status;
-+}
++/*
++ * Request ID for VHF events. This value is based on the output of the Surface
++ * EC and should not be changed.
++ */
++#define SAM_EVENT_VHF_RQID		0x0001
++#define SAM_EVENT_VHF_TC		0x08
 +
-+static void sid_perf_mode_remove(struct platform_device *pdev, const struct si_device_info *info)
-+{
-+	if (!info->has_perf_mode)
-+		return;
 +
-+	// remove perf_mode attribute
-+	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++struct vhf_evtctx {
++	struct device     *dev;
++	struct hid_device *hid;
++};
 +
-+	// set exit perf_mode
-+	sg5_ec_perf_mode_set(param_perf_mode_exit);
-+}
++struct vhf_drvdata {
++	struct vhf_evtctx event_ctx;
++};
 +
 +
-+static int sid_lid_enable_wakeup(const struct si_device_info *info, bool enable)
-+{
-+	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
-+	int status;
++/*
++ * These report descriptors have been extracted from a Surface Book 2.
++ * They seems to be similar enough to be usable on the Surface Laptop.
++ */
++static const u8 vhf_hid_desc[] = {
++	// keyboard descriptor (event command ID 0x03)
++	0x05, 0x01,             /*  Usage Page (Desktop),                   */
++	0x09, 0x06,             /*  Usage (Keyboard),                       */
++	0xA1, 0x01,             /*  Collection (Application),               */
++	0x85, 0x01,             /*      Report ID (1),                      */
++	0x15, 0x00,             /*      Logical Minimum (0),                */
++	0x25, 0x01,             /*      Logical Maximum (1),                */
++	0x75, 0x01,             /*      Report Size (1),                    */
++	0x95, 0x08,             /*      Report Count (8),                   */
++	0x05, 0x07,             /*      Usage Page (Keyboard),              */
++	0x19, 0xE0,             /*      Usage Minimum (KB Leftcontrol),     */
++	0x29, 0xE7,             /*      Usage Maximum (KB Right GUI),       */
++	0x81, 0x02,             /*      Input (Variable),                   */
++	0x75, 0x08,             /*      Report Size (8),                    */
++	0x95, 0x0A,             /*      Report Count (10),                  */
++	0x19, 0x00,             /*      Usage Minimum (None),               */
++	0x29, 0x91,             /*      Usage Maximum (KB LANG2),           */
++	0x26, 0xFF, 0x00,       /*      Logical Maximum (255),              */
++	0x81, 0x00,             /*      Input,                              */
++	0x05, 0x0C,             /*      Usage Page (Consumer),              */
++	0x0A, 0xC0, 0x02,       /*      Usage (02C0h),                      */
++	0xA1, 0x02,             /*      Collection (Logical),               */
++	0x1A, 0xC1, 0x02,       /*          Usage Minimum (02C1h),          */
++	0x2A, 0xC6, 0x02,       /*          Usage Maximum (02C6h),          */
++	0x95, 0x06,             /*          Report Count (6),               */
++	0xB1, 0x03,             /*          Feature (Constant, Variable),   */
++	0xC0,                   /*      End Collection,                     */
++	0x05, 0x08,             /*      Usage Page (LED),                   */
++	0x19, 0x01,             /*      Usage Minimum (01h),                */
++	0x29, 0x03,             /*      Usage Maximum (03h),                */
++	0x75, 0x01,             /*      Report Size (1),                    */
++	0x95, 0x03,             /*      Report Count (3),                   */
++	0x25, 0x01,             /*      Logical Maximum (1),                */
++	0x91, 0x02,             /*      Output (Variable),                  */
++	0x95, 0x05,             /*      Report Count (5),                   */
++	0x91, 0x01,             /*      Output (Constant),                  */
++	0xC0,                   /*  End Collection,                         */
 +
-+	if (!info->lid_device)
-+		return 0;
++	// media key descriptor (event command ID 0x04)
++	0x05, 0x0C,             /*  Usage Page (Consumer),                  */
++	0x09, 0x01,             /*  Usage (Consumer Control),               */
++	0xA1, 0x01,             /*  Collection (Application),               */
++	0x85, 0x03,             /*      Report ID (3),                      */
++	0x75, 0x10,             /*      Report Size (16),                   */
++	0x15, 0x00,             /*      Logical Minimum (0),                */
++	0x26, 0xFF, 0x03,       /*      Logical Maximum (1023),             */
++	0x19, 0x00,             /*      Usage Minimum (00h),                */
++	0x2A, 0xFF, 0x03,       /*      Usage Maximum (03FFh),              */
++	0x81, 0x00,             /*      Input,                              */
++	0xC0,                   /*  End Collection,                         */
++};
 +
-+	status = acpi_set_gpe_wake_mask(NULL, info->lid_device->gpe_number, action);
-+	if (status)
-+		return -EFAULT;
 +
++static int vhf_hid_start(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
 +	return 0;
 +}
 +
-+static int sid_lid_device_setup(const struct si_device_info *info)
++static void vhf_hid_stop(struct hid_device *hid)
 +{
-+	acpi_handle lid_handle;
-+	int status;
-+
-+	if (!info->lid_device)
-+		return 0;
-+
-+	status = acpi_get_handle(NULL, (acpi_string)info->lid_device->acpi_path, &lid_handle);
-+	if (status)
-+		return -EFAULT;
-+
-+	status = acpi_setup_gpe_for_wake(lid_handle, NULL, info->lid_device->gpe_number);
-+	if (status)
-+		return -EFAULT;
-+
-+	status = acpi_enable_gpe(NULL, info->lid_device->gpe_number);
-+	if (status)
-+		return -EFAULT;
++	hid_dbg(hid, "%s\n", __func__);
++}
 +
-+	return sid_lid_enable_wakeup(info, false);
++static int vhf_hid_open(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
++	return 0;
 +}
 +
-+static void sid_lid_device_remove(const struct si_device_info *info)
++static void vhf_hid_close(struct hid_device *hid)
 +{
-+	/* restore default behavior without this module */
-+	sid_lid_enable_wakeup(info, false);
++	hid_dbg(hid, "%s\n", __func__);
 +}
 +
++static int vhf_hid_parse(struct hid_device *hid)
++{
++	return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc));
++}
 +
-+static int surfacegen5_acpi_sid_suspend(struct device *dev)
++static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
++			       u8 *buf, size_t len, unsigned char rtype,
++			       int reqtype)
 +{
-+	const struct si_device_info *info = dev_get_drvdata(dev);
-+	return sid_lid_enable_wakeup(info, true);
++	hid_dbg(hid, "%s\n", __func__);
++	return 0;
 +}
 +
-+static int surfacegen5_acpi_sid_resume(struct device *dev)
++static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len)
 +{
-+	const struct si_device_info *info = dev_get_drvdata(dev);
-+	return sid_lid_enable_wakeup(info, false);
++	hid_dbg(hid, "%s\n", __func__);
++	print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false);
++
++	return len;
 +}
 +
-+static SIMPLE_DEV_PM_OPS(surfacegen5_acpi_sid_pm, surfacegen5_acpi_sid_suspend, surfacegen5_acpi_sid_resume);
++static struct hid_ll_driver vhf_hid_ll_driver = {
++	.start         = vhf_hid_start,
++	.stop          = vhf_hid_stop,
++	.open          = vhf_hid_open,
++	.close         = vhf_hid_close,
++	.parse         = vhf_hid_parse,
++	.raw_request   = vhf_hid_raw_request,
++	.output_report = vhf_hid_output_report,
++};
 +
 +
-+static int surfacegen5_acpi_sid_probe(struct platform_device *pdev)
++static struct hid_device *vhf_create_hid_device(struct platform_device *pdev)
 +{
-+	const struct dmi_system_id *dmi_match;
-+	struct si_device_info *info;
-+	int status;
-+
-+	dmi_match = dmi_first_match(dmi_lid_device_table);
-+	if (!dmi_match)
-+		return -ENODEV;
++	struct hid_device *hid;
 +
-+	info = dmi_match->driver_data;
++	hid = hid_allocate_device();
++	if (IS_ERR(hid)) {
++		return hid;
++	}
 +
-+	platform_set_drvdata(pdev, info);
++	hid->dev.parent = &pdev->dev;
 +
-+	status = sid_perf_mode_setup(pdev, info);
-+	if (status)
-+		goto err_perf_mode;
++	hid->bus     = BUS_VIRTUAL;
++	hid->vendor  = USB_VENDOR_ID_MICROSOFT;
++	hid->product = USB_DEVICE_ID_MS_VHF;
 +
-+	status = sid_lid_device_setup(info);
-+	if (status)
-+		goto err_lid;
++	hid->ll_driver = &vhf_hid_ll_driver;
 +
-+	return 0;
++	sprintf(hid->name, "%s", VHF_INPUT_NAME);
 +
-+err_lid:
-+	sid_perf_mode_remove(pdev, info);
-+err_perf_mode:
-+	return status;
++	return hid;
 +}
 +
-+static int surfacegen5_acpi_sid_remove(struct platform_device *pdev)
++static int vhf_event_handler(struct surface_sam_ssh_event *event, void *data)
 +{
-+	const struct si_device_info *info = platform_get_drvdata(pdev);
++	struct vhf_evtctx *ctx = (struct vhf_evtctx *)data;
 +
-+	sid_perf_mode_remove(pdev, info);
-+	sid_lid_device_remove(info);
++	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
++		return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1);
++	}
 +
-+	platform_set_drvdata(pdev, NULL);
++	dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid);
 +	return 0;
 +}
 +
-+static const struct acpi_device_id surfacegen5_acpi_sid_match[] = {
-+	{ "MSHW0081", },	/* Surface Pro 4, 5, and 6 */
-+	{ "MSHW0080", },	/* Surface Book 1 */
-+	{ "MSHW0107", },	/* Surface Book 2 */
-+	{ "MSHW0086", },	/* Surface Laptop 1 */
-+	{ "MSHW0112", },	/* Surface Laptop 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),
-+		.pm = &surfacegen5_acpi_sid_pm,
-+	},
-+};
-+
-+inline int surfacegen5_acpi_sid_register(void)
-+{
-+	return platform_driver_register(&surfacegen5_acpi_sid);
-+}
-+
-+inline void surfacegen5_acpi_sid_unregister(void)
++static unsigned long vhf_event_delay(struct surface_sam_ssh_event *event, void *data)
 +{
-+	platform_driver_unregister(&surfacegen5_acpi_sid);
-+}
-+
-+#else /* CONFIG_SURFACE_ACPI_SID */
++	// high priority immediate execution for keyboard events
++	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
++		return SURFACE_SAM_SSH_EVENT_IMMEDIATE;
++	}
 +
-+inline int surfacegen5_acpi_sid_register(void)
-+{
 +	return 0;
 +}
 +
-+inline void surfacegen5_acpi_sid_unregister(void)
-+{
-+}
-+
-+#endif /* CONFIG_SURFACE_ACPI_SID */
-+
-+
-+/*************************************************************************
-+ * Module initialization
-+ */
-+
-+int __init surface_acpi_init(void)
++static int surface_sam_vhf_probe(struct platform_device *pdev)
 +{
++	struct vhf_drvdata *drvdata;
++	struct hid_device *hid;
 +	int status;
 +
-+	status = surfacegen5_acpi_ssh_register();
++	// add device link to EC
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
 +	if (status) {
-+		goto err_ssh;
++		return status == -ENXIO ? -EPROBE_DEFER : status;
 +	}
 +
-+	status = surfacegen5_acpi_san_register();
-+	if (status) {
-+		goto err_san;
++	drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		return -ENOMEM;
++	}
++
++	hid = vhf_create_hid_device(pdev);
++	if (IS_ERR(hid)) {
++		status = PTR_ERR(hid);
++		goto err_probe_hid;
 +	}
 +
-+	status = surfacegen5_acpi_vhf_register();
++	status = hid_add_device(hid);
 +	if (status) {
-+		goto err_vhf;
++		goto err_add_hid;
 +	}
 +
-+	status = surfacegen5_acpi_dtx_register();
++	drvdata->event_ctx.dev = &pdev->dev;
++	drvdata->event_ctx.hid = hid;
++
++	platform_set_drvdata(pdev, drvdata);
++
++	status = surface_sam_ssh_set_delayed_event_handler(
++			SAM_EVENT_VHF_RQID,
++	                vhf_event_handler,
++	                vhf_event_delay,
++			&drvdata->event_ctx);
 +	if (status) {
-+		goto err_dtx;
++		goto err_add_hid;
 +	}
 +
-+	status = surfacegen5_acpi_sid_register();
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID);
 +	if (status) {
-+		goto err_sid;
++		goto err_event_source;
 +	}
 +
 +	return 0;
 +
-+err_sid:
-+	surfacegen5_acpi_sid_unregister();
-+err_dtx:
-+	surfacegen5_acpi_vhf_unregister();
-+err_vhf:
-+	surfacegen5_acpi_san_unregister();
-+err_san:
-+	surfacegen5_acpi_ssh_unregister();
-+err_ssh:
++err_event_source:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID);
++err_add_hid:
++	hid_destroy_device(hid);
++	platform_set_drvdata(pdev, NULL);
++err_probe_hid:
++	kfree(drvdata);
 +	return status;
 +}
 +
-+void __exit surface_acpi_exit(void)
++static int surface_sam_vhf_remove(struct platform_device *pdev)
 +{
-+	surfacegen5_acpi_sid_unregister();
-+	surfacegen5_acpi_dtx_unregister();
-+	surfacegen5_acpi_vhf_unregister();
-+	surfacegen5_acpi_san_unregister();
-+	surfacegen5_acpi_ssh_unregister();
++	struct vhf_drvdata *drvdata = platform_get_drvdata(pdev);
++
++	surface_sam_ssh_disable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID);
++
++	hid_destroy_device(drvdata->event_ctx.hid);
++	kfree(drvdata);
++
++	platform_set_drvdata(pdev, NULL);
++	return 0;
 +}
 +
-+module_init(surface_acpi_init)
-+module_exit(surface_acpi_exit)
++
++static const struct acpi_device_id surface_sam_vhf_match[] = {
++	{ "MSHW0096" },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match);
++
++struct platform_driver surface_sam_vhf = {
++	.probe = surface_sam_vhf_probe,
++	.remove = surface_sam_vhf_remove,
++	.driver = {
++		.name = "surface_sam_vhf",
++		.acpi_match_table = ACPI_PTR(surface_sam_vhf_match),
++	},
++};
++module_platform_driver(surface_sam_vhf);
 +
 +MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
-+MODULE_DESCRIPTION("ACPI/Platform Drivers for Microsoft Surface Devices");
++MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices");
 +MODULE_LICENSE("GPL v2");
 diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c
 index 9db93f500b4e..42d1dae34b21 100644

+ 2 - 2
patches/4.19/0002-suspend.patch

@@ -1,6 +1,6 @@
-From fa7e9dbe07d4e3a7e5bc90483fb3855bf37a55b8 Mon Sep 17 00:00:00 2001
+From f784868ed2318515d368e4c994eb99ea2ca73886 Mon Sep 17 00:00:00 2001
 From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com>
-Date: Fri, 20 Sep 2019 01:03:29 +0900
+Date: Sat, 28 Sep 2019 17:48:21 +0200
 Subject: [PATCH 02/12] suspend
 
 ---

+ 2 - 2
patches/4.19/0003-buttons.patch

@@ -1,6 +1,6 @@
-From 5d229605142d04aed881b5df78faf510a3782e4d Mon Sep 17 00:00:00 2001
+From 6eb9078484ae4d59a705fc4eabb54d5b60bd5109 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:42:15 +0200
+Date: Sat, 27 Jul 2019 17:51:37 +0200
 Subject: [PATCH 03/12] buttons
 
 ---

+ 2 - 2
patches/4.19/0004-cameras.patch

@@ -1,6 +1,6 @@
-From 053effa42ff66c5287a54ee5890ffb36731a99c4 Mon Sep 17 00:00:00 2001
+From d3e2d5cde68c18fdebe77456c9e360d17af8f225 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:42:25 +0200
+Date: Sat, 28 Sep 2019 17:53:54 +0200
 Subject: [PATCH 04/12] cameras
 
 ---

+ 5 - 5
patches/4.19/0005-ipts.patch

@@ -1,6 +1,6 @@
-From ba20bcf9e7fa8faba1a373d89ecc18b3f9211d5a Mon Sep 17 00:00:00 2001
-From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com>
-Date: Tue, 10 Sep 2019 21:52:46 +0900
+From 1174178edc84d79e1f361bf7917e81d92b0a1f1b Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Sat, 28 Sep 2019 17:58:17 +0200
 Subject: [PATCH 05/12] ipts
 
 ---
@@ -6722,7 +6722,7 @@ index 000000000000..9c34b55ff036
 +
 +#endif // _IPTS_H_
 diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h
-index cdd7af16d5ee..c1bd39324c98 100644
+index f85aa3f4042d..2daace422dce 100644
 --- a/drivers/misc/mei/hw-me-regs.h
 +++ b/drivers/misc/mei/hw-me-regs.h
 @@ -119,6 +119,7 @@
@@ -6734,7 +6734,7 @@ index cdd7af16d5ee..c1bd39324c98 100644
  #define MEI_DEV_ID_SPT_H_2    0xA13B  /* Sunrise Point H 2 */
  
 diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
-index e41f9e0a3fdf..e61be367d7e4 100644
+index 28cdd87851cb..c13ba550435c 100644
 --- a/drivers/misc/mei/pci-me.c
 +++ b/drivers/misc/mei/pci-me.c
 @@ -86,6 +86,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {

+ 2 - 2
patches/4.19/0006-hid.patch

@@ -1,6 +1,6 @@
-From b0bbf860d2a601d8c565858b5f3524c94627ab25 Mon Sep 17 00:00:00 2001
+From 5ebbd99f31ba145796039bfeaf672d219aeeaf85 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:42:50 +0200
+Date: Sat, 28 Sep 2019 17:58:43 +0200
 Subject: [PATCH 06/12] hid
 
 ---

+ 2 - 2
patches/4.19/0007-sdcard-reader.patch

@@ -1,6 +1,6 @@
-From 88a3bb4523f4681fd995f6c179e298317d57666f Mon Sep 17 00:00:00 2001
+From 7c09061a90a28a9fe2df8e5dc3b9fa8390b40548 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:43:03 +0200
+Date: Sat, 28 Sep 2019 17:59:13 +0200
 Subject: [PATCH 07/12] sdcard-reader
 
 ---

+ 2 - 2
patches/4.19/0008-wifi.patch

@@ -1,6 +1,6 @@
-From 91d5d2387c622beda4a808d030d26088f2561715 Mon Sep 17 00:00:00 2001
+From 428a110a8e7d8ca6dbf4d916ea4f861747c8f285 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:43:14 +0200
+Date: Sat, 28 Sep 2019 18:00:19 +0200
 Subject: [PATCH 08/12] wifi
 
 ---

+ 6 - 6
patches/4.19/0009-surface3-power.patch

@@ -1,6 +1,6 @@
-From 735dba4a5b07638b17ba1ac139e7028bac29cf1d Mon Sep 17 00:00:00 2001
+From 85edee56032573eafc1f58fe612e629b8df694b3 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:43:27 +0200
+Date: Sat, 28 Sep 2019 18:00:43 +0200
 Subject: [PATCH 09/12] surface3-power
 
 ---
@@ -11,10 +11,10 @@ Subject: [PATCH 09/12] surface3-power
  create mode 100644 drivers/platform/x86/surface3_power.c
 
 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index 9a47363a0c30..d6695d8fc795 100644
+index ea17f993320e..7cee1015981d 100644
 --- a/drivers/platform/x86/Kconfig
 +++ b/drivers/platform/x86/Kconfig
-@@ -1257,6 +1257,13 @@ config SURFACE_3_BUTTON
+@@ -1160,6 +1160,13 @@ config SURFACE_3_BUTTON
  	---help---
  	  This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet.
  
@@ -29,10 +29,10 @@ index 9a47363a0c30..d6695d8fc795 100644
  	tristate "Intel P-Unit IPC Driver"
  	---help---
 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
-index 2250a32a5527..dd045ccf3a90 100644
+index ddc2fbfaf110..cbea9579c1d2 100644
 --- a/drivers/platform/x86/Makefile
 +++ b/drivers/platform/x86/Makefile
-@@ -82,6 +82,7 @@ obj-$(CONFIG_INTEL_PMC_IPC)	+= intel_pmc_ipc.o
+@@ -81,6 +81,7 @@ obj-$(CONFIG_INTEL_PMC_IPC)	+= intel_pmc_ipc.o
  obj-$(CONFIG_TOUCHSCREEN_DMI)	+= touchscreen_dmi.o
  obj-$(CONFIG_SURFACE_PRO3_BUTTON)	+= surfacepro3_button.o
  obj-$(CONFIG_SURFACE_3_BUTTON)	+= surface3_button.o

+ 26 - 24
patches/4.19/0010-mwlwifi.patch

@@ -1,12 +1,12 @@
-From 9545f8525aa758207c974215ee6b635516aeac82 Mon Sep 17 00:00:00 2001
+From 34bb8ff418427f44896891eab0d8739335491118 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:43:45 +0200
+Date: Sat, 28 Sep 2019 18:01:27 +0200
 Subject: [PATCH 10/12] mwlwifi
 
 ---
  drivers/net/wireless/marvell/Kconfig          |    1 +
  drivers/net/wireless/marvell/Makefile         |    1 +
- drivers/net/wireless/marvell/mwlwifi/Kconfig  |   22 +
+ drivers/net/wireless/marvell/mwlwifi/Kconfig  |   23 +
  drivers/net/wireless/marvell/mwlwifi/Makefile |   19 +
  .../wireless/marvell/mwlwifi/Makefile.module  |   28 +
  .../net/wireless/marvell/mwlwifi/README.md    |  142 +
@@ -35,7 +35,7 @@ Subject: [PATCH 10/12] mwlwifi
  ...-workaround-for-80+80-and-160-MHz-channels |   32 +
  .../wireless/marvell/mwlwifi/hostapd/README   |   26 +
  .../net/wireless/marvell/mwlwifi/mac80211.c   |  933 ++++
- .../net/wireless/marvell/mwlwifi/mu_mimo.c    |   20 +
+ .../net/wireless/marvell/mwlwifi/mu_mimo.c    |   21 +
  .../net/wireless/marvell/mwlwifi/mu_mimo.h    |   23 +
  .../net/wireless/marvell/mwlwifi/sysadpt.h    |   86 +
  .../net/wireless/marvell/mwlwifi/thermal.c    |  182 +
@@ -44,7 +44,7 @@ Subject: [PATCH 10/12] mwlwifi
  drivers/net/wireless/marvell/mwlwifi/utils.h  |  158 +
  .../net/wireless/marvell/mwlwifi/vendor_cmd.c |  136 +
  .../net/wireless/marvell/mwlwifi/vendor_cmd.h |   60 +
- 40 files changed, 19415 insertions(+)
+ 40 files changed, 19417 insertions(+)
  create mode 100644 drivers/net/wireless/marvell/mwlwifi/Kconfig
  create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile
  create mode 100644 drivers/net/wireless/marvell/mwlwifi/Makefile.module
@@ -109,10 +109,10 @@ index 1b0a7d2bc8e6..04dff3388a41 100644
  obj-$(CONFIG_MWL8K)	+= mwl8k.o
 diff --git a/drivers/net/wireless/marvell/mwlwifi/Kconfig b/drivers/net/wireless/marvell/mwlwifi/Kconfig
 new file mode 100644
-index 000000000000..8832217430f5
+index 000000000000..a9bcb9cd4100
 --- /dev/null
 +++ b/drivers/net/wireless/marvell/mwlwifi/Kconfig
-@@ -0,0 +1,22 @@
+@@ -0,0 +1,23 @@
 +config MWLWIFI
 +	tristate "Marvell Avastar 88W8864/88W8897 PCIe driver (mac80211 compatible)"
 +	depends on PCI && MAC80211
@@ -135,6 +135,7 @@ index 000000000000..8832217430f5
 +		select either MWIFIEX or MWLWIFI, not both. MWIFIEX is fullmac,
 +		supporting more comprehensive client functions for laptops/embedded
 +		devices. MWLWIFI is mac80211-based for full AP/Wireless Bridge.
++
 diff --git a/drivers/net/wireless/marvell/mwlwifi/Makefile b/drivers/net/wireless/marvell/mwlwifi/Makefile
 new file mode 100644
 index 000000000000..061833703c7f
@@ -196,7 +197,7 @@ index 000000000000..d11a1b88cab6
 +	find . -name "*.o" -exec rm -f {} \;
 diff --git a/drivers/net/wireless/marvell/mwlwifi/README.md b/drivers/net/wireless/marvell/mwlwifi/README.md
 new file mode 100644
-index 000000000000..530dc33e7f41
+index 000000000000..788c5d4dc80d
 --- /dev/null
 +++ b/drivers/net/wireless/marvell/mwlwifi/README.md
 @@ -0,0 +1,142 @@
@@ -225,7 +226,7 @@ index 000000000000..530dc33e7f41
 +    commit 03a72eacda5d9a1837a74387081596a0d5466ec1
 +    Author: Jouni Malinen <jouni@qca.qualcomm.com>
 +    Date:   Thu Dec 17 18:39:19 2015 +0200
-+
++    
 +    VHT: Add an interoperability workaround for 80+80 and 160 MHz channels
 +
 +    Number of deployed 80 MHz capable VHT stations that do not support 80+80
@@ -256,10 +257,10 @@ index 000000000000..530dc33e7f41
 +
 +    #Disable 5g band
 +    marvell,5ghz = <0>;
-+
++    
 +    #Specify antenna number, default is 4x4. For WRT1200AC, you must set these values to 2x2.
 +    marvell,chainmask = <4 4>;
-+
++    
 +    #Specify external power table. If your device needs external power table, you must provide the power table via this parameter, otherwise the Tx power will be pretty low.
 +    marvell,powertable
 +    ```
@@ -268,7 +269,7 @@ index 000000000000..530dc33e7f41
 +    ```sh
 +    cat /sys/kernel/debug/ieee80211/phy0/mwlwifi/info
 +    ```
-+
++    
 +    You should see a line in the results which looks like the following:
 +    ```sh
 +    power table loaded from dts: no
@@ -292,7 +293,7 @@ index 000000000000..530dc33e7f41
 +    There are two ways to resolve this problem:
 +    * Please don't change country code and let mwlwifi set it for you.
 +    * Remove phy2. Under this case, even though you change country code, mwlwifi will reject it. Because phy2 is not existed, country code setting won't be conflicted. To do this, run the following commands (for OpenWrt/LEDE):
-+
++    
 +        ```sh
 +        opkg remove kmod-mwifiex-sdio
 +        opkg remove mwifiex-sdio-firmware
@@ -17404,7 +17405,7 @@ index 000000000000..2ad5f381b9ee
 +#endif /* _TX_NDP_H_ */
 diff --git a/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels b/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels
 new file mode 100644
-index 000000000000..c73d02e31977
+index 000000000000..adadd2e4d8d4
 --- /dev/null
 +++ b/drivers/net/wireless/marvell/mwlwifi/hostapd/700-interoperability-workaround-for-80+80-and-160-MHz-channels
 @@ -0,0 +1,32 @@
@@ -17413,9 +17414,9 @@ index 000000000000..c73d02e31977
 +--- a/src/ap/ieee802_11_vht.c
 ++++ b/src/ap/ieee802_11_vht.c
 +@@ -82,6 +82,27 @@ u8 * hostapd_eid_vht_operation(struct hostapd_data *hapd, u8 *eid)
-+
-+	oper->vht_op_info_chwidth = hapd->iconf->vht_oper_chwidth;
-+
++ 
++ 	oper->vht_op_info_chwidth = hapd->iconf->vht_oper_chwidth;
++ 
 ++	if (hapd->iconf->vht_oper_chwidth == 2) {
 ++		/*
 ++		 * Convert 160 MHz channel width to new style as interop
@@ -17437,16 +17438,16 @@ index 000000000000..c73d02e31977
 ++		oper->vht_op_info_chwidth = 1;
 ++	}
 ++
-+	/* VHT Basic MCS set comes from hw */
-+	/* Hard code 1 stream, MCS0-7 is a min Basic VHT MCS rates */
-+	oper->vht_basic_mcs_set = host_to_le16(0xfffc);
++ 	/* VHT Basic MCS set comes from hw */
++ 	/* Hard code 1 stream, MCS0-7 is a min Basic VHT MCS rates */
++ 	oper->vht_basic_mcs_set = host_to_le16(0xfffc);
 diff --git a/drivers/net/wireless/marvell/mwlwifi/hostapd/README b/drivers/net/wireless/marvell/mwlwifi/hostapd/README
 new file mode 100644
-index 000000000000..312586e8b5a1
+index 000000000000..a5fb2b68d3d3
 --- /dev/null
 +++ b/drivers/net/wireless/marvell/mwlwifi/hostapd/README
 @@ -0,0 +1,26 @@
-+700-interoperability-workaround-for-80+80-and-160-MHz-channels:
++700-interoperability-workaround-for-80+80-and-160-MHz-channels: 
 +
 +patch for OpenWrt hostapd package 2016-01-15 for following commit
 +(move it to package/network/services/hostapd/patches).
@@ -18413,10 +18414,10 @@ index 000000000000..725dec0f604b
 +};
 diff --git a/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c
 new file mode 100644
-index 000000000000..23c70df6caa8
+index 000000000000..74ab054f947e
 --- /dev/null
 +++ b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.c
-@@ -0,0 +1,20 @@
+@@ -0,0 +1,21 @@
 +/*
 + * Copyright (C) 2006-2018, Marvell International Ltd.
 + *
@@ -18437,6 +18438,7 @@ index 000000000000..23c70df6caa8
 +#include "sysadpt.h"
 +#include "core.h"
 +#include "mu_mimo.h"
++
 diff --git a/drivers/net/wireless/marvell/mwlwifi/mu_mimo.h b/drivers/net/wireless/marvell/mwlwifi/mu_mimo.h
 new file mode 100644
 index 000000000000..24179f404774

+ 2 - 2
patches/4.19/0011-surface-lte.patch

@@ -1,6 +1,6 @@
-From 59199ea3163562e29bc8a5ad2a1c1bf11cc634f9 Mon Sep 17 00:00:00 2001
+From 2b33096313db62f3c76924c2d4b3f50f93a6cbf9 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:43:57 +0200
+Date: Sat, 28 Sep 2019 18:02:03 +0200
 Subject: [PATCH 11/12] surface-lte
 
 ---

+ 5 - 5
patches/4.19/0012-surfacebook2-dgpu.patch

@@ -1,6 +1,6 @@
-From 11d49c9f0d336f6575f38228044a209652d1ede1 Mon Sep 17 00:00:00 2001
+From 06b632658527c60bd254622fc812d5abd4019315 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
-Date: Fri, 26 Jul 2019 03:44:10 +0200
+Date: Sat, 28 Sep 2019 18:02:33 +0200
 Subject: [PATCH 12/12] surfacebook2-dgpu
 
 ---
@@ -11,7 +11,7 @@ Subject: [PATCH 12/12] surfacebook2-dgpu
  create mode 100644 drivers/platform/x86/surfacebook2_dgpu_hps.c
 
 diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
-index d6695d8fc795..16f40109337c 100644
+index 7cee1015981d..75665b560a6f 100644
 --- a/drivers/platform/x86/Kconfig
 +++ b/drivers/platform/x86/Kconfig
 @@ -436,6 +436,15 @@ config SURFACE3_WMI
@@ -31,10 +31,10 @@ index d6695d8fc795..16f40109337c 100644
  	tristate "ThinkPad ACPI Laptop Extras"
  	depends on ACPI
 diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
-index dd045ccf3a90..6d24ede71496 100644
+index cbea9579c1d2..6eb62f822953 100644
 --- a/drivers/platform/x86/Makefile
 +++ b/drivers/platform/x86/Makefile
-@@ -45,6 +45,7 @@ obj-$(CONFIG_ACPI_WMI)		+= wmi.o
+@@ -44,6 +44,7 @@ obj-$(CONFIG_ACPI_WMI)		+= wmi.o
  obj-$(CONFIG_MSI_WMI)		+= msi-wmi.o
  obj-$(CONFIG_PEAQ_WMI)		+= peaq-wmi.o
  obj-$(CONFIG_SURFACE3_WMI)	+= surface3-wmi.o

+ 2 - 2
patches/5.3/0001-surface-acpi.patch

@@ -1,5 +1,5 @@
-From c7543d2458c5282bda2802701324cc77478146d8 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From 140c70aff419f61a2c2b6cca9fe2c1c5ade3629d Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
 Date: Mon, 26 Aug 2019 01:11:08 +0200
 Subject: [PATCH 1/9] surface-acpi
 

+ 1 - 1
patches/5.3/0002-buttons.patch

@@ -1,4 +1,4 @@
-From 83d479932acd83ecf45941386615d8518d79f568 Mon Sep 17 00:00:00 2001
+From f3c8c1722e729e2b8ef4c1cb261fa21fecf13f5d Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
 Date: Sat, 27 Jul 2019 17:51:37 +0200
 Subject: [PATCH 2/9] buttons

+ 1 - 1
patches/5.3/0003-surfacebook2-dgpu.patch

@@ -1,4 +1,4 @@
-From 1f8ca45b0f8650219a98d05f14ccf98c33967ba7 Mon Sep 17 00:00:00 2001
+From 887b26209eea40ed443ad7f1989f0e009f8928dd Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
 Date: Tue, 2 Jul 2019 22:17:46 +0200
 Subject: [PATCH 3/9] surfacebook2-dgpu

+ 2 - 2
patches/5.3/0004-hid.patch

@@ -1,5 +1,5 @@
-From ea18400e859c6d7656b81e5d33314046743d34a1 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From 487a63fd1eb7055ae79248e60d9688cb81c0f30c Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
 Date: Tue, 17 Sep 2019 17:16:23 +0200
 Subject: [PATCH 4/9] hid
 

+ 2 - 2
patches/5.3/0005-surface3-power.patch

@@ -1,5 +1,5 @@
-From a39f413e5460c0d1fb901872518a33038b9d463c Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From 4f79244298b003c92cd9590b767769d5b84b1ee1 Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
 Date: Tue, 17 Sep 2019 17:17:56 +0200
 Subject: [PATCH 5/9] surface3-power
 

+ 2 - 2
patches/5.3/0006-surface-lte.patch

@@ -1,5 +1,5 @@
-From 83e53749458cab7575105ddbae13f750a4cc09e4 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From e260eacab655d64cf9c1489dc37f89c410aa58a0 Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
 Date: Tue, 17 Sep 2019 17:21:43 +0200
 Subject: [PATCH 6/9] surface-lte
 

+ 2 - 2
patches/5.3/0007-wifi.patch

@@ -1,5 +1,5 @@
-From 829c222814c0617dad16d036876f94c725d2d638 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From 2a01efb959a43a387dab97223bcccfe6be4e582f Mon Sep 17 00:00:00 2001
+From: qzed <qzed@users.noreply.github.com>
 Date: Wed, 18 Sep 2019 03:18:25 +0200
 Subject: [PATCH 7/9] wifi
 

+ 2 - 2
patches/5.3/0008-legacy-i915.patch

@@ -1,5 +1,5 @@
-From 05296f2e99a9f6c9ef6fa47e7c879c73b07c7322 Mon Sep 17 00:00:00 2001
-From: Maximilian Luz <luzmaximilian@gmail.com>
+From cc0158b2e542fcb3c9f1102227e4b35316c25040 Mon Sep 17 00:00:00 2001
+From: Dorian Stoll <dorian.stoll@tmsp.io>
 Date: Mon, 16 Sep 2019 04:10:51 +0200
 Subject: [PATCH 8/9] legacy-i915
 

+ 8224 - 19
patches/5.3/0009-ipts.patch

@@ -1,4 +1,4 @@
-From 3def8f788efa2bfd7f541d9282b2353bdbbecf48 Mon Sep 17 00:00:00 2001
+From 8289a12d110b5e2a8749ed94bcfb4330964b4761 Mon Sep 17 00:00:00 2001
 From: Maximilian Luz <luzmaximilian@gmail.com>
 Date: Wed, 18 Sep 2019 13:04:18 +0200
 Subject: [PATCH 9/9] ipts
@@ -15,7 +15,7 @@ Subject: [PATCH 9/9] ipts
  drivers/gpu/drm/i915_legacy/intel_guc.h       |    1 +
  .../drm/i915_legacy/intel_guc_submission.c    |   89 +-
  .../drm/i915_legacy/intel_guc_submission.h    |    4 +
- drivers/gpu/drm/i915_legacy/intel_ipts.c      |  651 ++++++++++
+ drivers/gpu/drm/i915_legacy/intel_ipts.c      |  651 +++
  drivers/gpu/drm/i915_legacy/intel_ipts.h      |   34 +
  drivers/gpu/drm/i915_legacy/intel_lrc.c       |   15 +-
  drivers/gpu/drm/i915_legacy/intel_lrc.h       |    6 +
@@ -26,33 +26,44 @@ Subject: [PATCH 9/9] ipts
  drivers/misc/ipts/Makefile                    |   17 +
  drivers/misc/ipts/companion/Kconfig           |    9 +
  drivers/misc/ipts/companion/Makefile          |    1 +
- drivers/misc/ipts/companion/ipts-surface.c    |  100 ++
- drivers/misc/ipts/ipts-binary-spec.h          |  118 ++
- drivers/misc/ipts/ipts-dbgfs.c                |  291 +++++
- drivers/misc/ipts/ipts-fw.c                   |  113 ++
+ drivers/misc/ipts/companion/ipts-surface.c    |  100 +
+ drivers/misc/ipts/ipts-binary-spec.h          |  118 +
+ drivers/misc/ipts/ipts-dbgfs.c                |  291 ++
+ drivers/misc/ipts/ipts-fw.c                   |  113 +
  drivers/misc/ipts/ipts-fw.h                   |   12 +
- drivers/misc/ipts/ipts-gfx.c                  |  185 +++
+ drivers/misc/ipts/ipts-gfx.c                  |  185 +
  drivers/misc/ipts/ipts-gfx.h                  |   24 +
- drivers/misc/ipts/ipts-hid.c                  |  497 ++++++++
+ drivers/misc/ipts/ipts-hid.c                  |  497 ++
  drivers/misc/ipts/ipts-hid.h                  |   34 +
- drivers/misc/ipts/ipts-kernel.c               | 1042 +++++++++++++++++
+ drivers/misc/ipts/ipts-kernel.c               | 1042 +++++
  drivers/misc/ipts/ipts-kernel.h               |   23 +
- drivers/misc/ipts/ipts-mei-msgs.h             |  585 +++++++++
- drivers/misc/ipts/ipts-mei.c                  |  250 ++++
- drivers/misc/ipts/ipts-msg-handler.c          |  426 +++++++
+ drivers/misc/ipts/ipts-mei-msgs.h             |  585 +++
+ drivers/misc/ipts/ipts-mei.c                  |  250 +
+ drivers/misc/ipts/ipts-msg-handler.c          |  426 ++
  drivers/misc/ipts/ipts-msg-handler.h          |   32 +
  drivers/misc/ipts/ipts-params.c               |   21 +
  drivers/misc/ipts/ipts-params.h               |   14 +
- drivers/misc/ipts/ipts-resource.c             |  277 +++++
+ drivers/misc/ipts/ipts-resource.c             |  277 ++
  drivers/misc/ipts/ipts-resource.h             |   30 +
- drivers/misc/ipts/ipts-sensor-regs.h          |  700 +++++++++++
+ drivers/misc/ipts/ipts-sensor-regs.h          |  700 +++
  drivers/misc/ipts/ipts-state.h                |   29 +
- drivers/misc/ipts/ipts.h                      |  200 ++++
+ drivers/misc/ipts/ipts.h                      |  200 +
  drivers/misc/mei/hw-me-regs.h                 |    1 +
  drivers/misc/mei/pci-me.c                     |    1 +
+ drivers/platform/x86/Kconfig                  |   98 +-
+ drivers/platform/x86/Makefile                 |    2 +-
+ drivers/platform/x86/surface_acpi.c           | 4010 -----------------
+ drivers/platform/x86/surface_sam/Kconfig      |  104 +
+ drivers/platform/x86/surface_sam/Makefile     |    5 +
+ .../x86/surface_sam/surface_sam_dtx.c         |  620 +++
+ .../x86/surface_sam/surface_sam_san.c         |  708 +++
+ .../x86/surface_sam/surface_sam_sid.c         |  483 ++
+ .../x86/surface_sam/surface_sam_ssh.c         | 1691 +++++++
+ .../x86/surface_sam/surface_sam_ssh.h         |   91 +
+ .../x86/surface_sam/surface_sam_vhf.c         |  286 ++
  include/linux/intel_ipts_fw.h                 |   14 +
- include/linux/intel_ipts_if.h                 |   76 ++
- 48 files changed, 6026 insertions(+), 21 deletions(-)
+ include/linux/intel_ipts_if.h                 |   76 +
+ 59 files changed, 10016 insertions(+), 4129 deletions(-)
  create mode 100644 drivers/gpu/drm/i915_legacy/intel_ipts.c
  create mode 100644 drivers/gpu/drm/i915_legacy/intel_ipts.h
  create mode 100644 drivers/misc/ipts/Kconfig
@@ -81,6 +92,15 @@ Subject: [PATCH 9/9] ipts
  create mode 100644 drivers/misc/ipts/ipts-sensor-regs.h
  create mode 100644 drivers/misc/ipts/ipts-state.h
  create mode 100644 drivers/misc/ipts/ipts.h
+ delete mode 100644 drivers/platform/x86/surface_acpi.c
+ create mode 100644 drivers/platform/x86/surface_sam/Kconfig
+ create mode 100644 drivers/platform/x86/surface_sam/Makefile
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_dtx.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.c
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.h
+ create mode 100644 drivers/platform/x86/surface_sam/surface_sam_vhf.c
  create mode 100644 include/linux/intel_ipts_fw.h
  create mode 100644 include/linux/intel_ipts_if.h
 
@@ -6506,7 +6526,7 @@ index 000000000000..9c34b55ff036
 +
 +#endif // _IPTS_H_
 diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h
-index 77f7dff7098d..fb99dafd44a1 100644
+index c09f8bb49495..a3740901988e 100644
 --- a/drivers/misc/mei/hw-me-regs.h
 +++ b/drivers/misc/mei/hw-me-regs.h
 @@ -59,6 +59,7 @@
@@ -6518,7 +6538,7 @@ index 77f7dff7098d..fb99dafd44a1 100644
  #define MEI_DEV_ID_SPT_H_2    0xA13B  /* Sunrise Point H 2 */
  
 diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
-index 541538eff8b1..49ab69d7a273 100644
+index 3a2eadcd0378..28973f96f956 100644
 --- a/drivers/misc/mei/pci-me.c
 +++ b/drivers/misc/mei/pci-me.c
 @@ -77,6 +77,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
@@ -6529,6 +6549,8191 @@ index 541538eff8b1..49ab69d7a273 100644
  	{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H, MEI_ME_PCH8_SPS_CFG)},
  	{MEI_PCI_DEVICE(MEI_DEV_ID_SPT_H_2, MEI_ME_PCH8_SPS_CFG)},
  	{MEI_PCI_DEVICE(MEI_DEV_ID_LBG, MEI_ME_PCH12_CFG)},
+diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
+index c381d14dea20..c3d68aeec587 100644
+--- a/drivers/platform/x86/Kconfig
++++ b/drivers/platform/x86/Kconfig
+@@ -629,103 +629,6 @@ 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.
+ 
+-config SURFACE_ACPI
+-	depends on ACPI
+-	tristate "Microsoft Surface ACPI/Platform Drivers"
+-	---help---
+-	  ACPI and platform drivers for Microsoft Surface devices.
+-
+-config SURFACE_ACPI_SSH
+-	bool "Surface Serial Hub Driver"
+-	depends on SURFACE_ACPI
+-	depends on X86_INTEL_LPSS
+-	depends on SERIAL_8250_DW
+-	depends on SERIAL_8250_DMA
+-	depends on SERIAL_DEV_CTRL_TTYPORT
+-	select CRC_CCITT
+-	default y
+-	---help---
+-	  Surface Serial Hub driver for 5th generation (or later) Microsoft
+-	  Surface devices.
+-
+-	  This is the base driver for the embedded serial controller found on
+-	  5th generation (and later) Microsoft Surface devices (e.g. Book 2,
+-	  Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only
+-	  provides access to the embedded controller and subsequent drivers are
+-	  required for the respective functionalities.
+-
+-	  If you have a 5th generation (or later) Microsoft Surface device, say
+-	  Y or M here.
+-
+-config SURFACE_ACPI_SSH_DEBUG_DEVICE
+-	bool "Surface Serial Hub Debug Device"
+-	depends on SURFACE_ACPI_SSH
+-	default n
+-	---help---
+-	  Debug device for direct communication with the embedded controller
+-	  found on 5th generation (and later) Microsoft Surface devices (e.g.
+-	  Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via sysfs.
+-
+-	  If you are not sure, say N here.
+-
+-config SURFACE_ACPI_SAN
+-	bool "Surface ACPI Notify Driver"
+-	depends on SURFACE_ACPI_SSH
+-	default y
+-	---help---
+-	  Surface ACPI Notify driver for 5th generation (or later) Microsoft
+-	  Surface devices.
+-
+-	  This driver enables basic ACPI events and requests, such as battery
+-	  status requests/events, thermal events, lid status, and possibly more,
+-	  which would otherwise not work on these devices.
+-
+-	  If you are not sure, say Y here.
+-
+-config SURFACE_ACPI_VHF
+-	bool "Surface Virtual HID Framework Driver"
+-	depends on SURFACE_ACPI_SSH
+-	depends on HID
+-	default y
+-	---help---
+-	  Surface Virtual HID Framework driver for 5th generation (or later)
+-	  Microsoft Surface devices.
+-
+-	  This driver provides support for the Microsoft Virtual HID framework,
+-	  which is required for the Surface Laptop (1 and newer) keyboard.
+-
+-	  If you are not sure, say Y here.
+-
+-config SURFACE_ACPI_DTX
+-	bool "Surface Detachment System (DTX) Driver"
+-	depends on SURFACE_ACPI_SSH
+-	depends on INPUT
+-	default y
+-	---help---
+-	  Surface Detachment System (DTX) driver for the Microsoft Surface Book
+-	  2. This driver provides support for proper detachment handling in
+-	  user-space, status-events relating to the base and support for
+-	  the safe-guard keeping the base attached when the discrete GPU
+-	  contained in it is running via the special /dev/surface-dtx device.
+-
+-	  Also provides a standard input device to provide SW_TABLET_MODE events
+-	  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
+@@ -1448,6 +1351,7 @@ config PCENGINES_APU2
+ 	  will be called pcengines-apuv2.
+ 
+ source "drivers/platform/x86/intel_speed_select_if/Kconfig"
++source "drivers/platform/x86/surface_sam/Kconfig"
+ 
+ endif # X86_PLATFORM_DEVICES
+ 
+diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
+index 5c88172c0649..705525ff99a7 100644
+--- a/drivers/platform/x86/Makefile
++++ b/drivers/platform/x86/Makefile
+@@ -39,7 +39,6 @@ obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
+ obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
+ obj-$(CONFIG_IDEAPAD_LAPTOP)	+= ideapad-laptop.o
+ obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
+-obj-$(CONFIG_SURFACE_ACPI)	+= surface_acpi.o
+ obj-$(CONFIG_SENSORS_HDAPS)	+= hdaps.o
+ obj-$(CONFIG_FUJITSU_LAPTOP)	+= fujitsu-laptop.o
+ obj-$(CONFIG_FUJITSU_TABLET)	+= fujitsu-tablet.o
+@@ -103,3 +102,4 @@ obj-$(CONFIG_I2C_MULTI_INSTANTIATE)	+= i2c-multi-instantiate.o
+ obj-$(CONFIG_INTEL_ATOMISP2_PM)	+= intel_atomisp2_pm.o
+ obj-$(CONFIG_PCENGINES_APU2)	+= pcengines-apuv2.o
+ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += intel_speed_select_if/
++obj-$(CONFIG_SURFACE_SAM)	+= surface_sam/
+diff --git a/drivers/platform/x86/surface_acpi.c b/drivers/platform/x86/surface_acpi.c
+deleted file mode 100644
+index 633fd8929037..000000000000
+--- a/drivers/platform/x86/surface_acpi.c
++++ /dev/null
+@@ -1,4010 +0,0 @@
+-#include <asm/unaligned.h>
+-#include <linux/acpi.h>
+-#include <linux/completion.h>
+-#include <linux/crc-ccitt.h>
+-#include <linux/delay.h>
+-#include <linux/device.h>
+-#include <linux/dmaengine.h>
+-#include <linux/dmi.h>
+-#include <linux/fs.h>
+-#include <linux/hid.h>
+-#include <linux/input.h>
+-#include <linux/ioctl.h>
+-#include <linux/jiffies.h>
+-#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>
+-#include <linux/poll.h>
+-#include <linux/rculist.h>
+-#include <linux/refcount.h>
+-#include <linux/serdev.h>
+-#include <linux/spinlock.h>
+-#include <linux/sysfs.h>
+-#include <linux/types.h>
+-#include <linux/workqueue.h>
+-
+-
+-#define USB_VENDOR_ID_MICROSOFT				0x045e
+-#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)
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SSH
+-
+-/*
+- * Maximum request payload size in bytes.
+- * Value based on ACPI (255 bytes minus header/status bytes).
+- */
+-#define SURFACEGEN5_MAX_RQST_PAYLOAD	(255 - 10)
+-
+-/*
+- * Maximum response payload size in bytes.
+- * Value based on ACPI (255 bytes minus header/status bytes).
+- */
+-#define SURFACEGEN5_MAX_RQST_RESPONSE	(255 - 4)
+-
+-#define SURFACEGEN5_RQID_EVENT_BITS	5
+-
+-#define SURFACEGEN5_EVENT_IMMEDIATE	((unsigned long) -1)
+-
+-
+-struct surfacegen5_buf {
+-	u8 cap;
+-	u8 len;
+-	u8 *data;
+-};
+-
+-struct surfacegen5_rqst {
+-	u8 tc;
+-	u8 iid;
+-	u8 cid;
+-	u8 snc;
+-	u8 cdl;
+-	u8 *pld;
+-};
+-
+-struct surfacegen5_event {
+-	u16 rqid;
+-	u8  tc;
+-	u8  iid;
+-	u8  cid;
+-	u8  len;
+-	u8 *pld;
+-};
+-
+-
+-typedef int (*surfacegen5_ec_event_handler_fn)(struct surfacegen5_event *event, void *data);
+-typedef unsigned long (*surfacegen5_ec_event_handler_delay)(struct surfacegen5_event *event, void *data);
+-
+-int surfacegen5_ec_consumer_register(struct device *consumer);
+-
+-int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result);
+-
+-int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid);
+-int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid);
+-int surfacegen5_ec_remove_event_handler(u16 rqid);
+-int surfacegen5_ec_set_event_handler(u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data);
+-int surfacegen5_ec_set_delayed_event_handler(u16 rqid,
+-		surfacegen5_ec_event_handler_fn fn,
+-		surfacegen5_ec_event_handler_delay delay, void *data);
+-
+-#endif /* CONFIG_SURFACE_ACPI_SSH */
+-
+-
+-/*************************************************************************
+- * Surface Serial Hub Debug Device (cross-driver interface)
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SSH
+-
+-int surfacegen5_ssh_sysfs_register(struct device *dev);
+-void surfacegen5_ssh_sysfs_unregister(struct device *dev);
+-
+-#endif /* CONFIG_SURFACE_ACPI_SSH */
+-
+-
+-/*************************************************************************
+- * Surface Serial Hub driver (private implementation)
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SSH
+-
+-#define SG5_RQST_TAG_FULL		"surfacegen5_ec_rqst: "
+-#define SG5_RQST_TAG			"rqst: "
+-#define SG5_EVENT_TAG			"event: "
+-#define SG5_RECV_TAG			"recv: "
+-
+-#define SG5_SUPPORTED_FLOW_CONTROL_MASK		(~((u8) ACPI_UART_FLOW_CONTROL_HW))
+-
+-#define SG5_BYTELEN_SYNC		2
+-#define SG5_BYTELEN_TERM		2
+-#define SG5_BYTELEN_CRC			2
+-#define SG5_BYTELEN_CTRL		4	// command-header, ACK, or RETRY
+-#define SG5_BYTELEN_CMDFRAME		8	// without payload
+-
+-#define SG5_MAX_WRITE (                 \
+-	  SG5_BYTELEN_SYNC              \
+-	+ SG5_BYTELEN_CTRL              \
+-	+ SG5_BYTELEN_CRC               \
+-	+ SG5_BYTELEN_CMDFRAME          \
+-	+ SURFACEGEN5_MAX_RQST_PAYLOAD  \
+-	+ SG5_BYTELEN_CRC               \
+-)
+-
+-#define SG5_MSG_LEN_CTRL (              \
+-	  SG5_BYTELEN_SYNC              \
+-	+ SG5_BYTELEN_CTRL              \
+-	+ SG5_BYTELEN_CRC               \
+-	+ SG5_BYTELEN_TERM              \
+-)
+-
+-#define SG5_MSG_LEN_CMD_BASE (          \
+-	  SG5_BYTELEN_SYNC              \
+-	+ SG5_BYTELEN_CTRL              \
+-	+ SG5_BYTELEN_CRC               \
+-	+ SG5_BYTELEN_CRC               \
+-)	// without payload and command-frame
+-
+-#define SG5_WRITE_TIMEOUT		msecs_to_jiffies(1000)
+-#define SG5_READ_TIMEOUT		msecs_to_jiffies(1000)
+-#define SG5_NUM_RETRY			3
+-
+-#define SG5_WRITE_BUF_LEN		SG5_MAX_WRITE
+-#define SG5_READ_BUF_LEN		512		// must be power of 2
+-#define SG5_EVAL_BUF_LEN		SG5_MAX_WRITE	// also works for reading
+-
+-#define SG5_FRAME_TYPE_CMD		0x80
+-#define SG5_FRAME_TYPE_ACK		0x40
+-#define SG5_FRAME_TYPE_RETRY		0x04
+-
+-#define SG5_FRAME_OFFS_CTRL		SG5_BYTELEN_SYNC
+-#define SG5_FRAME_OFFS_CTRL_CRC		(SG5_FRAME_OFFS_CTRL + SG5_BYTELEN_CTRL)
+-#define SG5_FRAME_OFFS_TERM		(SG5_FRAME_OFFS_CTRL_CRC + SG5_BYTELEN_CRC)
+-#define SG5_FRAME_OFFS_CMD		SG5_FRAME_OFFS_TERM	// either TERM or CMD
+-#define SG5_FRAME_OFFS_CMD_PLD		(SG5_FRAME_OFFS_CMD + SG5_BYTELEN_CMDFRAME)
+-
+-/*
+- * A note on Request IDs (RQIDs):
+- * 	0x0000 is not a valid RQID
+- * 	0x0001 is valid, but reserved for Surface Laptop keyboard events
+- */
+-#define SG5_NUM_EVENT_TYPES		((1 << SURFACEGEN5_RQID_EVENT_BITS) - 1)
+-
+-/*
+- * Sync:			aa 55
+- * Terminate:			ff ff
+- *
+- * Request Message:		sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame)
+- * Ack Message:			sync ack crc(ack) terminate
+- * Retry Message:		sync retry crc(retry) terminate
+- * Response Message:		sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame)
+- *
+- * Command Header:		80 LEN 00 SEQ
+- * Ack:                 	40 00 00 SEQ
+- * Retry:			04 00 00 00
+- * Command Request Frame:	80 RTC 01 00 RIID RQID RCID PLD
+- * Command Response Frame:	80 RTC 00 01 RIID RQID RCID PLD
+- */
+-
+-struct surfacegen5_frame_ctrl {
+-	u8 type;
+-	u8 len;			// without crc
+-	u8 pad;
+-	u8 seq;
+-} __packed;
+-
+-struct surfacegen5_frame_cmd {
+-	u8 type;
+-	u8 tc;
+-	u8 unknown1;
+-	u8 unknown2;
+-	u8 iid;
+-	u8 rqid_lo;		// id for request/response matching (low byte)
+-	u8 rqid_hi;		// id for request/response matching (high byte)
+-	u8 cid;
+-} __packed;
+-
+-
+-enum surfacegen5_ec_state {
+-	SG5_EC_UNINITIALIZED,
+-	SG5_EC_INITIALIZED,
+-	SG5_EC_SUSPENDED,
+-};
+-
+-struct surfacegen5_ec_counters {
+-	u8  seq;		// control sequence id
+-	u16 rqid;		// id for request/response matching
+-};
+-
+-struct surfacegen5_ec_writer {
+-	u8 *data;
+-	u8 *ptr;
+-} __packed;
+-
+-enum surfacegen5_ec_receiver_state {
+-	SG5_RCV_DISCARD,
+-	SG5_RCV_CONTROL,
+-	SG5_RCV_COMMAND,
+-};
+-
+-struct surfacegen5_ec_receiver {
+-	spinlock_t        lock;
+-	enum surfacegen5_ec_receiver_state state;
+-	struct completion signal;
+-	struct kfifo      fifo;
+-	struct {
+-		bool pld;
+-		u8   seq;
+-		u16  rqid;
+-	} expect;
+-	struct {
+-		u16 cap;
+-		u16 len;
+-		u8 *ptr;
+-	} eval_buf;
+-};
+-
+-struct surfacegen5_ec_event_handler {
+-	surfacegen5_ec_event_handler_fn handler;
+-	surfacegen5_ec_event_handler_delay delay;
+-	void *data;
+-};
+-
+-struct surfacegen5_ec_events {
+-	spinlock_t lock;
+-	struct workqueue_struct *queue_ack;
+-	struct workqueue_struct *queue_evt;
+-	struct surfacegen5_ec_event_handler handler[SG5_NUM_EVENT_TYPES];
+-};
+-
+-struct surfacegen5_ec {
+-	struct mutex                   lock;
+-	enum surfacegen5_ec_state      state;
+-	struct serdev_device          *serdev;
+-	struct surfacegen5_ec_counters counter;
+-	struct surfacegen5_ec_writer   writer;
+-	struct surfacegen5_ec_receiver receiver;
+-	struct surfacegen5_ec_events   events;
+-};
+-
+-struct surfacegen5_fifo_packet {
+-	u8 type;	// packet type (ACK/RETRY/CMD)
+-	u8 seq;
+-	u8 len;
+-};
+-
+-struct surfacegen5_event_work {
+-	refcount_t               refcount;
+-	struct surfacegen5_ec   *ec;
+-	struct work_struct       work_ack;
+-	struct delayed_work      work_evt;
+-	struct surfacegen5_event event;
+-	u8                       seq;
+-};
+-
+-
+-static struct surfacegen5_ec surfacegen5_ec = {
+-	.lock   = __MUTEX_INITIALIZER(surfacegen5_ec.lock),
+-	.state  = SG5_EC_UNINITIALIZED,
+-	.serdev = NULL,
+-	.counter = {
+-		.seq  = 0,
+-		.rqid = 0,
+-	},
+-	.writer = {
+-		.data = NULL,
+-		.ptr  = NULL,
+-	},
+-	.receiver = {
+-		.lock = __SPIN_LOCK_UNLOCKED(),
+-		.state = SG5_RCV_DISCARD,
+-		.expect = {},
+-	},
+-	.events = {
+-		.lock = __SPIN_LOCK_UNLOCKED(),
+-		.handler = {},
+-	}
+-};
+-
+-
+-static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec,
+-                                 const struct surfacegen5_rqst *rqst,
+-				 struct surfacegen5_buf *result);
+-
+-
+-inline static struct surfacegen5_ec *surfacegen5_ec_acquire(void)
+-{
+-	struct surfacegen5_ec *ec = &surfacegen5_ec;
+-
+-	mutex_lock(&ec->lock);
+-	return ec;
+-}
+-
+-inline static void surfacegen5_ec_release(struct surfacegen5_ec *ec)
+-{
+-	mutex_unlock(&ec->lock);
+-}
+-
+-inline static struct surfacegen5_ec *surfacegen5_ec_acquire_init(void)
+-{
+-	struct surfacegen5_ec *ec = surfacegen5_ec_acquire();
+-
+-	if (ec->state == SG5_EC_UNINITIALIZED) {
+-		surfacegen5_ec_release(ec);
+-		return NULL;
+-	}
+-
+-	return ec;
+-}
+-
+-int surfacegen5_ec_consumer_register(struct device *consumer)
+-{
+-	u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
+-	struct surfacegen5_ec *ec;
+-	struct device_link *link;
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		return -ENXIO;
+-	}
+-
+-	link = device_link_add(consumer, &ec->serdev->dev, flags);
+-	if (!link) {
+-		return -EFAULT;
+-	}
+-
+-	surfacegen5_ec_release(ec);
+-	return 0;
+-}
+-
+-
+-inline static u16 surfacegen5_rqid_to_rqst(u16 rqid) {
+-	return rqid << SURFACEGEN5_RQID_EVENT_BITS;
+-}
+-
+-inline static bool surfacegen5_rqid_is_event(u16 rqid) {
+-	const u16 mask = (1 << SURFACEGEN5_RQID_EVENT_BITS) - 1;
+-	return rqid != 0 && (rqid | mask) == mask;
+-}
+-
+-int surfacegen5_ec_enable_event_source(u8 tc, u8 unknown, u16 rqid)
+-{
+-	struct surfacegen5_ec *ec;
+-
+-	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
+-	u8 buf[1] = { 0x00 };
+-
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = 0x01,
+-		.iid = 0x00,
+-		.cid = 0x0b,
+-		.snc = 0x01,
+-		.cdl = 0x04,
+-		.pld = pld,
+-	};
+-
+-	struct surfacegen5_buf result = {
+-		result.cap = ARRAY_SIZE(buf),
+-		result.len = 0,
+-		result.data = buf,
+-	};
+-
+-	int status;
+-
+-	// only allow RQIDs that lie within event spectrum
+-	if (!surfacegen5_rqid_is_event(rqid)) {
+-		return -EINVAL;
+-	}
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
+-		return -ENXIO;
+-	}
+-
+-	if (ec->state == SG5_EC_SUSPENDED) {
+-		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
+-
+-		surfacegen5_ec_release(ec);
+-		return -EPERM;
+-	}
+-
+-	status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
+-
+-	if (buf[0] != 0x00) {
+-		dev_warn(&ec->serdev->dev,
+-		         "unexpected result while enabling event source: 0x%02x\n",
+-			 buf[0]);
+-	}
+-
+-	surfacegen5_ec_release(ec);
+-	return status;
+-
+-}
+-
+-int surfacegen5_ec_disable_event_source(u8 tc, u8 unknown, u16 rqid)
+-{
+-	struct surfacegen5_ec *ec;
+-
+-	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
+-	u8 buf[1] = { 0x00 };
+-
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = 0x01,
+-		.iid = 0x00,
+-		.cid = 0x0c,
+-		.snc = 0x01,
+-		.cdl = 0x04,
+-		.pld = pld,
+-	};
+-
+-	struct surfacegen5_buf result = {
+-		result.cap = ARRAY_SIZE(buf),
+-		result.len = 0,
+-		result.data = buf,
+-	};
+-
+-	int status;
+-
+-	// only allow RQIDs that lie within event spectrum
+-	if (!surfacegen5_rqid_is_event(rqid)) {
+-		return -EINVAL;
+-	}
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
+-		return -ENXIO;
+-	}
+-
+-	if (ec->state == SG5_EC_SUSPENDED) {
+-		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
+-
+-		surfacegen5_ec_release(ec);
+-		return -EPERM;
+-	}
+-
+-	status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
+-
+-	if (buf[0] != 0x00) {
+-		dev_warn(&ec->serdev->dev,
+-		         "unexpected result while disabling event source: 0x%02x\n",
+-			 buf[0]);
+-	}
+-
+-	surfacegen5_ec_release(ec);
+-	return status;
+-}
+-
+-int surfacegen5_ec_set_delayed_event_handler(
+-		u16 rqid, surfacegen5_ec_event_handler_fn fn,
+-		surfacegen5_ec_event_handler_delay delay,
+-		void *data)
+-{
+-	struct surfacegen5_ec *ec;
+-	unsigned long flags;
+-
+-	if (!surfacegen5_rqid_is_event(rqid)) {
+-		return -EINVAL;
+-	}
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		return -ENXIO;
+-	}
+-
+-	spin_lock_irqsave(&ec->events.lock, flags);
+-
+-	// 0 is not a valid event RQID
+-	ec->events.handler[rqid - 1].handler = fn;
+-	ec->events.handler[rqid - 1].delay = delay;
+-	ec->events.handler[rqid - 1].data = data;
+-
+-	spin_unlock_irqrestore(&ec->events.lock, flags);
+-	surfacegen5_ec_release(ec);
+-
+-	return 0;
+-}
+-
+-int surfacegen5_ec_set_event_handler(
+-		u16 rqid, surfacegen5_ec_event_handler_fn fn, void *data)
+-{
+-	return surfacegen5_ec_set_delayed_event_handler(rqid, fn, NULL, data);
+-}
+-
+-int surfacegen5_ec_remove_event_handler(u16 rqid)
+-{
+-	struct surfacegen5_ec *ec;
+-	unsigned long flags;
+-
+-	if (!surfacegen5_rqid_is_event(rqid)) {
+-		return -EINVAL;
+-	}
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		return -ENXIO;
+-	}
+-
+-	spin_lock_irqsave(&ec->events.lock, flags);
+-
+-	// 0 is not a valid event RQID
+-	ec->events.handler[rqid - 1].handler = NULL;
+-	ec->events.handler[rqid - 1].delay = NULL;
+-	ec->events.handler[rqid - 1].data = NULL;
+-
+-	spin_unlock_irqrestore(&ec->events.lock, flags);
+-	surfacegen5_ec_release(ec);
+-
+-	/*
+-	 * Make sure that the handler is not in use any more after we've
+-	 * removed it.
+-	 */
+-	flush_workqueue(ec->events.queue_evt);
+-
+-	return 0;
+-}
+-
+-
+-inline static u16 surfacegen5_ssh_crc(const u8 *buf, size_t size)
+-{
+-	return crc_ccitt_false(0xffff, buf, size);
+-}
+-
+-inline static void surfacegen5_ssh_write_u16(struct surfacegen5_ec_writer *writer, u16 in)
+-{
+-	put_unaligned_le16(in, writer->ptr);
+-	writer->ptr += 2;
+-}
+-
+-inline static void surfacegen5_ssh_write_crc(struct surfacegen5_ec_writer *writer,
+-                                             const u8 *buf, size_t size)
+-{
+-	surfacegen5_ssh_write_u16(writer, surfacegen5_ssh_crc(buf, size));
+-}
+-
+-inline static void surfacegen5_ssh_write_syn(struct surfacegen5_ec_writer *writer)
+-{
+-	u8 *w = writer->ptr;
+-
+-	*w++ = 0xaa;
+-	*w++ = 0x55;
+-
+-	writer->ptr = w;
+-}
+-
+-inline static void surfacegen5_ssh_write_ter(struct surfacegen5_ec_writer *writer)
+-{
+-	u8 *w = writer->ptr;
+-
+-	*w++ = 0xff;
+-	*w++ = 0xff;
+-
+-	writer->ptr = w;
+-}
+-
+-inline static void surfacegen5_ssh_write_buf(struct surfacegen5_ec_writer *writer,
+-                                             u8 *in, size_t len)
+-{
+-	writer->ptr = memcpy(writer->ptr, in, len) + len;
+-}
+-
+-inline static void surfacegen5_ssh_write_hdr(struct surfacegen5_ec_writer *writer,
+-                                             const struct surfacegen5_rqst *rqst,
+-                                             struct surfacegen5_ec *ec)
+-{
+-	struct surfacegen5_frame_ctrl *hdr = (struct surfacegen5_frame_ctrl *)writer->ptr;
+-	u8 *begin = writer->ptr;
+-
+-	hdr->type = SG5_FRAME_TYPE_CMD;
+-	hdr->len  = SG5_BYTELEN_CMDFRAME + rqst->cdl;	// without CRC
+-	hdr->pad  = 0x00;
+-	hdr->seq  = ec->counter.seq;
+-
+-	writer->ptr += sizeof(*hdr);
+-
+-	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
+-}
+-
+-inline static void surfacegen5_ssh_write_cmd(struct surfacegen5_ec_writer *writer,
+-                                             const struct surfacegen5_rqst *rqst,
+-                                             struct surfacegen5_ec *ec)
+-{
+-	struct surfacegen5_frame_cmd *cmd = (struct surfacegen5_frame_cmd *)writer->ptr;
+-	u8 *begin = writer->ptr;
+-
+-	u16 rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid);
+-	u8 rqid_lo = rqid & 0xFF;
+-	u8 rqid_hi = rqid >> 8;
+-
+-	cmd->type     = SG5_FRAME_TYPE_CMD;
+-	cmd->tc       = rqst->tc;
+-	cmd->unknown1 = 0x01;
+-	cmd->unknown2 = 0x00;
+-	cmd->iid      = rqst->iid;
+-	cmd->rqid_lo  = rqid_lo;
+-	cmd->rqid_hi  = rqid_hi;
+-	cmd->cid      = rqst->cid;
+-
+-	writer->ptr += sizeof(*cmd);
+-
+-	surfacegen5_ssh_write_buf(writer, rqst->pld, rqst->cdl);
+-	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
+-}
+-
+-inline static void surfacegen5_ssh_write_ack(struct surfacegen5_ec_writer *writer, u8 seq)
+-{
+-	struct surfacegen5_frame_ctrl *ack = (struct surfacegen5_frame_ctrl *)writer->ptr;
+-	u8 *begin = writer->ptr;
+-
+-	ack->type = SG5_FRAME_TYPE_ACK;
+-	ack->len  = 0x00;
+-	ack->pad  = 0x00;
+-	ack->seq  = seq;
+-
+-	writer->ptr += sizeof(*ack);
+-
+-	surfacegen5_ssh_write_crc(writer, begin, writer->ptr - begin);
+-}
+-
+-inline static void surfacegen5_ssh_writer_reset(struct surfacegen5_ec_writer *writer)
+-{
+-	writer->ptr = writer->data;
+-}
+-
+-inline static int surfacegen5_ssh_writer_flush(struct surfacegen5_ec *ec)
+-{
+-	struct surfacegen5_ec_writer *writer = &ec->writer;
+-	struct serdev_device *serdev = ec->serdev;
+-	int status;
+-
+-	size_t len = writer->ptr - writer->data;
+-
+-	dev_dbg(&ec->serdev->dev, "sending message\n");
+-	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
+-	                     writer->data, writer->ptr - writer->data, false);
+-
+-	status = serdev_device_write(serdev, writer->data, len, SG5_WRITE_TIMEOUT);
+-	return status >= 0 ? 0 : status;
+-}
+-
+-inline static void surfacegen5_ssh_write_msg_cmd(struct surfacegen5_ec *ec,
+-                                                 const struct surfacegen5_rqst *rqst)
+-{
+-	surfacegen5_ssh_writer_reset(&ec->writer);
+-	surfacegen5_ssh_write_syn(&ec->writer);
+-	surfacegen5_ssh_write_hdr(&ec->writer, rqst, ec);
+-	surfacegen5_ssh_write_cmd(&ec->writer, rqst, ec);
+-}
+-
+-inline static void surfacegen5_ssh_write_msg_ack(struct surfacegen5_ec *ec, u8 seq)
+-{
+-	surfacegen5_ssh_writer_reset(&ec->writer);
+-	surfacegen5_ssh_write_syn(&ec->writer);
+-	surfacegen5_ssh_write_ack(&ec->writer, seq);
+-	surfacegen5_ssh_write_ter(&ec->writer);
+-}
+-
+-inline static void surfacegen5_ssh_receiver_restart(struct surfacegen5_ec *ec,
+-                                                    const struct surfacegen5_rqst *rqst)
+-{
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&ec->receiver.lock, flags);
+-	reinit_completion(&ec->receiver.signal);
+-	ec->receiver.state = SG5_RCV_CONTROL;
+-	ec->receiver.expect.pld = rqst->snc;
+-	ec->receiver.expect.seq = ec->counter.seq;
+-	ec->receiver.expect.rqid = surfacegen5_rqid_to_rqst(ec->counter.rqid);
+-	ec->receiver.eval_buf.len = 0;
+-	spin_unlock_irqrestore(&ec->receiver.lock, flags);
+-}
+-
+-inline static void surfacegen5_ssh_receiver_discard(struct surfacegen5_ec *ec)
+-{
+-	unsigned long flags;
+-
+-	spin_lock_irqsave(&ec->receiver.lock, flags);
+-	ec->receiver.state = SG5_RCV_DISCARD;
+-	ec->receiver.eval_buf.len = 0;
+-	kfifo_reset(&ec->receiver.fifo);
+-	spin_unlock_irqrestore(&ec->receiver.lock, flags);
+-}
+-
+-static int surfacegen5_ec_rqst_unlocked(struct surfacegen5_ec *ec,
+-                                 const struct surfacegen5_rqst *rqst,
+-				 struct surfacegen5_buf *result)
+-{
+-	struct device *dev = &ec->serdev->dev;
+-	struct surfacegen5_fifo_packet packet = {};
+-	int status;
+-	int try;
+-	unsigned int rem;
+-
+-	if (rqst->cdl > SURFACEGEN5_MAX_RQST_PAYLOAD) {
+-		dev_err(dev, SG5_RQST_TAG "request payload too large\n");
+-		return -EINVAL;
+-	}
+-
+-	// write command in buffer, we may need it multiple times
+-	surfacegen5_ssh_write_msg_cmd(ec, rqst);
+-	surfacegen5_ssh_receiver_restart(ec, rqst);
+-
+-	// send command, try to get an ack response
+-	for (try = 0; try < SG5_NUM_RETRY; try++) {
+-		status = surfacegen5_ssh_writer_flush(ec);
+-		if (status) {
+-			goto ec_rqst_out;
+-		}
+-
+-		rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT);
+-		if (rem) {
+-			// completion assures valid packet, thus ignore returned length
+-			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
+-
+-			if (packet.type == SG5_FRAME_TYPE_ACK) {
+-				break;
+-			}
+-		}
+-	}
+-
+-	// check if we ran out of tries?
+-	if (try >= SG5_NUM_RETRY) {
+-		dev_err(dev, SG5_RQST_TAG "communication failed %d times, giving up\n", try);
+-		status = -EIO;
+-		goto ec_rqst_out;
+-	}
+-
+-	ec->counter.seq  += 1;
+-	ec->counter.rqid += 1;
+-
+-	// get command response/payload
+-	if (rqst->snc && result) {
+-		rem = wait_for_completion_timeout(&ec->receiver.signal, SG5_READ_TIMEOUT);
+-		if (rem) {
+-			// completion assures valid packet, thus ignore returned length
+-			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
+-
+-			if (result->cap < packet.len) {
+-				status = -EINVAL;
+-				goto ec_rqst_out;
+-			}
+-
+-			// completion assures valid packet, thus ignore returned length
+-			(void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len);
+-			result->len = packet.len;
+-		} else {
+-			dev_err(dev, SG5_RQST_TAG "communication timed out\n");
+-			status = -EIO;
+-			goto ec_rqst_out;
+-		}
+-
+-		// send ACK
+-		surfacegen5_ssh_write_msg_ack(ec, packet.seq);
+-		status = surfacegen5_ssh_writer_flush(ec);
+-		if (status) {
+-			goto ec_rqst_out;
+-		}
+-	}
+-
+-ec_rqst_out:
+-	surfacegen5_ssh_receiver_discard(ec);
+-	return status;
+-}
+-
+-int surfacegen5_ec_rqst(const struct surfacegen5_rqst *rqst, struct surfacegen5_buf *result)
+-{
+-	struct surfacegen5_ec *ec;
+-	int status;
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		printk(KERN_WARNING SG5_RQST_TAG_FULL "embedded controller is uninitialized\n");
+-		return -ENXIO;
+-	}
+-
+-	if (ec->state == SG5_EC_SUSPENDED) {
+-		dev_warn(&ec->serdev->dev, SG5_RQST_TAG "embedded controller is suspended\n");
+-
+-		surfacegen5_ec_release(ec);
+-		return -EPERM;
+-	}
+-
+-	status = surfacegen5_ec_rqst_unlocked(ec, rqst, result);
+-
+-	surfacegen5_ec_release(ec);
+-	return status;
+-}
+-
+-
+-static int surfacegen5_ssh_ec_resume(struct surfacegen5_ec *ec)
+-{
+-	u8 buf[1] = { 0x00 };
+-
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = 0x01,
+-		.iid = 0x00,
+-		.cid = 0x16,
+-		.snc = 0x01,
+-		.cdl = 0x00,
+-		.pld = NULL,
+-	};
+-
+-	struct surfacegen5_buf result = {
+-		result.cap = ARRAY_SIZE(buf),
+-		result.len = 0,
+-		result.data = buf,
+-	};
+-
+-	int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
+-	if (status) {
+-		return status;
+-	}
+-
+-	if (buf[0] != 0x00) {
+-		dev_warn(&ec->serdev->dev,
+-		         "unexpected result while trying to resume EC: 0x%02x\n",
+-			 buf[0]);
+-	}
+-
+-	return 0;
+-}
+-
+-static int surfacegen5_ssh_ec_suspend(struct surfacegen5_ec *ec)
+-{
+-	u8 buf[1] = { 0x00 };
+-
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = 0x01,
+-		.iid = 0x00,
+-		.cid = 0x15,
+-		.snc = 0x01,
+-		.cdl = 0x00,
+-		.pld = NULL,
+-	};
+-
+-	struct surfacegen5_buf result = {
+-		result.cap = ARRAY_SIZE(buf),
+-		result.len = 0,
+-		result.data = buf,
+-	};
+-
+-	int status = surfacegen5_ec_rqst_unlocked(ec, &rqst, &result);
+-	if (status) {
+-		return status;
+-	}
+-
+-	if (buf[0] != 0x00) {
+-		dev_warn(&ec->serdev->dev,
+-		         "unexpected result while trying to suspend EC: 0x%02x\n",
+-			 buf[0]);
+-	}
+-
+-	return 0;
+-}
+-
+-
+-inline static bool surfacegen5_ssh_is_valid_syn(const u8 *ptr)
+-{
+-	return ptr[0] == 0xaa && ptr[1] == 0x55;
+-}
+-
+-inline static bool surfacegen5_ssh_is_valid_ter(const u8 *ptr)
+-{
+-	return ptr[0] == 0xff && ptr[1] == 0xff;
+-}
+-
+-inline static bool surfacegen5_ssh_is_valid_crc(const u8 *begin, const u8 *end)
+-{
+-	u16 crc = surfacegen5_ssh_crc(begin, end - begin);
+-	return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8));
+-}
+-
+-
+-static int surfacegen5_ssh_send_ack(struct surfacegen5_ec *ec, u8 seq)
+-{
+-	int status;
+-	u8 buf[SG5_MSG_LEN_CTRL];
+-	u16 crc;
+-
+-	buf[0] = 0xaa;
+-	buf[1] = 0x55;
+-	buf[2] = 0x40;
+-	buf[3] = 0x00;
+-	buf[4] = 0x00;
+-	buf[5] = seq;
+-
+-	crc = surfacegen5_ssh_crc(buf + SG5_FRAME_OFFS_CTRL, SG5_BYTELEN_CTRL);
+-	buf[6] = crc & 0xff;
+-	buf[7] = crc >> 8;
+-
+-	buf[8] = 0xff;
+-	buf[9] = 0xff;
+-
+-	dev_dbg(&ec->serdev->dev, "sending message\n");
+-	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
+-	                     buf, SG5_MSG_LEN_CTRL, false);
+-
+-	status = serdev_device_write(ec->serdev, buf, SG5_MSG_LEN_CTRL, SG5_WRITE_TIMEOUT);
+-	return status >= 0 ? 0 : status;
+-}
+-
+-static void surfacegen5_event_work_ack_handler(struct work_struct *_work)
+-{
+-	struct surfacegen5_event_work *work;
+-	struct surfacegen5_event *event;
+-	struct surfacegen5_ec *ec;
+-	struct device *dev;
+-	int status;
+-
+-	work = container_of(_work, struct surfacegen5_event_work, work_ack);
+-	event = &work->event;
+-	ec = work->ec;
+-	dev = &ec->serdev->dev;
+-
+-	// make sure we load a fresh ec state
+-	smp_mb();
+-
+-	if (ec->state == SG5_EC_INITIALIZED) {
+-		status = surfacegen5_ssh_send_ack(ec, work->seq);
+-		if (status) {
+-			dev_err(dev, SG5_EVENT_TAG "failed to send ACK: %d\n", status);
+-		}
+-	}
+-
+-	if (refcount_dec_and_test(&work->refcount)) {
+-		kfree(work);
+-	}
+-}
+-
+-static void surfacegen5_event_work_evt_handler(struct work_struct *_work)
+-{
+-	struct delayed_work *dwork = (struct delayed_work *)_work;
+-	struct surfacegen5_event_work *work;
+-	struct surfacegen5_event *event;
+-	struct surfacegen5_ec *ec;
+-	struct device *dev;
+-	unsigned long flags;
+-
+-	surfacegen5_ec_event_handler_fn handler;
+-	void *handler_data;
+-
+-	int status = 0;
+-
+-	work = container_of(dwork, struct surfacegen5_event_work, work_evt);
+-	event = &work->event;
+-	ec = work->ec;
+-	dev = &ec->serdev->dev;
+-
+-	spin_lock_irqsave(&ec->events.lock, flags);
+-	handler       = ec->events.handler[event->rqid - 1].handler;
+-	handler_data  = ec->events.handler[event->rqid - 1].data;
+-	spin_unlock_irqrestore(&ec->events.lock, flags);
+-
+-	/*
+-	 * During handler removal or driver release, we ensure every event gets
+-	 * handled before return of that function. Thus a handler obtained here is
+-	 * guaranteed to be valid at least until this function returns.
+-	 */
+-
+-	if (handler) {
+-		status = handler(event, handler_data);
+-	} else {
+-		dev_warn(dev, SG5_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid);
+-	}
+-
+-	if (status) {
+-		dev_err(dev, SG5_EVENT_TAG "error handling event: %d\n", status);
+-	}
+-
+-	if (refcount_dec_and_test(&work->refcount)) {
+-		kfree(work);
+-	}
+-}
+-
+-static void surfacegen5_ssh_handle_event(struct surfacegen5_ec *ec, const u8 *buf)
+-{
+-	struct device *dev = &ec->serdev->dev;
+-	const struct surfacegen5_frame_ctrl *ctrl;
+-	const struct surfacegen5_frame_cmd *cmd;
+-	struct surfacegen5_event_work *work;
+-	unsigned long flags;
+-	u16 pld_len;
+-
+-	surfacegen5_ec_event_handler_delay delay_fn;
+-	void *handler_data;
+-	unsigned long delay = 0;
+-
+-	ctrl = (const struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL);
+-	cmd  = (const struct surfacegen5_frame_cmd  *)(buf + SG5_FRAME_OFFS_CMD);
+-
+-	pld_len = ctrl->len - SG5_BYTELEN_CMDFRAME;
+-
+-	work = kzalloc(sizeof(struct surfacegen5_event_work) + pld_len, GFP_ATOMIC);
+-	if (!work) {
+-		dev_warn(dev, SG5_EVENT_TAG "failed to allocate memory, dropping event\n");
+-		return;
+-	}
+-
+-	refcount_set(&work->refcount, 2);
+-	work->ec         = ec;
+-	work->seq        = ctrl->seq;
+-	work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo;
+-	work->event.tc   = cmd->tc;
+-	work->event.iid  = cmd->iid;
+-	work->event.cid  = cmd->cid;
+-	work->event.len  = pld_len;
+-	work->event.pld  = ((u8*) work) + sizeof(struct surfacegen5_event_work);
+-
+-	memcpy(work->event.pld, buf + SG5_FRAME_OFFS_CMD_PLD, pld_len);
+-
+-	INIT_WORK(&work->work_ack, surfacegen5_event_work_ack_handler);
+-	queue_work(ec->events.queue_ack, &work->work_ack);
+-
+-	spin_lock_irqsave(&ec->events.lock, flags);
+-	handler_data = ec->events.handler[work->event.rqid - 1].data;
+-	delay_fn     = ec->events.handler[work->event.rqid - 1].delay;
+-	if (delay_fn) {
+-		delay = delay_fn(&work->event, handler_data);
+-	}
+-	spin_unlock_irqrestore(&ec->events.lock, flags);
+-
+-	// immediate execution for high priority events (e.g. keyboard)
+-	if (delay == SURFACEGEN5_EVENT_IMMEDIATE) {
+-		surfacegen5_event_work_evt_handler(&work->work_evt.work);
+-	} else {
+-		INIT_DELAYED_WORK(&work->work_evt, surfacegen5_event_work_evt_handler);
+-		queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay);
+-	}
+-}
+-
+-static int surfacegen5_ssh_receive_msg_ctrl(struct surfacegen5_ec *ec,
+-                                            const u8 *buf, size_t size)
+-{
+-	struct device *dev = &ec->serdev->dev;
+-	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
+-	const struct surfacegen5_frame_ctrl *ctrl;
+-	struct surfacegen5_fifo_packet packet;
+-
+-	const u8 *ctrl_begin = buf + SG5_FRAME_OFFS_CTRL;
+-	const u8 *ctrl_end   = buf + SG5_FRAME_OFFS_CTRL_CRC;
+-
+-	ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin);
+-
+-	// actual length check
+-	if (size < SG5_MSG_LEN_CTRL) {
+-		return 0;			// need more bytes
+-	}
+-
+-	// validate TERM
+-	if (!surfacegen5_ssh_is_valid_ter(buf + SG5_FRAME_OFFS_TERM)) {
+-		dev_err(dev, SG5_RECV_TAG "invalid end of message\n");
+-		return size;			// discard everything
+-	}
+-
+-	// validate CRC
+-	if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
+-		dev_err(dev, SG5_RECV_TAG "invalid checksum (ctrl)\n");
+-		return SG5_MSG_LEN_CTRL;	// only discard message
+-	}
+-
+-	// check if we expect the message
+-	if (rcv->state != SG5_RCV_CONTROL) {
+-		dev_err(dev, SG5_RECV_TAG "discarding message: ctrl not expected\n");
+-		return SG5_MSG_LEN_CTRL;	// discard message
+-	}
+-
+-	// check if it is for our request
+-	if (ctrl->type == SG5_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) {
+-		dev_err(dev, SG5_RECV_TAG "discarding message: ack does not match\n");
+-		return SG5_MSG_LEN_CTRL;	// discard message
+-	}
+-
+-	// we now have a valid & expected ACK/RETRY message
+-	dev_dbg(dev, SG5_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type);
+-
+-	packet.type = ctrl->type;
+-	packet.seq  = ctrl->seq;
+-	packet.len  = 0;
+-
+-	if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) {
+-		kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet));
+-
+-	} else {
+-		dev_warn(dev, SG5_RECV_TAG
+-			 "dropping frame: not enough space in fifo (type = %d)\n",
+-			 SG5_FRAME_TYPE_CMD);
+-
+-		return SG5_MSG_LEN_CTRL;	// discard message
+-	}
+-
+-	// update decoder state
+-	if (ctrl->type == SG5_FRAME_TYPE_ACK) {
+-		rcv->state = rcv->expect.pld
+-			? SG5_RCV_COMMAND
+-			: SG5_RCV_DISCARD;
+-	}
+-
+-	complete(&rcv->signal);
+-	return SG5_MSG_LEN_CTRL;		// handled message
+-}
+-
+-static int surfacegen5_ssh_receive_msg_cmd(struct surfacegen5_ec *ec,
+-                                           const u8 *buf, size_t size)
+-{
+-	struct device *dev = &ec->serdev->dev;
+-	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
+-	const struct surfacegen5_frame_ctrl *ctrl;
+-	const struct surfacegen5_frame_cmd *cmd;
+-	struct surfacegen5_fifo_packet packet;
+-
+-	const u8 *ctrl_begin     = buf + SG5_FRAME_OFFS_CTRL;
+-	const u8 *ctrl_end       = buf + SG5_FRAME_OFFS_CTRL_CRC;
+-	const u8 *cmd_begin      = buf + SG5_FRAME_OFFS_CMD;
+-	const u8 *cmd_begin_pld  = buf + SG5_FRAME_OFFS_CMD_PLD;
+-	const u8 *cmd_end;
+-
+-	size_t msg_len;
+-
+-	ctrl = (const struct surfacegen5_frame_ctrl *)(ctrl_begin);
+-	cmd  = (const struct surfacegen5_frame_cmd  *)(cmd_begin);
+-
+-	// we need at least a full control frame
+-	if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL + SG5_BYTELEN_CRC)) {
+-		return 0;		// need more bytes
+-	}
+-
+-	// validate control-frame CRC
+-	if (!surfacegen5_ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
+-		dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-ctrl)\n");
+-		/*
+-		 * We can't be sure here if length is valid, thus
+-		 * discard everything.
+-		 */
+-		return size;
+-	}
+-
+-	// actual length check (ctrl->len contains command-frame but not crc)
+-	msg_len = SG5_MSG_LEN_CMD_BASE + ctrl->len;
+-	if (size < msg_len) {
+-		return 0;			// need more bytes
+-	}
+-
+-	cmd_end = cmd_begin + ctrl->len;
+-
+-	// validate command-frame type
+-	if (cmd->type != SG5_FRAME_TYPE_CMD) {
+-		dev_err(dev, SG5_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type);
+-		return size;			// discard everything
+-	}
+-
+-	// validate command-frame CRC
+-	if (!surfacegen5_ssh_is_valid_crc(cmd_begin, cmd_end)) {
+-		dev_err(dev, SG5_RECV_TAG "invalid checksum (cmd-pld)\n");
+-
+-		/*
+-		 * The message length is provided in the control frame. As we
+-		 * already validated that, we can be sure here that it's
+-		 * correct, so we only need to discard the message.
+-		 */
+-		return msg_len;
+-	}
+-
+-	// check if we received an event notification
+-	if (surfacegen5_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) {
+-		surfacegen5_ssh_handle_event(ec, buf);
+-		return msg_len;			// handled message
+-	}
+-
+-	// check if we expect the message
+-	if (rcv->state != SG5_RCV_COMMAND) {
+-		dev_dbg(dev, SG5_RECV_TAG "discarding message: command not expected\n");
+-		return msg_len;			// discard message
+-	}
+-
+-	// check if response is for our request
+-	if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) {
+-		dev_dbg(dev, SG5_RECV_TAG "discarding message: command not a match\n");
+-		return msg_len;			// discard message
+-	}
+-
+-	// we now have a valid & expected command message
+-	dev_dbg(dev, SG5_RECV_TAG "valid command message received\n");
+-
+-	packet.type = ctrl->type;
+-	packet.seq = ctrl->seq;
+-	packet.len = cmd_end - cmd_begin_pld;
+-
+-	if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) {
+-		kfifo_in(&rcv->fifo, &packet, sizeof(packet));
+-		kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len);
+-
+-	} else {
+-		dev_warn(dev, SG5_RECV_TAG
+-			 "dropping frame: not enough space in fifo (type = %d)\n",
+-			 SG5_FRAME_TYPE_CMD);
+-
+-		return SG5_MSG_LEN_CTRL;	// discard message
+-	}
+-
+-	rcv->state = SG5_RCV_DISCARD;
+-
+-	complete(&rcv->signal);
+-	return msg_len;				// handled message
+-}
+-
+-static int surfacegen5_ssh_eval_buf(struct surfacegen5_ec *ec,
+-                                    const u8 *buf, size_t size)
+-{
+-	struct device *dev = &ec->serdev->dev;
+-	struct surfacegen5_frame_ctrl *ctrl;
+-
+-	// we need at least a control frame to check what to do
+-	if (size < (SG5_BYTELEN_SYNC + SG5_BYTELEN_CTRL)) {
+-		return 0;		// need more bytes
+-	}
+-
+-	// make sure we're actually at the start of a new message
+-	if (!surfacegen5_ssh_is_valid_syn(buf)) {
+-		dev_err(dev, SG5_RECV_TAG "invalid start of message\n");
+-		return size;		// discard everything
+-	}
+-
+-	// handle individual message types seperately
+-	ctrl = (struct surfacegen5_frame_ctrl *)(buf + SG5_FRAME_OFFS_CTRL);
+-
+-	switch (ctrl->type) {
+-	case SG5_FRAME_TYPE_ACK:
+-	case SG5_FRAME_TYPE_RETRY:
+-		return surfacegen5_ssh_receive_msg_ctrl(ec, buf, size);
+-
+-	case SG5_FRAME_TYPE_CMD:
+-		return surfacegen5_ssh_receive_msg_cmd(ec, buf, size);
+-
+-	default:
+-		dev_err(dev, SG5_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type);
+-		return size;		// discard everything
+-	}
+-}
+-
+-static int surfacegen5_ssh_receive_buf(struct serdev_device *serdev,
+-                                       const unsigned char *buf, size_t size)
+-{
+-	struct surfacegen5_ec *ec = serdev_device_get_drvdata(serdev);
+-	struct surfacegen5_ec_receiver *rcv = &ec->receiver;
+-	unsigned long flags;
+-	int offs = 0;
+-	int used, n;
+-
+-	dev_dbg(&serdev->dev, SG5_RECV_TAG "received buffer (size: %zu)\n", size);
+-	print_hex_dump_debug(SG5_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false);
+-
+-	/*
+-	 * The battery _BIX message gets a bit long, thus we have to add some
+-	 * additional buffering here.
+-	 */
+-
+-	spin_lock_irqsave(&rcv->lock, flags);
+-
+-	// copy to eval-buffer
+-	used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len));
+-	memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used);
+-	rcv->eval_buf.len += used;
+-
+-	// evaluate buffer until we need more bytes or eval-buf is empty
+-	while (offs < rcv->eval_buf.len) {
+-		n = rcv->eval_buf.len - offs;
+-		n = surfacegen5_ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n);
+-		if (n <= 0) break;	// need more bytes
+-
+-		offs += n;
+-	}
+-
+-	// throw away the evaluated parts
+-	rcv->eval_buf.len -= offs;
+-	memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len);
+-
+-	spin_unlock_irqrestore(&rcv->lock, flags);
+-
+-	return used;
+-}
+-
+-
+-static acpi_status
+-surfacegen5_ssh_setup_from_resource(struct acpi_resource *resource, void *context)
+-{
+-	struct serdev_device *serdev = context;
+-	struct acpi_resource_common_serialbus *serial;
+-	struct acpi_resource_uart_serialbus *uart;
+-	int status = 0;
+-
+-	if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) {
+-		return AE_OK;
+-	}
+-
+-	serial = &resource->data.common_serial_bus;
+-	if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) {
+-		return AE_OK;
+-	}
+-
+-	uart = &resource->data.uart_serial_bus;
+-
+-	// set up serdev device
+-	serdev_device_set_baudrate(serdev, uart->default_baud_rate);
+-
+-	// serdev currently only supports RTSCTS flow control
+-	if (uart->flow_control & SG5_SUPPORTED_FLOW_CONTROL_MASK) {
+-		dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control);
+-	}
+-
+-	// set RTSCTS flow control
+-	serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW);
+-
+-	// serdev currently only supports EVEN/ODD parity
+-	switch (uart->parity) {
+-	case ACPI_UART_PARITY_NONE:
+-		status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
+-		break;
+-	case ACPI_UART_PARITY_EVEN:
+-		status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);
+-		break;
+-	case ACPI_UART_PARITY_ODD:
+-		status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);
+-		break;
+-	default:
+-		dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity);
+-		break;
+-	}
+-
+-	if (status) {
+-		dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity);
+-		return status;
+-	}
+-
+-	return AE_CTRL_TERMINATE;       // we've found the resource and are done
+-}
+-
+-
+-static bool surfacegen5_idma_filter(struct dma_chan *chan, void *param)
+-{
+-	// see dw8250_idma_filter
+-	return param == chan->device->dev;
+-}
+-
+-static int surfacegen5_ssh_check_dma(struct serdev_device *serdev)
+-{
+-	struct device *dev = serdev->ctrl->dev.parent;
+-	struct dma_chan *rx, *tx;
+-	dma_cap_mask_t mask;
+-	int status = 0;
+-
+-	/*
+-	 * The EC UART requires DMA for proper communication. If we don't use DMA,
+-	 * we'll drop bytes when the system has high load, e.g. during boot. This
+-	 * causes some ugly behaviour, i.e. battery information (_BIX) messages
+-	 * failing frequently. We're making sure the required DMA channels are
+-	 * available here so serial8250_do_startup is able to grab them later
+-	 * instead of silently falling back to a non-DMA approach.
+-	 */
+-
+-	dma_cap_zero(mask);
+-	dma_cap_set(DMA_SLAVE, mask);
+-
+-	rx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "rx");
+-	if (IS_ERR_OR_NULL(rx)) {
+-		status = rx ? PTR_ERR(rx) : -EPROBE_DEFER;
+-		if (status != -EPROBE_DEFER) {
+-			dev_err(&serdev->dev, "sg5_dma: error requesting rx channel: %d\n", status);
+-		} else {
+-			dev_dbg(&serdev->dev, "sg5_dma: rx channel not found, deferring probe\n");
+-		}
+-		goto check_dma_out;
+-	}
+-
+-	tx = dma_request_slave_channel_compat(mask, surfacegen5_idma_filter, dev->parent, dev, "tx");
+-	if (IS_ERR_OR_NULL(tx)) {
+-		status = tx ? PTR_ERR(tx) : -EPROBE_DEFER;
+-		if (status != -EPROBE_DEFER) {
+-			dev_err(&serdev->dev, "sg5_dma: error requesting tx channel: %d\n", status);
+-		} else {
+-			dev_dbg(&serdev->dev, "sg5_dma: tx channel not found, deferring probe\n");
+-		}
+-		goto check_dma_release_rx;
+-	}
+-
+-	dma_release_channel(tx);
+-check_dma_release_rx:
+-	dma_release_channel(rx);
+-check_dma_out:
+-	return status;
+-}
+-
+-
+-static int surfacegen5_ssh_suspend(struct device *dev)
+-{
+-	struct surfacegen5_ec *ec;
+-	int status = 0;
+-
+-	dev_dbg(dev, "suspending\n");
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (ec) {
+-		status = surfacegen5_ssh_ec_suspend(ec);
+-		if (status) {
+-			dev_err(dev, "failed to suspend EC: %d\n", status);
+-		}
+-
+-		ec->state = SG5_EC_SUSPENDED;
+-		surfacegen5_ec_release(ec);
+-	}
+-
+-	return status;
+-}
+-
+-static int surfacegen5_ssh_resume(struct device *dev)
+-{
+-	struct surfacegen5_ec *ec;
+-	int status = 0;
+-
+-	dev_dbg(dev, "resuming\n");
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (ec) {
+-		ec->state = SG5_EC_INITIALIZED;
+-
+-		status = surfacegen5_ssh_ec_resume(ec);
+-		if (status) {
+-			dev_err(dev, "failed to resume EC: %d\n", status);
+-		}
+-
+-		surfacegen5_ec_release(ec);
+-	}
+-
+-	return status;
+-}
+-
+-static SIMPLE_DEV_PM_OPS(surfacegen5_ssh_pm_ops, surfacegen5_ssh_suspend, surfacegen5_ssh_resume);
+-
+-
+-static const struct serdev_device_ops surfacegen5_ssh_device_ops = {
+-	.receive_buf  = surfacegen5_ssh_receive_buf,
+-	.write_wakeup = serdev_device_write_wakeup,
+-};
+-
+-static int surfacegen5_acpi_ssh_probe(struct serdev_device *serdev)
+-{
+-	struct surfacegen5_ec *ec;
+-	struct workqueue_struct *event_queue_ack;
+-	struct workqueue_struct *event_queue_evt;
+-	u8 *write_buf;
+-	u8 *read_buf;
+-	u8 *eval_buf;
+-	acpi_handle *ssh = ACPI_HANDLE(&serdev->dev);
+-	acpi_status status;
+-
+-	dev_dbg(&serdev->dev, "probing\n");
+-
+-	// ensure DMA is ready before we set up the device
+-	status = surfacegen5_ssh_check_dma(serdev);
+-	if (status) {
+-		return status;
+-	}
+-
+-	// allocate buffers
+-	write_buf = kzalloc(SG5_WRITE_BUF_LEN, GFP_KERNEL);
+-	if (!write_buf) {
+-		status = -ENOMEM;
+-		goto err_probe_write_buf;
+-	}
+-
+-	read_buf = kzalloc(SG5_READ_BUF_LEN, GFP_KERNEL);
+-	if (!read_buf) {
+-		status = -ENOMEM;
+-		goto err_probe_read_buf;
+-	}
+-
+-	eval_buf = kzalloc(SG5_EVAL_BUF_LEN, GFP_KERNEL);
+-	if (!eval_buf) {
+-		status = -ENOMEM;
+-		goto err_probe_eval_buf;
+-	}
+-
+-	event_queue_ack = create_singlethread_workqueue("sg5_ackq");
+-	if (!event_queue_ack) {
+-		status = -ENOMEM;
+-		goto err_probe_ackq;
+-	}
+-
+-	event_queue_evt = create_workqueue("sg5_evtq");
+-	if (!event_queue_evt) {
+-		status = -ENOMEM;
+-		goto err_probe_evtq;
+-	}
+-
+-	// set up EC
+-	ec = surfacegen5_ec_acquire();
+-	if (ec->state != SG5_EC_UNINITIALIZED) {
+-		dev_err(&serdev->dev, "embedded controller already initialized\n");
+-		surfacegen5_ec_release(ec);
+-
+-		status = -EBUSY;
+-		goto err_probe_busy;
+-	}
+-
+-	ec->serdev      = serdev;
+-	ec->writer.data = write_buf;
+-	ec->writer.ptr  = write_buf;
+-
+-	// initialize receiver
+-	init_completion(&ec->receiver.signal);
+-	kfifo_init(&ec->receiver.fifo, read_buf, SG5_READ_BUF_LEN);
+-	ec->receiver.eval_buf.ptr = eval_buf;
+-	ec->receiver.eval_buf.cap = SG5_EVAL_BUF_LEN;
+-	ec->receiver.eval_buf.len = 0;
+-
+-	// initialize event handling
+-	ec->events.queue_ack = event_queue_ack;
+-	ec->events.queue_evt = event_queue_evt;
+-
+-	ec->state = SG5_EC_INITIALIZED;
+-
+-	serdev_device_set_drvdata(serdev, ec);
+-
+-	// ensure everything is properly set-up before we open the device
+-	smp_mb();
+-
+-	serdev_device_set_client_ops(serdev, &surfacegen5_ssh_device_ops);
+-	status = serdev_device_open(serdev);
+-	if (status) {
+-		goto err_probe_open;
+-	}
+-
+-	status = acpi_walk_resources(ssh, METHOD_NAME__CRS,
+-	                             surfacegen5_ssh_setup_from_resource, serdev);
+-	if (ACPI_FAILURE(status)) {
+-		goto err_probe_devinit;
+-	}
+-
+-	status = surfacegen5_ssh_ec_resume(ec);
+-	if (status) {
+-		goto err_probe_devinit;
+-	}
+-
+-	status = surfacegen5_ssh_sysfs_register(&serdev->dev);
+-	if (status) {
+-		goto err_probe_devinit;
+-	}
+-
+-	surfacegen5_ec_release(ec);
+-
+-	acpi_walk_dep_device_list(ssh);
+-
+-	return 0;
+-
+-err_probe_devinit:
+-	serdev_device_close(serdev);
+-err_probe_open:
+-	ec->state = SG5_EC_UNINITIALIZED;
+-	serdev_device_set_drvdata(serdev, NULL);
+-	surfacegen5_ec_release(ec);
+-err_probe_busy:
+-	destroy_workqueue(event_queue_evt);
+-err_probe_evtq:
+-	destroy_workqueue(event_queue_ack);
+-err_probe_ackq:
+-	kfree(eval_buf);
+-err_probe_eval_buf:
+-	kfree(read_buf);
+-err_probe_read_buf:
+-	kfree(write_buf);
+-err_probe_write_buf:
+-	return status;
+-}
+-
+-static void surfacegen5_acpi_ssh_remove(struct serdev_device *serdev)
+-{
+-	struct surfacegen5_ec *ec;
+-	unsigned long flags;
+-	//int status;
+-
+-	ec = surfacegen5_ec_acquire_init();
+-	if (!ec) {
+-		return;
+-	}
+-
+-	surfacegen5_ssh_sysfs_unregister(&serdev->dev);
+-
+-	// suspend EC and disable events
+-	//status = surfacegen5_ssh_ec_suspend(ec);
+-	//if (status) {
+-	//	dev_err(&serdev->dev, "failed to suspend EC: %d\n", status);
+-	//}
+-
+-	// make sure all events (received up to now) have been properly handled
+-	flush_workqueue(ec->events.queue_ack);
+-	flush_workqueue(ec->events.queue_evt);
+-
+-	// remove event handlers
+-	spin_lock_irqsave(&ec->events.lock, flags);
+-	memset(ec->events.handler, 0,
+-	       sizeof(struct surfacegen5_ec_event_handler)
+-	        * SG5_NUM_EVENT_TYPES);
+-	spin_unlock_irqrestore(&ec->events.lock, flags);
+-
+-	// set device to deinitialized state
+-	ec->state  = SG5_EC_UNINITIALIZED;
+-	ec->serdev = NULL;
+-
+-	// ensure state and serdev get set before continuing
+-	smp_mb();
+-
+-	/*
+-	 * Flush any event that has not been processed yet to ensure we're not going to
+-	 * use the serial device any more (e.g. for ACKing).
+-	 */
+-	flush_workqueue(ec->events.queue_ack);
+-	flush_workqueue(ec->events.queue_evt);
+-
+-	serdev_device_close(serdev);
+-
+-	/*
+-         * Only at this point, no new events can be received. Destroying the
+-         * workqueue here flushes all remaining events. Those events will be
+-         * silently ignored and neither ACKed nor any handler gets called.
+-	 */
+-	destroy_workqueue(ec->events.queue_ack);
+-	destroy_workqueue(ec->events.queue_evt);
+-
+-	// free writer
+-	kfree(ec->writer.data);
+-	ec->writer.data = NULL;
+-	ec->writer.ptr  = NULL;
+-
+-	// free receiver
+-	spin_lock_irqsave(&ec->receiver.lock, flags);
+-	ec->receiver.state = SG5_RCV_DISCARD;
+-	kfifo_free(&ec->receiver.fifo);
+-
+-	kfree(ec->receiver.eval_buf.ptr);
+-	ec->receiver.eval_buf.ptr = NULL;
+-	ec->receiver.eval_buf.cap = 0;
+-	ec->receiver.eval_buf.len = 0;
+-	spin_unlock_irqrestore(&ec->receiver.lock, flags);
+-
+-	serdev_device_set_drvdata(serdev, NULL);
+-	surfacegen5_ec_release(ec);
+-}
+-
+-
+-static const struct acpi_device_id surfacegen5_acpi_ssh_match[] = {
+-	{ "MSHW0084", 0 },
+-	{ },
+-};
+-MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_ssh_match);
+-
+-struct serdev_device_driver surfacegen5_acpi_ssh = {
+-	.probe = surfacegen5_acpi_ssh_probe,
+-	.remove = surfacegen5_acpi_ssh_remove,
+-	.driver = {
+-		.name = "surfacegen5_acpi_ssh",
+-		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_ssh_match),
+-		.pm = &surfacegen5_ssh_pm_ops,
+-	},
+-};
+-
+-inline int surfacegen5_acpi_ssh_register(void)
+-{
+-	return serdev_device_driver_register(&surfacegen5_acpi_ssh);
+-}
+-
+-inline void surfacegen5_acpi_ssh_unregister(void)
+-{
+-	serdev_device_driver_unregister(&surfacegen5_acpi_ssh);
+-}
+-
+-#else /* CONFIG_SURFACE_ACPI_SSH */
+-
+-inline int surfacegen5_acpi_ssh_register(void)
+-{
+-	return 0;
+-}
+-
+-inline void surfacegen5_acpi_ssh_unregister(void)
+-{
+-}
+-
+-
+-#endif /* CONFIG_SURFACE_ACPI_SSH */
+-
+-
+-/*************************************************************************
+- * Surface Serial Hub Debug Device (private implementation)
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE
+-
+-static char sg5_ssh_debug_rqst_buf_sysfs[SURFACEGEN5_MAX_RQST_RESPONSE + 1] = { 0 };
+-static char sg5_ssh_debug_rqst_buf_pld[SURFACEGEN5_MAX_RQST_PAYLOAD] = { 0 };
+-static char sg5_ssh_debug_rqst_buf_res[SURFACEGEN5_MAX_RQST_RESPONSE] = { 0 };
+-
+-static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
+-                         char *buf, loff_t offs, size_t count)
+-{
+-	if (offs < 0 || count + offs > SURFACEGEN5_MAX_RQST_RESPONSE) {
+-		return -EINVAL;
+-	}
+-
+-	memcpy(buf, sg5_ssh_debug_rqst_buf_sysfs + offs, count);
+-	return count;
+-}
+-
+-static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
+-			  char *buf, loff_t offs, size_t count)
+-{
+-	struct surfacegen5_rqst rqst = {};
+-	struct surfacegen5_buf result = {};
+-	int status;
+-
+-	// check basic write constriants
+-	if (offs != 0 || count > SURFACEGEN5_MAX_RQST_PAYLOAD + 5) {
+-		return -EINVAL;
+-	}
+-
+-	// payload length should be consistent with data provided
+-	if (buf[4] + 5 != count) {
+-		return -EINVAL;
+-	}
+-
+-	rqst.tc  = buf[0];
+-	rqst.iid = buf[1];
+-	rqst.cid = buf[2];
+-	rqst.snc = buf[3];
+-	rqst.cdl = buf[4];
+-	rqst.pld = sg5_ssh_debug_rqst_buf_pld;
+-	memcpy(sg5_ssh_debug_rqst_buf_pld, buf + 5, count - 5);
+-
+-	result.cap = SURFACEGEN5_MAX_RQST_RESPONSE;
+-	result.len = 0;
+-	result.data = sg5_ssh_debug_rqst_buf_res;
+-
+-	status = surfacegen5_ec_rqst(&rqst, &result);
+-	if (status) {
+-		return status;
+-	}
+-
+-	sg5_ssh_debug_rqst_buf_sysfs[0] = result.len;
+-	memcpy(sg5_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len);
+-	memset(sg5_ssh_debug_rqst_buf_sysfs + result.len + 1, 0,
+-	       SURFACEGEN5_MAX_RQST_RESPONSE + 1 - result.len);
+-
+-	return count;
+-}
+-
+-static const BIN_ATTR_RW(rqst, SURFACEGEN5_MAX_RQST_RESPONSE + 1);
+-
+-
+-inline int surfacegen5_ssh_sysfs_register(struct device *dev)
+-{
+-	return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst);
+-}
+-
+-inline void surfacegen5_ssh_sysfs_unregister(struct device *dev)
+-{
+-	sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst);
+-}
+-
+-#elif defined(CONFIG_SURFACE_ACPI_SSH)
+-
+-inline int surfacegen5_ssh_sysfs_register(struct device *dev)
+-{
+-	return 0;
+-}
+-
+-inline void surfacegen5_ssh_sysfs_unregister(struct device *dev)
+-{
+-}
+-
+-#endif /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE*/
+-
+-
+-/*************************************************************************
+- * Surface ACPI Notify driver
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SAN
+-
+-#define SG5_SAN_RQST_RETRY		5
+-
+-#define SG5_SAN_DSM_REVISION		0
+-#define SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT	0x09
+-
+-static const guid_t SG5_SAN_DSM_UUID =
+-	GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d,
+-	          0x48, 0x7c, 0x91, 0xab, 0x3c);
+-
+-#define SG5_EVENT_DELAY_POWER		msecs_to_jiffies(5000)
+-
+-#define SG5_EVENT_PWR_TC		0x02
+-#define SG5_EVENT_PWR_RQID		0x0002
+-#define SG5_EVENT_PWR_CID_HWCHANGE	0x15
+-#define SG5_EVENT_PWR_CID_CHARGING	0x16
+-#define SG5_EVENT_PWR_CID_ADAPTER	0x17
+-#define SG5_EVENT_PWR_CID_STATE		0x4f
+-
+-#define SG5_EVENT_TEMP_TC		0x03
+-#define SG5_EVENT_TEMP_RQID		0x0003
+-#define SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT	0x0b
+-
+-#define SG5_SAN_RQST_TAG            	"surfacegen5_ec_rqst: "
+-
+-#define SG5_QUIRK_BASE_STATE_DELAY	1000
+-
+-
+-struct surfacegen5_san_acpi_consumer {
+-	char *path;
+-	bool  required;
+-	u32   flags;
+-};
+-
+-struct surfacegen5_san_opreg_context {
+-	struct acpi_connection_info connection;
+-	struct device *dev;
+-};
+-
+-struct surfacegen5_san_consumer_link {
+-	const struct surfacegen5_san_acpi_consumer *properties;
+-	struct device_link                         *link;
+-};
+-
+-struct surfacegen5_san_consumers {
+-	u32                                   num;
+-	struct surfacegen5_san_consumer_link *links;
+-};
+-
+-struct surfacegen5_san_drvdata {
+-	struct surfacegen5_san_opreg_context opreg_ctx;
+-	struct surfacegen5_san_consumers     consumers;
+-};
+-
+-struct gsb_data_in {
+-	u8 cv;
+-} __packed;
+-
+-struct gsb_data_rqsx {
+-	u8 cv;				// command value (should be 0x01 or 0x03)
+-	u8 tc;				// target controller
+-	u8 tid;				// expected to be 0x01, could be revision
+-	u8 iid;				// target sub-controller (e.g. primary vs. secondary battery)
+-	u8 snc;				// expect-response-flag
+-	u8 cid;				// command ID
+-	u8 cdl;				// payload length
+-	u8 _pad;			// padding
+-	u8 pld[0];			// payload
+-} __packed;
+-
+-struct gsb_data_etwl {
+-	u8 cv;				// command value (should be 0x02)
+-	u8 etw3;			// ?
+-	u8 etw4;			// ?
+-	u8 msg[0];			// error message (ASCIIZ)
+-} __packed;
+-
+-struct gsb_data_out {
+-	u8 status;			// _SSH communication status
+-	u8 len;				// _SSH payload length
+-	u8 pld[0];			// _SSH payload
+-} __packed;
+-
+-union gsb_buffer_data {
+-	struct gsb_data_in   in;	// common input
+-	struct gsb_data_rqsx rqsx;	// RQSX input
+-	struct gsb_data_etwl etwl;	// ETWL input
+-	struct gsb_data_out  out;	// output
+-};
+-
+-struct gsb_buffer {
+-	u8 status;			// GSB AttribRawProcess status
+-	u8 len;				// GSB AttribRawProcess length
+-	union gsb_buffer_data data;
+-} __packed;
+-
+-
+-enum surfacegen5_pwr_event {
+-	SURFACEGEN5_PWR_EVENT_BAT1_STAT	= 0x03,
+-	SURFACEGEN5_PWR_EVENT_BAT1_INFO	= 0x04,
+-	SURFACEGEN5_PWR_EVENT_ADP1_STAT	= 0x05,
+-	SURFACEGEN5_PWR_EVENT_ADP1_INFO	= 0x06,
+-	SURFACEGEN5_PWR_EVENT_BAT2_STAT	= 0x07,
+-	SURFACEGEN5_PWR_EVENT_BAT2_INFO	= 0x08,
+-};
+-
+-
+-static int surfacegen5_acpi_notify_power_event(struct device *dev, enum surfacegen5_pwr_event event)
+-{
+-	acpi_handle san = ACPI_HANDLE(dev);
+-	union acpi_object *obj;
+-
+-	obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION,
+-	                              (u8) event, NULL, ACPI_TYPE_BUFFER);
+-
+-	if (IS_ERR_OR_NULL(obj)) {
+-		return obj ? PTR_ERR(obj) : -ENXIO;
+-	}
+-
+-	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
+-		dev_err(dev, "got unexpected result from _DSM\n");
+-		return -EFAULT;
+-	}
+-
+-	ACPI_FREE(obj);
+-	return 0;
+-}
+-
+-static int surfacegen5_acpi_notify_sensor_trip_point(struct device *dev, u8 iid)
+-{
+-	acpi_handle san = ACPI_HANDLE(dev);
+-	union acpi_object *obj;
+-	union acpi_object param;
+-
+-	param.type = ACPI_TYPE_INTEGER;
+-	param.integer.value = iid;
+-
+-	obj = acpi_evaluate_dsm_typed(san, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION,
+-	                              SG5_SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT,
+-				      &param, ACPI_TYPE_BUFFER);
+-
+-	if (IS_ERR_OR_NULL(obj)) {
+-		return obj ? PTR_ERR(obj) : -ENXIO;
+-	}
+-
+-	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
+-		dev_err(dev, "got unexpected result from _DSM\n");
+-		return -EFAULT;
+-	}
+-
+-	ACPI_FREE(obj);
+-	return 0;
+-}
+-
+-
+-inline static int surfacegen5_evt_power_adapter(struct device *dev, struct surfacegen5_event *event)
+-{
+-	int status;
+-
+-	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_ADP1_STAT);
+-	if (status) {
+-		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
+-		return status;
+-	}
+-
+-	return 0;
+-}
+-
+-inline static int surfacegen5_evt_power_hwchange(struct device *dev, struct surfacegen5_event *event)
+-{
+-	enum surfacegen5_pwr_event evcode;
+-	int status;
+-
+-	if (event->iid == 0x02) {
+-		evcode = SURFACEGEN5_PWR_EVENT_BAT2_INFO;
+-	} else {
+-		evcode = SURFACEGEN5_PWR_EVENT_BAT1_INFO;
+-	}
+-
+-	status = surfacegen5_acpi_notify_power_event(dev, evcode);
+-	if (status) {
+-		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
+-		return status;
+-	}
+-
+-	return 0;
+-}
+-
+-inline static int surfacegen5_evt_power_state(struct device *dev, struct surfacegen5_event *event)
+-{
+-	int status;
+-
+-	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT1_STAT);
+-	if (status) {
+-		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
+-		return status;
+-	}
+-
+-	status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT2_STAT);
+-	if (status) {
+-		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
+-		return status;
+-	}
+-
+-	return 0;
+-}
+-
+-static unsigned long surfacegen5_evt_power_delay(struct surfacegen5_event *event, void *data)
+-{
+-	switch (event->cid) {
+-	case SG5_EVENT_PWR_CID_CHARGING:
+-	case SG5_EVENT_PWR_CID_STATE:
+-		return SG5_EVENT_DELAY_POWER;
+-
+-	case SG5_EVENT_PWR_CID_ADAPTER:
+-	case SG5_EVENT_PWR_CID_HWCHANGE:
+-	default:
+-		return 0;
+-	}
+-}
+-
+-static int surfacegen5_evt_power(struct surfacegen5_event *event, void *data)
+-{
+-	struct device *dev = (struct device *)data;
+-
+-	switch (event->cid) {
+-	case SG5_EVENT_PWR_CID_HWCHANGE:
+-		return surfacegen5_evt_power_hwchange(dev, event);
+-
+-	case SG5_EVENT_PWR_CID_ADAPTER:
+-		return surfacegen5_evt_power_adapter(dev, event);
+-
+-	case SG5_EVENT_PWR_CID_CHARGING:
+-	case SG5_EVENT_PWR_CID_STATE:
+-		return surfacegen5_evt_power_state(dev, event);
+-
+-	default:
+-		dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid);
+-	}
+-
+-	return 0;
+-}
+-
+-
+-inline static int surfacegen5_evt_thermal_notify(struct device *dev, struct surfacegen5_event *event)
+-{
+-	int status;
+-
+-	status = surfacegen5_acpi_notify_sensor_trip_point(dev, event->iid);
+-	if (status) {
+-		dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid);
+-		return status;
+-	}
+-
+-	return 0;
+-}
+-
+-static int surfacegen5_evt_thermal(struct surfacegen5_event *event, void *data)
+-{
+-	struct device *dev = (struct device *)data;
+-
+-	switch (event->cid) {
+-	case SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT:
+-		return surfacegen5_evt_thermal_notify(dev, event);
+-
+-	default:
+-		dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid);
+-	}
+-
+-	return 0;
+-}
+-
+-
+-static struct gsb_data_rqsx *surfacegen5_san_validate_rqsx(
+-	struct device *dev, const char *type, struct gsb_buffer *buffer)
+-{
+-	struct gsb_data_rqsx *rqsx = &buffer->data.rqsx;
+-
+-	if (buffer->len < 8) {
+-		dev_err(dev, "invalid %s package (len = %d)\n",
+-			type, buffer->len);
+-		return NULL;
+-	}
+-
+-	if (rqsx->cdl != buffer->len - 8) {
+-		dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n",
+-			type, buffer->len, rqsx->cdl);
+-		return NULL;
+-	}
+-
+-	if (rqsx->tid != 0x01) {
+-		dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n",
+-			 type, rqsx->tid);
+-		return NULL;
+-	}
+-
+-	return rqsx;
+-}
+-
+-static acpi_status
+-surfacegen5_san_etwl(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
+-{
+-	struct gsb_data_etwl *etwl = &buffer->data.etwl;
+-
+-	if (buffer->len < 3) {
+-		dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len);
+-		return AE_OK;
+-	}
+-
+-	dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n",
+-		etwl->etw3, etwl->etw4,
+-		buffer->len - 3, (char *)etwl->msg);
+-
+-	// indicate success
+-	buffer->status = 0x00;
+-	buffer->len = 0x00;
+-
+-	return AE_OK;
+-}
+-
+-static acpi_status
+-surfacegen5_san_rqst(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
+-{
+-	struct gsb_data_rqsx *gsb_rqst = surfacegen5_san_validate_rqsx(ctx->dev, "RQST", buffer);
+-	struct surfacegen5_rqst rqst = {};
+-	struct surfacegen5_buf result = {};
+-	int status = 0;
+-	int try;
+-
+-	if (!gsb_rqst) {
+-		return AE_OK;
+-	}
+-
+-	rqst.tc  = gsb_rqst->tc;
+-	rqst.iid = gsb_rqst->iid;
+-	rqst.cid = gsb_rqst->cid;
+-	rqst.snc = gsb_rqst->snc;
+-	rqst.cdl = gsb_rqst->cdl;
+-	rqst.pld = &gsb_rqst->pld[0];
+-
+-	result.cap  = SURFACEGEN5_MAX_RQST_RESPONSE;
+-	result.len  = 0;
+-	result.data = kzalloc(result.cap, GFP_KERNEL);
+-
+-	if (!result.data) {
+-		return AE_NO_MEMORY;
+-	}
+-
+-	for (try = 0; try < SG5_SAN_RQST_RETRY; try++) {
+-		if (try) {
+-			dev_warn(ctx->dev, SG5_SAN_RQST_TAG "IO error occured, trying again\n");
+-		}
+-
+-		status = surfacegen5_ec_rqst(&rqst, &result);
+-		if (status != -EIO) break;
+-	}
+-
+-	if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) {
+-		/* Base state quirk:
+-		 * The base state may be queried from ACPI when the EC is still
+-		 * suspended. In this case it will return '-EPERM'. This query
+-		 * will only be triggered from the ACPI lid GPE interrupt, thus
+-		 * we are either in laptop or studio mode (base status 0x01 or
+-		 * 0x02). Furthermore, we will only get here if the device (and
+-		 * EC) have been suspended.
+-		 *
+-		 * We now assume that the device is in laptop mode (0x01). This
+-		 * has the drawback that it will wake the device when unfolding
+-		 * it in studio mode, but it also allows us to avoid actively
+-		 * waiting for the EC to wake up, which may incur a notable
+-		 * delay.
+-		 */
+-
+-		buffer->status          = 0x00;
+-		buffer->len             = 0x03;
+-		buffer->data.out.status = 0x00;
+-		buffer->data.out.len    = 0x01;
+-		buffer->data.out.pld[0] = 0x01;
+-
+-	} else if (!status) {		// success
+-		buffer->status          = 0x00;
+-		buffer->len             = result.len + 2;
+-		buffer->data.out.status = 0x00;
+-		buffer->data.out.len    = result.len;
+-		memcpy(&buffer->data.out.pld[0], result.data, result.len);
+-
+-	} else {			// failure
+-		dev_err(ctx->dev, SG5_SAN_RQST_TAG "failed with error %d\n", status);
+-		buffer->status          = 0x00;
+-		buffer->len             = 0x02;
+-		buffer->data.out.status = 0x01;		// indicate _SSH error
+-		buffer->data.out.len    = 0x00;
+-	}
+-
+-	kfree(result.data);
+-
+-	return AE_OK;
+-}
+-
+-static acpi_status
+-surfacegen5_san_rqsg(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer)
+-{
+-	struct gsb_data_rqsx *rqsg = surfacegen5_san_validate_rqsx(ctx->dev, "RQSG", buffer);
+-
+-	if (!rqsg) {
+-		return AE_OK;
+-	}
+-
+-	// TODO: RQSG handler
+-
+-	dev_warn(ctx->dev, "unsupported request: RQSG(0x%02x, 0x%02x, 0x%02x)\n",
+-		 rqsg->tc, rqsg->cid, rqsg->iid);
+-
+-	return AE_OK;
+-}
+-
+-
+-static acpi_status
+-surfacegen5_san_opreg_handler(u32 function, acpi_physical_address command,
+-                              u32 bits, u64 *value64,
+-                              void *opreg_context, void *region_context)
+-{
+-	struct surfacegen5_san_opreg_context *context = opreg_context;
+-	struct gsb_buffer *buffer = (struct gsb_buffer *)value64;
+-	int accessor_type = (0xFFFF0000 & function) >> 16;
+-
+-	if (command != 0) {
+-		dev_warn(context->dev, "unsupported command: 0x%02llx\n", command);
+-		return AE_OK;
+-	}
+-
+-	if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) {
+-		dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type);
+-		return AE_OK;
+-	}
+-
+-	// buffer must have at least contain the command-value
+-	if (buffer->len == 0) {
+-		dev_err(context->dev, "request-package too small\n");
+-		return AE_OK;
+-	}
+-
+-	switch (buffer->data.in.cv) {
+-	case 0x01:  return surfacegen5_san_rqst(context, buffer);
+-	case 0x02:  return surfacegen5_san_etwl(context, buffer);
+-	case 0x03:  return surfacegen5_san_rqsg(context, buffer);
+-	}
+-
+-	dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv);
+-	return AE_OK;
+-}
+-
+-static int surfacegen5_san_enable_events(struct device *dev)
+-{
+-	int status;
+-
+-	status = surfacegen5_ec_set_delayed_event_handler(
+-			SG5_EVENT_PWR_RQID, surfacegen5_evt_power,
+-			surfacegen5_evt_power_delay, dev);
+-	if (status) {
+-		goto err_event_handler_power;
+-	}
+-
+-	status = surfacegen5_ec_set_event_handler(
+-			SG5_EVENT_TEMP_RQID, surfacegen5_evt_thermal,
+-			dev);
+-	if (status) {
+-		goto err_event_handler_thermal;
+-	}
+-
+-	status = surfacegen5_ec_enable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
+-	if (status) {
+-		goto err_event_source_power;
+-	}
+-
+-	status = surfacegen5_ec_enable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID);
+-	if (status) {
+-		goto err_event_source_thermal;
+-	}
+-
+-	return 0;
+-
+-err_event_source_thermal:
+-	surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
+-err_event_source_power:
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID);
+-err_event_handler_thermal:
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID);
+-err_event_handler_power:
+-	return status;
+-}
+-
+-static void surfacegen5_san_disable_events(void)
+-{
+-	surfacegen5_ec_disable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID);
+-	surfacegen5_ec_disable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID);
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_TEMP_RQID);
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_PWR_RQID);
+-}
+-
+-
+-static int surfacegen5_san_consumers_link(struct platform_device *pdev,
+-                                          const struct surfacegen5_san_acpi_consumer *cons,
+-                                          struct surfacegen5_san_consumers *out)
+-{
+-	const struct surfacegen5_san_acpi_consumer *con;
+-	struct surfacegen5_san_consumer_link *links, *link;
+-	struct acpi_device *adev;
+-	acpi_handle handle;
+-	u32 max_links = 0;
+-	int status;
+-
+-	if (!cons) {
+-		return 0;
+-	}
+-
+-	// count links
+-	for (con = cons; con->path; ++con) {
+-		max_links += 1;
+-	}
+-
+-	// allocate
+-	links = kzalloc(max_links * sizeof(struct surfacegen5_san_consumer_link), GFP_KERNEL);
+-	link = &links[0];
+-
+-	if (!links) {
+-		return -ENOMEM;
+-	}
+-
+-	// create links
+-	for (con = cons; con->path; ++con) {
+-		status = acpi_get_handle(NULL, con->path, &handle);
+-		if (status) {
+-			if (con->required || status != AE_NOT_FOUND) {
+-				status = -ENXIO;
+-				goto consumers_link_cleanup;
+-			} else {
+-				continue;
+-			}
+-		}
+-
+-		status = acpi_bus_get_device(handle, &adev);
+-		if (status) {
+-			goto consumers_link_cleanup;
+-		}
+-
+-		link->link = device_link_add(&adev->dev, &pdev->dev, con->flags);
+-		if (!(link->link)) {
+-			status = -EFAULT;
+-			goto consumers_link_cleanup;
+-		}
+-		link->properties = con;
+-
+-		link += 1;
+-	}
+-
+-	out->num = link - links;
+-	out->links = links;
+-
+-	return 0;
+-
+-consumers_link_cleanup:
+-	for (link = link - 1; link >= links; --link) {
+-		if (link->properties->flags & DL_FLAG_STATELESS) {
+-			device_link_del(link->link);
+-		}
+-	}
+-
+-	return status;
+-}
+-
+-static void surfacegen5_san_consumers_unlink(struct surfacegen5_san_consumers *consumers) {
+-	u32 i;
+-
+-	if (!consumers) {
+-		return;
+-	}
+-
+-	for (i = 0; i < consumers->num; ++i) {
+-		if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) {
+-			device_link_del(consumers->links[i].link);
+-		}
+-	}
+-
+-	kfree(consumers->links);
+-
+-	consumers->num = 0;
+-	consumers->links = NULL;
+-}
+-
+-static int surfacegen5_acpi_san_probe(struct platform_device *pdev)
+-{
+-	const struct surfacegen5_san_acpi_consumer *cons;
+-	struct surfacegen5_san_drvdata *drvdata;
+-	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
+-	int status;
+-
+-	/*
+-	 * Defer probe if the _SSH driver has not set up the controller yet. This
+-	 * makes sure we do not fail any initial requests (e.g. _STA request without
+-	 * which the battery does not get set up correctly). Otherwise register as
+-	 * consumer to set up a device_link.
+-	 */
+-	status = surfacegen5_ec_consumer_register(&pdev->dev);
+-	if (status) {
+-		return status == -ENXIO ? -EPROBE_DEFER : status;
+-	}
+-
+-	drvdata = kzalloc(sizeof(struct surfacegen5_san_drvdata), GFP_KERNEL);
+-	if (!drvdata) {
+-		return -ENOMEM;
+-	}
+-
+-	drvdata->opreg_ctx.dev = &pdev->dev;
+-
+-	cons = acpi_device_get_match_data(&pdev->dev);
+-	status = surfacegen5_san_consumers_link(pdev, cons, &drvdata->consumers);
+-	if (status) {
+-		goto err_probe_consumers;
+-	}
+-
+-	platform_set_drvdata(pdev, drvdata);
+-
+-	status = acpi_install_address_space_handler(san,
+-			ACPI_ADR_SPACE_GSBUS,
+-			&surfacegen5_san_opreg_handler,
+-			NULL, &drvdata->opreg_ctx);
+-
+-	if (ACPI_FAILURE(status)) {
+-		status = -ENODEV;
+-		goto err_probe_install_handler;
+-	}
+-
+-	status = surfacegen5_san_enable_events(&pdev->dev);
+-	if (status) {
+-		goto err_probe_enable_events;
+-	}
+-
+-	acpi_walk_dep_device_list(san);
+-	return 0;
+-
+-err_probe_enable_events:
+-	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler);
+-err_probe_install_handler:
+-	platform_set_drvdata(san, NULL);
+-	surfacegen5_san_consumers_unlink(&drvdata->consumers);
+-err_probe_consumers:
+-	kfree(drvdata);
+-	return status;
+-}
+-
+-static int surfacegen5_acpi_san_remove(struct platform_device *pdev)
+-{
+-	struct surfacegen5_san_drvdata *drvdata = platform_get_drvdata(pdev);
+-	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
+-	acpi_status status = AE_OK;
+-
+-	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &surfacegen5_san_opreg_handler);
+-	surfacegen5_san_disable_events();
+-
+-	surfacegen5_san_consumers_unlink(&drvdata->consumers);
+-	kfree(drvdata);
+-
+-	platform_set_drvdata(pdev, NULL);
+-	return status;
+-}
+-
+-
+-static const struct surfacegen5_san_acpi_consumer surfacegen5_mshw0091_consumers[] = {
+-	{ "\\_SB.SRTC", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
+-	{ "\\ADP1",     true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
+-	{ "\\_SB.BAT1", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
+-	{ "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
+-	{ },
+-};
+-
+-static const struct acpi_device_id surfacegen5_acpi_san_match[] = {
+-	{ "MSHW0091", (long unsigned int) surfacegen5_mshw0091_consumers },
+-	{ },
+-};
+-MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_san_match);
+-
+-struct platform_driver surfacegen5_acpi_san = {
+-	.probe = surfacegen5_acpi_san_probe,
+-	.remove = surfacegen5_acpi_san_remove,
+-	.driver = {
+-		.name = "surfacegen5_acpi_san",
+-		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_san_match),
+-	},
+-};
+-
+-
+-inline int surfacegen5_acpi_san_register(void)
+-{
+-	return platform_driver_register(&surfacegen5_acpi_san);
+-}
+-
+-inline void surfacegen5_acpi_san_unregister(void)
+-{
+-	platform_driver_unregister(&surfacegen5_acpi_san);
+-}
+-
+-#else /* CONFIG_SURFACE_ACPI_SAN */
+-
+-inline int surfacegen5_acpi_san_register(void)
+-{
+-	return 0;
+-}
+-
+-inline void surfacegen5_acpi_san_unregister(void)
+-{
+-}
+-
+-#endif /* CONFIG_SURFACE_ACPI_SAN */
+-
+-
+-/*************************************************************************
+- * Virtual HID Framework driver
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_VHF
+-
+-#define SG5_VHF_INPUT_NAME	"Microsoft Virtual HID Framework Device"
+-
+-/*
+- * Request ID for VHF events. This value is based on the output of the Surface
+- * EC and should not be changed.
+- */
+-#define SG5_EVENT_VHF_RQID	0x0001
+-#define SG5_EVENT_VHF_TC	0x08
+-
+-
+-struct surfacegen5_vhf_evtctx {
+-	struct device     *dev;
+-	struct hid_device *hid;
+-};
+-
+-struct surfacegen5_vhf_drvdata {
+-	struct surfacegen5_vhf_evtctx event_ctx;
+-};
+-
+-
+-/*
+- * These report descriptors have been extracted from a Surface Book 2.
+- * They seems to be similar enough to be usable on the Surface Laptop.
+- */
+-static const u8 vhf_hid_desc[] = {
+-	// keyboard descriptor (event command ID 0x03)
+-	0x05, 0x01,             /*  Usage Page (Desktop),                   */
+-	0x09, 0x06,             /*  Usage (Keyboard),                       */
+-	0xA1, 0x01,             /*  Collection (Application),               */
+-	0x85, 0x01,             /*      Report ID (1),                      */
+-	0x15, 0x00,             /*      Logical Minimum (0),                */
+-	0x25, 0x01,             /*      Logical Maximum (1),                */
+-	0x75, 0x01,             /*      Report Size (1),                    */
+-	0x95, 0x08,             /*      Report Count (8),                   */
+-	0x05, 0x07,             /*      Usage Page (Keyboard),              */
+-	0x19, 0xE0,             /*      Usage Minimum (KB Leftcontrol),     */
+-	0x29, 0xE7,             /*      Usage Maximum (KB Right GUI),       */
+-	0x81, 0x02,             /*      Input (Variable),                   */
+-	0x75, 0x08,             /*      Report Size (8),                    */
+-	0x95, 0x0A,             /*      Report Count (10),                  */
+-	0x19, 0x00,             /*      Usage Minimum (None),               */
+-	0x29, 0x91,             /*      Usage Maximum (KB LANG2),           */
+-	0x26, 0xFF, 0x00,       /*      Logical Maximum (255),              */
+-	0x81, 0x00,             /*      Input,                              */
+-	0x05, 0x0C,             /*      Usage Page (Consumer),              */
+-	0x0A, 0xC0, 0x02,       /*      Usage (02C0h),                      */
+-	0xA1, 0x02,             /*      Collection (Logical),               */
+-	0x1A, 0xC1, 0x02,       /*          Usage Minimum (02C1h),          */
+-	0x2A, 0xC6, 0x02,       /*          Usage Maximum (02C6h),          */
+-	0x95, 0x06,             /*          Report Count (6),               */
+-	0xB1, 0x03,             /*          Feature (Constant, Variable),   */
+-	0xC0,                   /*      End Collection,                     */
+-	0x05, 0x08,             /*      Usage Page (LED),                   */
+-	0x19, 0x01,             /*      Usage Minimum (01h),                */
+-	0x29, 0x03,             /*      Usage Maximum (03h),                */
+-	0x75, 0x01,             /*      Report Size (1),                    */
+-	0x95, 0x03,             /*      Report Count (3),                   */
+-	0x25, 0x01,             /*      Logical Maximum (1),                */
+-	0x91, 0x02,             /*      Output (Variable),                  */
+-	0x95, 0x05,             /*      Report Count (5),                   */
+-	0x91, 0x01,             /*      Output (Constant),                  */
+-	0xC0,                   /*  End Collection,                         */
+-
+-	// media key descriptor (event command ID 0x04)
+-	0x05, 0x0C,             /*  Usage Page (Consumer),                  */
+-	0x09, 0x01,             /*  Usage (Consumer Control),               */
+-	0xA1, 0x01,             /*  Collection (Application),               */
+-	0x85, 0x03,             /*      Report ID (3),                      */
+-	0x75, 0x10,             /*      Report Size (16),                   */
+-	0x15, 0x00,             /*      Logical Minimum (0),                */
+-	0x26, 0xFF, 0x03,       /*      Logical Maximum (1023),             */
+-	0x19, 0x00,             /*      Usage Minimum (00h),                */
+-	0x2A, 0xFF, 0x03,       /*      Usage Maximum (03FFh),              */
+-	0x81, 0x00,             /*      Input,                              */
+-	0xC0,                   /*  End Collection,                         */
+-};
+-
+-
+-static int vhf_hid_start(struct hid_device *hid)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-	return 0;
+-}
+-
+-static void vhf_hid_stop(struct hid_device *hid)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-}
+-
+-static int vhf_hid_open(struct hid_device *hid)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-	return 0;
+-}
+-
+-static void vhf_hid_close(struct hid_device *hid)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-}
+-
+-static int vhf_hid_parse(struct hid_device *hid)
+-{
+-	return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc));
+-}
+-
+-static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
+-			       u8 *buf, size_t len, unsigned char rtype,
+-			       int reqtype)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-	return 0;
+-}
+-
+-static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len)
+-{
+-	hid_dbg(hid, "%s\n", __func__);
+-	print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false);
+-
+-	return len;
+-}
+-
+-static struct hid_ll_driver vhf_hid_ll_driver = {
+-	.start         = vhf_hid_start,
+-	.stop          = vhf_hid_stop,
+-	.open          = vhf_hid_open,
+-	.close         = vhf_hid_close,
+-	.parse         = vhf_hid_parse,
+-	.raw_request   = vhf_hid_raw_request,
+-	.output_report = vhf_hid_output_report,
+-};
+-
+-
+-static struct hid_device *surfacegen5_vhf_create_hid_device(struct platform_device *pdev)
+-{
+-	struct hid_device *hid;
+-
+-	hid = hid_allocate_device();
+-	if (IS_ERR(hid)) {
+-		return hid;
+-	}
+-
+-	hid->dev.parent = &pdev->dev;
+-
+-	hid->bus     = BUS_VIRTUAL;
+-	hid->vendor  = USB_VENDOR_ID_MICROSOFT;
+-	hid->product = USB_DEVICE_ID_MS_VHF;
+-
+-	hid->ll_driver = &vhf_hid_ll_driver;
+-
+-	sprintf(hid->name, "%s", SG5_VHF_INPUT_NAME);
+-
+-	return hid;
+-}
+-
+-static int surfacegen5_vhf_event_handler(struct surfacegen5_event *event, void *data)
+-{
+-	struct surfacegen5_vhf_evtctx *ctx = (struct surfacegen5_vhf_evtctx *)data;
+-
+-	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
+-		return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1);
+-	}
+-
+-	dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid);
+-	return 0;
+-}
+-
+-static unsigned long surfacegen5_vhf_event_delay(struct surfacegen5_event *event, void *data)
+-{
+-	// high priority immediate execution for keyboard events
+-	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
+-		return SURFACEGEN5_EVENT_IMMEDIATE;
+-	}
+-
+-	return 0;
+-}
+-
+-static int surfacegen5_acpi_vhf_probe(struct platform_device *pdev)
+-{
+-	struct surfacegen5_vhf_drvdata *drvdata;
+-	struct hid_device *hid;
+-	int status;
+-
+-	// add device link to EC
+-	status = surfacegen5_ec_consumer_register(&pdev->dev);
+-	if (status) {
+-		return status == -ENXIO ? -EPROBE_DEFER : status;
+-	}
+-
+-	drvdata = kzalloc(sizeof(struct surfacegen5_vhf_drvdata), GFP_KERNEL);
+-	if (!drvdata) {
+-		return -ENOMEM;
+-	}
+-
+-	hid = surfacegen5_vhf_create_hid_device(pdev);
+-	if (IS_ERR(hid)) {
+-		status = PTR_ERR(hid);
+-		goto err_probe_hid;
+-	}
+-
+-	status = hid_add_device(hid);
+-	if (status) {
+-		goto err_add_hid;
+-	}
+-
+-	drvdata->event_ctx.dev = &pdev->dev;
+-	drvdata->event_ctx.hid = hid;
+-
+-	platform_set_drvdata(pdev, drvdata);
+-
+-	/*
+-         * Set event hanlder for VHF events. They seem to be enabled by
+-         * default, thus there should be no need to explicitly enable them.
+-	 */
+-	status = surfacegen5_ec_set_delayed_event_handler(
+-			SG5_EVENT_VHF_RQID,
+-	                surfacegen5_vhf_event_handler,
+-	                surfacegen5_vhf_event_delay,
+-			&drvdata->event_ctx);
+-	if (status) {
+-		goto err_add_hid;
+-	}
+-
+-	status = surfacegen5_ec_enable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID);
+-	if (status) {
+-		goto err_event_source;
+-	}
+-
+-	return 0;
+-
+-err_event_source:
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID);
+-err_add_hid:
+-	hid_destroy_device(hid);
+-	platform_set_drvdata(pdev, NULL);
+-err_probe_hid:
+-	kfree(drvdata);
+-	return status;
+-}
+-
+-static int surfacegen5_acpi_vhf_remove(struct platform_device *pdev)
+-{
+-	struct surfacegen5_vhf_drvdata *drvdata = platform_get_drvdata(pdev);
+-
+-	surfacegen5_ec_disable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID);
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID);
+-
+-	hid_destroy_device(drvdata->event_ctx.hid);
+-	kfree(drvdata);
+-
+-	platform_set_drvdata(pdev, NULL);
+-	return 0;
+-}
+-
+-
+-static const struct acpi_device_id surfacegen5_acpi_vhf_match[] = {
+-	{ "MSHW0096" },
+-	{ },
+-};
+-MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_vhf_match);
+-
+-struct platform_driver surfacegen5_acpi_vhf = {
+-	.probe = surfacegen5_acpi_vhf_probe,
+-	.remove = surfacegen5_acpi_vhf_remove,
+-	.driver = {
+-		.name = "surfacegen5_acpi_vhf",
+-		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_vhf_match),
+-	},
+-};
+-
+-
+-inline int surfacegen5_acpi_vhf_register(void)
+-{
+-	return platform_driver_register(&surfacegen5_acpi_vhf);
+-}
+-
+-inline void surfacegen5_acpi_vhf_unregister(void)
+-{
+-	platform_driver_unregister(&surfacegen5_acpi_vhf);
+-}
+-
+-#else /* CONFIG_SURFACE_ACPI_VHF */
+-
+-inline int surfacegen5_acpi_vhf_register(void)
+-{
+-	return 0;
+-}
+-
+-inline void surfacegen5_acpi_vhf_unregister(void)
+-{
+-}
+-
+-#endif /* CONFIG_SURFACE_ACPI_VHF */
+-
+-
+-/*************************************************************************
+- * Detachment System Driver (DTX)
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_DTX
+-
+-#define SG5_DTX_INPUT_NAME	"Microsoft Surface Base 2 Integration Device"
+-
+-#define DTX_CMD_LATCH_LOCK				_IO(0x11, 0x01)
+-#define DTX_CMD_LATCH_UNLOCK				_IO(0x11, 0x02)
+-#define DTX_CMD_LATCH_REQUEST				_IO(0x11, 0x03)
+-#define DTX_CMD_LATCH_OPEN				_IO(0x11, 0x04)
+-#define DTX_CMD_GET_OPMODE				_IOR(0x11, 0x05, int)
+-
+-#define SG5_RQST_DTX_TC					0x11
+-#define SG5_RQST_DTX_CID_LATCH_LOCK			0x06
+-#define SG5_RQST_DTX_CID_LATCH_UNLOCK			0x07
+-#define SG5_RQST_DTX_CID_LATCH_REQUEST			0x08
+-#define SG5_RQST_DTX_CID_LATCH_OPEN			0x09
+-#define SG5_RQST_DTX_CID_GET_OPMODE			0x0D
+-
+-#define SG5_EVENT_DTX_TC				0x11
+-#define SG5_EVENT_DTX_RQID				0x0011
+-#define SG5_EVENT_DTX_CID_CONNECTION			0x0c
+-#define SG5_EVENT_DTX_CID_BUTTON			0x0e
+-#define SG5_EVENT_DTX_CID_ERROR				0x0f
+-#define SG5_EVENT_DTX_CID_LATCH_STATUS			0x11
+-
+-#define DTX_OPMODE_TABLET				0x00
+-#define DTX_OPMODE_LAPTOP				0x01
+-#define DTX_OPMODE_STUDIO				0x02
+-
+-#define DTX_LATCH_CLOSED				0x00
+-#define DTX_LATCH_OPENED				0x01
+-
+-// Warning: This must always be a power of 2!
+-#define SURFACE_DTX_CLIENT_BUF_SIZE             	16
+-
+-#define SG5_DTX_CONNECT_OPMODE_DELAY			1000
+-
+-#define DTX_ERR		KERN_ERR "surfacegen5_acpi_dtx: "
+-#define DTX_WARN	KERN_WARNING "surfacegen5_acpi_dtx: "
+-
+-
+-struct surface_dtx_event {
+-	u8 type;
+-	u8 code;
+-	u8 arg0;
+-	u8 arg1;
+-} __packed;
+-
+-struct surface_dtx_dev {
+-	wait_queue_head_t waitq;
+-	struct miscdevice mdev;
+-	spinlock_t client_lock;
+-	struct list_head client_list;
+-	struct mutex mutex;
+-	bool active;
+-	spinlock_t input_lock;
+-	struct input_dev *input_dev;
+-};
+-
+-struct surface_dtx_client {
+-	struct list_head node;
+-	struct surface_dtx_dev *ddev;
+-	struct fasync_struct *fasync;
+-	spinlock_t buffer_lock;
+-	unsigned int buffer_head;
+-	unsigned int buffer_tail;
+-	struct surface_dtx_event buffer[SURFACE_DTX_CLIENT_BUF_SIZE];
+-};
+-
+-
+-static struct surface_dtx_dev surface_dtx_dev;
+-
+-
+-static int sg5_ec_query_opmpde(void)
+-{
+-	u8 result_buf[1];
+-	int status;
+-
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = SG5_RQST_DTX_TC,
+-		.iid = 0,
+-		.cid = SG5_RQST_DTX_CID_GET_OPMODE,
+-		.snc = 1,
+-		.cdl = 0,
+-		.pld = NULL,
+-	};
+-
+-	struct surfacegen5_buf result = {
+-		.cap = 1,
+-		.len = 0,
+-		.data = result_buf,
+-	};
+-
+-	status = surfacegen5_ec_rqst(&rqst, &result);
+-	if (status) {
+-		return status;
+-	}
+-
+-	if (result.len != 1) {
+-		return -EFAULT;
+-	}
+-
+-	return result.data[0];
+-}
+-
+-
+-static int dtx_cmd_simple(u8 cid)
+-{
+-	struct surfacegen5_rqst rqst = {
+-		.tc  = SG5_RQST_DTX_TC,
+-		.iid = 0,
+-		.cid = cid,
+-		.snc = 0,
+-		.cdl = 0,
+-		.pld = NULL,
+-	};
+-
+-	return surfacegen5_ec_rqst(&rqst, NULL);
+-}
+-
+-static int dtx_cmd_get_opmode(int __user *buf)
+-{
+-	int opmode = sg5_ec_query_opmpde();
+-	if (opmode < 0) {
+-		return opmode;
+-	}
+-
+-	if (put_user(opmode, buf)) {
+-		return -EACCES;
+-	}
+-
+-	return 0;
+-}
+-
+-
+-static int surface_dtx_open(struct inode *inode, struct file *file)
+-{
+-	struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev);
+-	struct surface_dtx_client *client;
+-
+-	// initialize client
+-	client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL);
+-	if (!client) {
+-		return -ENOMEM;
+-	}
+-
+-	spin_lock_init(&client->buffer_lock);
+-	client->buffer_head = 0;
+-	client->buffer_tail = 0;
+-	client->ddev = ddev;
+-
+-	// attach client
+-	spin_lock(&ddev->client_lock);
+-	list_add_tail_rcu(&client->node, &ddev->client_list);
+-	spin_unlock(&ddev->client_lock);
+-
+-	file->private_data = client;
+-	nonseekable_open(inode, file);
+-
+-	return 0;
+-}
+-
+-static int surface_dtx_release(struct inode *inode, struct file *file)
+-{
+-	struct surface_dtx_client *client = file->private_data;
+-
+-	// detach client
+-	spin_lock(&client->ddev->client_lock);
+-	list_del_rcu(&client->node);
+-	spin_unlock(&client->ddev->client_lock);
+-	synchronize_rcu();
+-
+-	kfree(client);
+-	file->private_data = NULL;
+-
+-	return 0;
+-}
+-
+-static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
+-{
+-	struct surface_dtx_client *client = file->private_data;
+-	struct surface_dtx_dev *ddev = client->ddev;
+-	struct surface_dtx_event event;
+-	size_t read = 0;
+-	int status = 0;
+-
+-	if (count != 0 && count < sizeof(struct surface_dtx_event)) {
+-		return -EINVAL;
+-	}
+-
+-	if (!ddev->active) {
+-		return -ENODEV;
+-	}
+-
+-	// check availability
+-	if (client->buffer_head == client->buffer_tail){
+-		if (file->f_flags & O_NONBLOCK) {
+-			return -EAGAIN;
+-		}
+-
+-		status = wait_event_interruptible(ddev->waitq,
+-				client->buffer_head != client->buffer_tail ||
+-				!ddev->active);
+-		if (status) {
+-			return status;
+-		}
+-
+-		if (!ddev->active) {
+-			return -ENODEV;
+-		}
+-	}
+-
+-	// copy events one by one
+-	while (read + sizeof(struct surface_dtx_event) <= count) {
+-		spin_lock_irq(&client->buffer_lock);
+-
+-		if(client->buffer_head == client->buffer_tail) {
+-			spin_unlock_irq(&client->buffer_lock);
+-			break;
+-		}
+-
+-		// get one event
+-		event = client->buffer[client->buffer_tail];
+-		client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1);
+-		spin_unlock_irq(&client->buffer_lock);
+-
+-		// copy to userspace
+-		if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) {
+-			return -EFAULT;
+-		}
+-
+-		read += sizeof(struct surface_dtx_event);
+-	}
+-
+-	return read;
+-}
+-
+-static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
+-{
+-	struct surface_dtx_client *client = file->private_data;
+-	int mask;
+-
+-	poll_wait(file, &client->ddev->waitq, pt);
+-
+-	if (client->ddev->active) {
+-		mask = EPOLLOUT | EPOLLWRNORM;
+-	} else {
+-		mask = EPOLLHUP | EPOLLERR;
+-	}
+-
+-	if (client->buffer_head != client->buffer_tail) {
+-		mask |= EPOLLIN | EPOLLRDNORM;
+-	}
+-
+-	return mask;
+-}
+-
+-static int surface_dtx_fasync(int fd, struct file *file, int on)
+-{
+-	struct surface_dtx_client *client = file->private_data;
+-
+-	return fasync_helper(fd, file, on, &client->fasync);
+-}
+-
+-static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+-{
+-	struct surface_dtx_client *client = file->private_data;
+-	struct surface_dtx_dev *ddev = client->ddev;
+-	int status;
+-
+-	status = mutex_lock_interruptible(&ddev->mutex);
+-	if (status) {
+-		return status;
+-	}
+-
+-	if (!ddev->active) {
+-		mutex_unlock(&ddev->mutex);
+-		return -ENODEV;
+-	}
+-
+-	switch (cmd) {
+-	case DTX_CMD_LATCH_LOCK:
+-		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_LOCK);
+-		break;
+-
+-	case DTX_CMD_LATCH_UNLOCK:
+-		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_UNLOCK);
+-		break;
+-
+-	case DTX_CMD_LATCH_REQUEST:
+-		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_REQUEST);
+-		break;
+-
+-	case DTX_CMD_LATCH_OPEN:
+-		status = dtx_cmd_simple(SG5_RQST_DTX_CID_LATCH_OPEN);
+-		break;
+-
+-	case DTX_CMD_GET_OPMODE:
+-		status = dtx_cmd_get_opmode((int __user *)arg);
+-		break;
+-
+-	default:
+-		status = -EINVAL;
+-		break;
+-	}
+-
+-	mutex_unlock(&ddev->mutex);
+-	return status;
+-}
+-
+-static const struct file_operations surface_dtx_fops = {
+-	.owner          = THIS_MODULE,
+-	.open           = surface_dtx_open,
+-	.release        = surface_dtx_release,
+-	.read           = surface_dtx_read,
+-	.poll           = surface_dtx_poll,
+-	.fasync         = surface_dtx_fasync,
+-	.unlocked_ioctl = surface_dtx_ioctl,
+-	.llseek         = no_llseek,
+-};
+-
+-static struct surface_dtx_dev surface_dtx_dev = {
+-	.mdev = {
+-		.minor = MISC_DYNAMIC_MINOR,
+-		.name = "surface_dtx",
+-		.fops = &surface_dtx_fops,
+-	},
+-	.client_lock = __SPIN_LOCK_UNLOCKED(),
+-	.input_lock = __SPIN_LOCK_UNLOCKED(),
+-	.mutex  = __MUTEX_INITIALIZER(surface_dtx_dev.mutex),
+-	.active = false,
+-};
+-
+-
+-static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event)
+-{
+-	struct surface_dtx_client *client;
+-
+-	rcu_read_lock();
+-	list_for_each_entry_rcu(client, &ddev->client_list, node) {
+-		spin_lock(&client->buffer_lock);
+-
+-		client->buffer[client->buffer_head++] = *event;
+-		client->buffer_head &= SURFACE_DTX_CLIENT_BUF_SIZE - 1;
+-
+-		if (unlikely(client->buffer_head == client->buffer_tail)) {
+-			printk(DTX_WARN "event buffer overrun\n");
+-			client->buffer_tail = (client->buffer_tail + 1) & (SURFACE_DTX_CLIENT_BUF_SIZE - 1);
+-		}
+-
+-		spin_unlock(&client->buffer_lock);
+-
+-		kill_fasync(&client->fasync, SIGIO, POLL_IN);
+-	}
+-	rcu_read_unlock();
+-
+-	wake_up_interruptible(&ddev->waitq);
+-}
+-
+-
+-static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev)
+-{
+-	struct surface_dtx_event event;
+-	int opmode;
+-
+-	// get operation mode
+-	opmode = sg5_ec_query_opmpde();
+-	if (opmode < 0) {
+-		printk(DTX_ERR "EC request failed with error %d\n", opmode);
+-	}
+-
+-	// send DTX event
+-	event.type = 0x11;
+-	event.code = 0x0D;
+-	event.arg0 = opmode;
+-	event.arg1 = 0x00;
+-
+-	surface_dtx_push_event(ddev, &event);
+-
+-	// send SW_TABLET_MODE event
+-	spin_lock(&ddev->input_lock);
+-	input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00);
+-	input_sync(ddev->input_dev);
+-	spin_unlock(&ddev->input_lock);
+-}
+-
+-static int surface_dtx_evt_dtx(struct surfacegen5_event *in_event, void *data)
+-{
+-	struct surface_dtx_dev *ddev = data;
+-	struct surface_dtx_event event;
+-
+-	switch (in_event->cid) {
+-	case SG5_EVENT_DTX_CID_CONNECTION:
+-	case SG5_EVENT_DTX_CID_BUTTON:
+-	case SG5_EVENT_DTX_CID_ERROR:
+-	case SG5_EVENT_DTX_CID_LATCH_STATUS:
+-		if (in_event->len > 2) {
+-			printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n",
+-			       in_event->cid, in_event->len);
+-			return 0;
+-		}
+-
+-		event.type = in_event->tc;
+-		event.code = in_event->cid;
+-		event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00;
+-		event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00;
+-		surface_dtx_push_event(ddev, &event);
+-		break;
+-
+-	default:
+-		printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid);
+-	}
+-
+-	// update device mode
+-	if (in_event->cid == SG5_EVENT_DTX_CID_CONNECTION) {
+-		if (in_event->pld[0]) {
+-			// Note: we're already in a workqueue task
+-			msleep(SG5_DTX_CONNECT_OPMODE_DELAY);
+-		}
+-
+-		surface_dtx_update_opmpde(ddev);
+-	}
+-
+-	return 0;
+-}
+-
+-static int surface_dtx_events_setup(struct surface_dtx_dev *ddev)
+-{
+-	int status;
+-
+-	status = surfacegen5_ec_set_event_handler(SG5_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev);
+-	if (status) {
+-		goto err_event_handler;
+-	}
+-
+-	status = surfacegen5_ec_enable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID);
+-	if (status) {
+-		goto err_event_source;
+-	}
+-
+-	return 0;
+-
+-err_event_source:
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID);
+-err_event_handler:
+-	return status;
+-}
+-
+-static void surface_dtx_events_disable(void)
+-{
+-	surfacegen5_ec_disable_event_source(SG5_EVENT_DTX_TC, 0x01, SG5_EVENT_DTX_RQID);
+-	surfacegen5_ec_remove_event_handler(SG5_EVENT_DTX_RQID);
+-}
+-
+-
+-static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev)
+-{
+-	struct input_dev *input_dev;
+-	int status;
+-
+-	input_dev = input_allocate_device();
+-	if (!input_dev) {
+-		return ERR_PTR(-ENOMEM);
+-	}
+-
+-	input_dev->name = SG5_DTX_INPUT_NAME;
+-	input_dev->dev.parent = &pdev->dev;
+-	input_dev->id.bustype = BUS_VIRTUAL;
+-	input_dev->id.vendor  = USB_VENDOR_ID_MICROSOFT;
+-	input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION;
+-
+-	input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
+-
+-	status = sg5_ec_query_opmpde();
+-	if (status < 0) {
+-		input_free_device(input_dev);
+-		return ERR_PTR(status);
+-	}
+-
+-	input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00);
+-
+-	status = input_register_device(input_dev);
+-	if (status) {
+-		input_unregister_device(input_dev);
+-		return ERR_PTR(status);
+-	}
+-
+-	return input_dev;
+-}
+-
+-
+-static int surfacegen5_acpi_dtx_probe(struct platform_device *pdev)
+-{
+-	struct surface_dtx_dev *ddev = &surface_dtx_dev;
+-	struct input_dev *input_dev;
+-	int status;
+-
+-	// link to ec
+-	status = surfacegen5_ec_consumer_register(&pdev->dev);
+-	if (status) {
+-		return status == -ENXIO ? -EPROBE_DEFER : status;
+-	}
+-
+-	input_dev = surface_dtx_register_inputdev(pdev);
+-	if (IS_ERR(input_dev)) {
+-		return PTR_ERR(input_dev);
+-	}
+-
+-	// initialize device
+-	mutex_lock(&ddev->mutex);
+-	if (ddev->active) {
+-		mutex_unlock(&ddev->mutex);
+-		status = -ENODEV;
+-		goto err_register;
+-	}
+-
+-	INIT_LIST_HEAD(&ddev->client_list);
+-	init_waitqueue_head(&ddev->waitq);
+-	ddev->active = true;
+-	ddev->input_dev = input_dev;
+-	mutex_unlock(&ddev->mutex);
+-
+-	status = misc_register(&ddev->mdev);
+-	if (status) {
+-		goto err_register;
+-	}
+-
+-	// enable events
+-	status = surface_dtx_events_setup(ddev);
+-	if (status) {
+-		goto err_events_setup;
+-	}
+-
+-	return 0;
+-
+-err_events_setup:
+-	misc_deregister(&ddev->mdev);
+-err_register:
+-	input_unregister_device(ddev->input_dev);
+-	return status;
+-}
+-
+-static int surfacegen5_acpi_dtx_remove(struct platform_device *pdev)
+-{
+-	struct surface_dtx_dev *ddev = &surface_dtx_dev;
+-	struct surface_dtx_client *client;
+-
+-	mutex_lock(&ddev->mutex);
+-	if (!ddev->active) {
+-		mutex_unlock(&ddev->mutex);
+-		return 0;
+-	}
+-
+-	// mark as inactive
+-	ddev->active = false;
+-	mutex_unlock(&ddev->mutex);
+-
+-	// After this call we're guaranteed that no more input events will arive
+-	surface_dtx_events_disable();
+-
+-	// wake up clients
+-	spin_lock(&ddev->client_lock);
+-	list_for_each_entry(client, &ddev->client_list, node) {
+-		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+-	}
+-	spin_unlock(&ddev->client_lock);
+-
+-	wake_up_interruptible(&ddev->waitq);
+-
+-	// unregister user-space devices
+-	input_unregister_device(ddev->input_dev);
+-	misc_deregister(&ddev->mdev);
+-
+-	return 0;
+-}
+-
+-
+-static const struct acpi_device_id surfacegen5_acpi_dtx_match[] = {
+-	{ "MSHW0133", 0 },
+-	{ },
+-};
+-MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_dtx_match);
+-
+-struct platform_driver surfacegen5_acpi_dtx = {
+-	.probe = surfacegen5_acpi_dtx_probe,
+-	.remove = surfacegen5_acpi_dtx_remove,
+-	.driver = {
+-		.name = "surfacegen5_acpi_dtx",
+-		.acpi_match_table = ACPI_PTR(surfacegen5_acpi_dtx_match),
+-	},
+-};
+-
+-
+-inline int surfacegen5_acpi_dtx_register(void)
+-{
+-	return platform_driver_register(&surfacegen5_acpi_dtx);
+-}
+-
+-inline void surfacegen5_acpi_dtx_unregister(void)
+-{
+-	platform_driver_unregister(&surfacegen5_acpi_dtx);
+-}
+-
+-#else /* CONFIG_SURFACE_ACPI_DTX */
+-
+-inline int surfacegen5_acpi_dtx_register(void)
+-{
+-	return 0;
+-}
+-
+-inline void surfacegen5_acpi_dtx_unregister(void)
+-{
+-}
+-
+-#endif /* CONFIG_SURFACE_ACPI_DTX */
+-
+-
+-/*************************************************************************
+- * Surface Platform Integration Driver
+- */
+-
+-#ifdef CONFIG_SURFACE_ACPI_SID
+-
+-struct si_lid_device {
+-	const char *acpi_path;
+-	const u32 gpe_number;
+-};
+-
+-struct si_device_info {
+-	const bool has_perf_mode;
+-	const struct si_lid_device *lid_device;
+-};
+-
+-
+-static const struct si_lid_device lid_device_l17 = {
+-	.acpi_path = "\\_SB.LID0",
+-	.gpe_number = 0x17,
+-};
+-
+-static const struct si_lid_device lid_device_l57 = {
+-	.acpi_path = "\\_SB.LID0",
+-	.gpe_number = 0x57,
+-};
+-
+-static const struct si_lid_device lid_device_l4F = {
+-	.acpi_path = "\\_SB.LID0",
+-	.gpe_number = 0x57,
+-};
+-
+-
+-static const struct si_device_info si_device_pro_4 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l17,
+-};
+-
+-static const struct si_device_info si_device_pro_5 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l4F,
+-};
+-
+-static const struct si_device_info si_device_pro_6 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l4F,
+-};
+-
+-static const struct si_device_info si_device_book_1 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l17,
+-};
+-
+-static const struct si_device_info si_device_book_2 = {
+-	.has_perf_mode = true,
+-	.lid_device = &lid_device_l17,
+-};
+-
+-static const struct si_device_info si_device_laptop_1 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l57,
+-};
+-
+-static const struct si_device_info si_device_laptop_2 = {
+-	.has_perf_mode = false,
+-	.lid_device = &lid_device_l57,
+-};
+-
+-
+-static const struct dmi_system_id dmi_lid_device_table[] = {
+-	{
+-		.ident = "Surface Pro 4",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
+-		},
+-		.driver_data = (void *)&si_device_pro_4,
+-	},
+-	{
+-		.ident = "Surface Pro 5",
+-		.matches = {
+-			/* match for SKU here due to generic product name "Surface Pro" */
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
+-		},
+-		.driver_data = (void *)&si_device_pro_5,
+-	},
+-	{
+-		.ident = "Surface Pro 5 (LTE)",
+-		.matches = {
+-			/* match for SKU here due to generic product name "Surface Pro" */
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
+-		},
+-		.driver_data = (void *)&si_device_pro_5,
+-	},
+-	{
+-		.ident = "Surface Pro 6",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
+-		},
+-		.driver_data = (void *)&si_device_pro_6,
+-	},
+-	{
+-		.ident = "Surface Book 1",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
+-		},
+-		.driver_data = (void *)&si_device_book_1,
+-	},
+-	{
+-		.ident = "Surface Book 2",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
+-		},
+-		.driver_data = (void *)&si_device_book_2,
+-	},
+-	{
+-		.ident = "Surface Laptop 1",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
+-		},
+-		.driver_data = (void *)&si_device_laptop_1,
+-	},
+-	{
+-		.ident = "Surface Laptop 2",
+-		.matches = {
+-			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+-			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
+-		},
+-		.driver_data = (void *)&si_device_laptop_2,
+-	},
+-	{ }
+-};
+-
+-#define SG5_PARAM_PERM		(S_IRUGO | S_IWUSR)
+-
+-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,
+-};
+-
+-
+-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 sid_perf_mode_setup(struct platform_device *pdev, const struct si_device_info *info)
+-{
+-	int status;
+-
+-	if (!info->has_perf_mode)
+-		return 0;
+-
+-	// link to ec
+-	status = surfacegen5_ec_consumer_register(&pdev->dev);
+-	if (status) {
+-		return status == -ENXIO ? -EPROBE_DEFER : status;
+-	}
+-
+-	// 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) {
+-			return status;
+-		}
+-	}
+-
+-	// 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);
+-	return status;
+-}
+-
+-static void sid_perf_mode_remove(struct platform_device *pdev, const struct si_device_info *info)
+-{
+-	if (!info->has_perf_mode)
+-		return;
+-
+-	// 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);
+-}
+-
+-
+-static int sid_lid_enable_wakeup(const struct si_device_info *info, bool enable)
+-{
+-	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
+-	int status;
+-
+-	if (!info->lid_device)
+-		return 0;
+-
+-	status = acpi_set_gpe_wake_mask(NULL, info->lid_device->gpe_number, action);
+-	if (status)
+-		return -EFAULT;
+-
+-	return 0;
+-}
+-
+-static int sid_lid_device_setup(const struct si_device_info *info)
+-{
+-	acpi_handle lid_handle;
+-	int status;
+-
+-	if (!info->lid_device)
+-		return 0;
+-
+-	status = acpi_get_handle(NULL, (acpi_string)info->lid_device->acpi_path, &lid_handle);
+-	if (status)
+-		return -EFAULT;
+-
+-	status = acpi_setup_gpe_for_wake(lid_handle, NULL, info->lid_device->gpe_number);
+-	if (status)
+-		return -EFAULT;
+-
+-	status = acpi_enable_gpe(NULL, info->lid_device->gpe_number);
+-	if (status)
+-		return -EFAULT;
+-
+-	return sid_lid_enable_wakeup(info, false);
+-}
+-
+-static void sid_lid_device_remove(const struct si_device_info *info)
+-{
+-	/* restore default behavior without this module */
+-	sid_lid_enable_wakeup(info, false);
+-}
+-
+-
+-static int surfacegen5_acpi_sid_suspend(struct device *dev)
+-{
+-	const struct si_device_info *info = dev_get_drvdata(dev);
+-	return sid_lid_enable_wakeup(info, true);
+-}
+-
+-static int surfacegen5_acpi_sid_resume(struct device *dev)
+-{
+-	const struct si_device_info *info = dev_get_drvdata(dev);
+-	return sid_lid_enable_wakeup(info, false);
+-}
+-
+-static SIMPLE_DEV_PM_OPS(surfacegen5_acpi_sid_pm, surfacegen5_acpi_sid_suspend, surfacegen5_acpi_sid_resume);
+-
+-
+-static int surfacegen5_acpi_sid_probe(struct platform_device *pdev)
+-{
+-	const struct dmi_system_id *dmi_match;
+-	struct si_device_info *info;
+-	int status;
+-
+-	dmi_match = dmi_first_match(dmi_lid_device_table);
+-	if (!dmi_match)
+-		return -ENODEV;
+-
+-	info = dmi_match->driver_data;
+-
+-	platform_set_drvdata(pdev, info);
+-
+-	status = sid_perf_mode_setup(pdev, info);
+-	if (status)
+-		goto err_perf_mode;
+-
+-	status = sid_lid_device_setup(info);
+-	if (status)
+-		goto err_lid;
+-
+-	return 0;
+-
+-err_lid:
+-	sid_perf_mode_remove(pdev, info);
+-err_perf_mode:
+-	return status;
+-}
+-
+-static int surfacegen5_acpi_sid_remove(struct platform_device *pdev)
+-{
+-	const struct si_device_info *info = platform_get_drvdata(pdev);
+-
+-	sid_perf_mode_remove(pdev, info);
+-	sid_lid_device_remove(info);
+-
+-	platform_set_drvdata(pdev, NULL);
+-	return 0;
+-}
+-
+-static const struct acpi_device_id surfacegen5_acpi_sid_match[] = {
+-	{ "MSHW0081", },	/* Surface Pro 4, 5, and 6 */
+-	{ "MSHW0080", },	/* Surface Book 1 */
+-	{ "MSHW0107", },	/* Surface Book 2 */
+-	{ "MSHW0086", },	/* Surface Laptop 1 */
+-	{ "MSHW0112", },	/* Surface Laptop 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),
+-		.pm = &surfacegen5_acpi_sid_pm,
+-	},
+-};
+-
+-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
+- */
+-
+-int __init surface_acpi_init(void)
+-{
+-	int status;
+-
+-	status = surfacegen5_acpi_ssh_register();
+-	if (status) {
+-		goto err_ssh;
+-	}
+-
+-	status = surfacegen5_acpi_san_register();
+-	if (status) {
+-		goto err_san;
+-	}
+-
+-	status = surfacegen5_acpi_vhf_register();
+-	if (status) {
+-		goto err_vhf;
+-	}
+-
+-	status = surfacegen5_acpi_dtx_register();
+-	if (status) {
+-		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:
+-	surfacegen5_acpi_san_unregister();
+-err_san:
+-	surfacegen5_acpi_ssh_unregister();
+-err_ssh:
+-	return status;
+-}
+-
+-void __exit surface_acpi_exit(void)
+-{
+-	surfacegen5_acpi_sid_unregister();
+-	surfacegen5_acpi_dtx_unregister();
+-	surfacegen5_acpi_vhf_unregister();
+-	surfacegen5_acpi_san_unregister();
+-	surfacegen5_acpi_ssh_unregister();
+-}
+-
+-module_init(surface_acpi_init)
+-module_exit(surface_acpi_exit)
+-
+-MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+-MODULE_DESCRIPTION("ACPI/Platform Drivers for Microsoft Surface Devices");
+-MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/Kconfig b/drivers/platform/x86/surface_sam/Kconfig
+new file mode 100644
+index 000000000000..3ef7d69a214f
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/Kconfig
+@@ -0,0 +1,104 @@
++menuconfig SURFACE_SAM
++    depends on ACPI
++    tristate "Microsoft Surface/System Aggregator Module and Platform Drivers"
++    ---help---
++      Drivers for the Surface/System Aggregator Module (SAM) of Microsoft
++      Surface devices.
++
++      SAM is an embedded controller that provides access to various
++      functionalities on these devices, including battery status, keyboard
++      events (on the Laptops) and many more.
++
++      Say Y here if you have a Microsoft Surface device with a SAM device
++      (i.e. 5th generation or later).
++
++config SURFACE_SAM_SSH
++	tristate "Surface Serial Hub Driver"
++	depends on SURFACE_SAM
++	depends on X86_INTEL_LPSS
++	depends on SERIAL_8250_DW
++	depends on SERIAL_8250_DMA
++	depends on SERIAL_DEV_CTRL_TTYPORT
++	select CRC_CCITT
++	default m
++	---help---
++	  Surface Serial Hub driver for 5th generation (or later) Microsoft
++	  Surface devices.
++
++	  This is the base driver for the embedded serial controller found on
++	  5th generation (and later) Microsoft Surface devices (e.g. Book 2,
++	  Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only
++	  provides access to the embedded controller (SAM) and subsequent
++	  drivers are required for the respective functionalities.
++
++	  If you have a 5th generation (or later) Microsoft Surface device, say
++	  Y or M here.
++
++config SURFACE_SAM_SSH_DEBUG_DEVICE
++	bool "Surface Serial Hub Debug Device"
++	depends on SURFACE_SAM_SSH
++	default n
++	---help---
++	  Debug device for direct communication with the embedded controller
++	  found on 5th generation (and later) Microsoft Surface devices (e.g.
++	  Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via sysfs.
++
++	  If you are not sure, say N here.
++
++config SURFACE_SAM_SAN
++	tristate "Surface ACPI Notify Driver"
++	depends on SURFACE_SAM_SSH
++	default m
++	---help---
++	  Surface ACPI Notify driver for 5th generation (or later) Microsoft
++	  Surface devices.
++
++	  This driver enables basic ACPI events and requests, such as battery
++	  status requests/events, thermal events, lid status, and possibly more,
++	  which would otherwise not work on these devices.
++
++	  If you are not sure, say Y here.
++
++config SURFACE_SAM_VHF
++	tristate "Surface Virtual HID Framework Driver"
++	depends on SURFACE_SAM_SSH
++	depends on HID
++	default m
++	---help---
++	  Surface Virtual HID Framework driver for 5th generation (or later)
++	  Microsoft Surface devices.
++
++	  This driver provides support for the Microsoft Virtual HID framework,
++	  which is required for the Surface Laptop (1 and newer) keyboard.
++
++	  If you are not sure, say Y here.
++
++config SURFACE_SAM_DTX
++	tristate "Surface Detachment System (DTX) Driver"
++	depends on SURFACE_SAM_SSH
++	depends on INPUT
++	default m
++	---help---
++	  Surface Detachment System (DTX) driver for the Microsoft Surface Book
++	  2. This driver provides support for proper detachment handling in
++	  user-space, status-events relating to the base and support for
++	  the safe-guard keeping the base attached when the discrete GPU
++	  contained in it is running via the special /dev/surface-dtx device.
++
++	  Also provides a standard input device to provide SW_TABLET_MODE events
++	  upon device mode change.
++
++	  If you are not sure, say Y here.
++
++config SURFACE_SAM_SID
++	tristate "Surface Platform Integration Driver"
++	depends on SURFACE_SAM_SSH
++	default m
++	---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.
+diff --git a/drivers/platform/x86/surface_sam/Makefile b/drivers/platform/x86/surface_sam/Makefile
+new file mode 100644
+index 000000000000..5431174ea993
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/Makefile
+@@ -0,0 +1,5 @@
++obj-$(CONFIG_SURFACE_SAM_SSH)	+= surface_sam_ssh.o
++obj-$(CONFIG_SURFACE_SAM_SAN)	+= surface_sam_san.o
++obj-$(CONFIG_SURFACE_SAM_SID)	+= surface_sam_sid.o
++obj-$(CONFIG_SURFACE_SAM_DTX)	+= surface_sam_dtx.o
++obj-$(CONFIG_SURFACE_SAM_VHF)	+= surface_sam_vhf.o
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_dtx.c b/drivers/platform/x86/surface_sam/surface_sam_dtx.c
+new file mode 100644
+index 000000000000..9f2c873f1452
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_dtx.c
+@@ -0,0 +1,620 @@
++/*
++ * Detachment system (DTX) driver for Microsoft Surface Book 2.
++ */
++
++#include <linux/acpi.h>
++#include <linux/delay.h>
++#include <linux/fs.h>
++#include <linux/input.h>
++#include <linux/ioctl.h>
++#include <linux/kernel.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/poll.h>
++#include <linux/rculist.h>
++#include <linux/slab.h>
++#include <linux/spinlock.h>
++#include <linux/platform_device.h>
++
++#include "surface_sam_ssh.h"
++
++
++#define USB_VENDOR_ID_MICROSOFT				0x045e
++#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION	0x0922
++
++// name copied from MS device manager
++#define DTX_INPUT_NAME	"Microsoft Surface Base 2 Integration Device"
++
++
++#define DTX_CMD_LATCH_LOCK				_IO(0x11, 0x01)
++#define DTX_CMD_LATCH_UNLOCK				_IO(0x11, 0x02)
++#define DTX_CMD_LATCH_REQUEST				_IO(0x11, 0x03)
++#define DTX_CMD_LATCH_OPEN				_IO(0x11, 0x04)
++#define DTX_CMD_GET_OPMODE				_IOR(0x11, 0x05, int)
++
++#define SAM_RQST_DTX_TC					0x11
++#define SAM_RQST_DTX_CID_LATCH_LOCK			0x06
++#define SAM_RQST_DTX_CID_LATCH_UNLOCK			0x07
++#define SAM_RQST_DTX_CID_LATCH_REQUEST			0x08
++#define SAM_RQST_DTX_CID_LATCH_OPEN			0x09
++#define SAM_RQST_DTX_CID_GET_OPMODE			0x0D
++
++#define SAM_EVENT_DTX_TC				0x11
++#define SAM_EVENT_DTX_RQID				0x0011
++#define SAM_EVENT_DTX_CID_CONNECTION			0x0c
++#define SAM_EVENT_DTX_CID_BUTTON			0x0e
++#define SAM_EVENT_DTX_CID_ERROR				0x0f
++#define SAM_EVENT_DTX_CID_LATCH_STATUS			0x11
++
++#define DTX_OPMODE_TABLET				0x00
++#define DTX_OPMODE_LAPTOP				0x01
++#define DTX_OPMODE_STUDIO				0x02
++
++#define DTX_LATCH_CLOSED				0x00
++#define DTX_LATCH_OPENED				0x01
++
++
++// Warning: This must always be a power of 2!
++#define DTX_CLIENT_BUF_SIZE				16
++
++#define DTX_CONNECT_OPMODE_DELAY			1000
++
++#define DTX_ERR		KERN_ERR "surface_sam_dtx: "
++#define DTX_WARN	KERN_WARNING "surface_sam_dtx: "
++
++
++struct surface_dtx_event {
++	u8 type;
++	u8 code;
++	u8 arg0;
++	u8 arg1;
++} __packed;
++
++struct surface_dtx_dev {
++	wait_queue_head_t waitq;
++	struct miscdevice mdev;
++	spinlock_t client_lock;
++	struct list_head client_list;
++	struct mutex mutex;
++	bool active;
++	spinlock_t input_lock;
++	struct input_dev *input_dev;
++};
++
++struct surface_dtx_client {
++	struct list_head node;
++	struct surface_dtx_dev *ddev;
++	struct fasync_struct *fasync;
++	spinlock_t buffer_lock;
++	unsigned int buffer_head;
++	unsigned int buffer_tail;
++	struct surface_dtx_event buffer[DTX_CLIENT_BUF_SIZE];
++};
++
++
++static struct surface_dtx_dev surface_dtx_dev;
++
++
++static int surface_sam_query_opmpde(void)
++{
++	u8 result_buf[1];
++	int status;
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = SAM_RQST_DTX_TC,
++		.iid = 0,
++		.cid = SAM_RQST_DTX_CID_GET_OPMODE,
++		.snc = 1,
++		.cdl = 0,
++		.pld = NULL,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		.cap = 1,
++		.len = 0,
++		.data = result_buf,
++	};
++
++	status = surface_sam_ssh_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (result.len != 1) {
++		return -EFAULT;
++	}
++
++	return result.data[0];
++}
++
++
++static int dtx_cmd_simple(u8 cid)
++{
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = SAM_RQST_DTX_TC,
++		.iid = 0,
++		.cid = cid,
++		.snc = 0,
++		.cdl = 0,
++		.pld = NULL,
++	};
++
++	return surface_sam_ssh_rqst(&rqst, NULL);
++}
++
++static int dtx_cmd_get_opmode(int __user *buf)
++{
++	int opmode = surface_sam_query_opmpde();
++	if (opmode < 0) {
++		return opmode;
++	}
++
++	if (put_user(opmode, buf)) {
++		return -EACCES;
++	}
++
++	return 0;
++}
++
++
++static int surface_dtx_open(struct inode *inode, struct file *file)
++{
++	struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev);
++	struct surface_dtx_client *client;
++
++	// initialize client
++	client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL);
++	if (!client) {
++		return -ENOMEM;
++	}
++
++	spin_lock_init(&client->buffer_lock);
++	client->buffer_head = 0;
++	client->buffer_tail = 0;
++	client->ddev = ddev;
++
++	// attach client
++	spin_lock(&ddev->client_lock);
++	list_add_tail_rcu(&client->node, &ddev->client_list);
++	spin_unlock(&ddev->client_lock);
++
++	file->private_data = client;
++	nonseekable_open(inode, file);
++
++	return 0;
++}
++
++static int surface_dtx_release(struct inode *inode, struct file *file)
++{
++	struct surface_dtx_client *client = file->private_data;
++
++	// detach client
++	spin_lock(&client->ddev->client_lock);
++	list_del_rcu(&client->node);
++	spin_unlock(&client->ddev->client_lock);
++	synchronize_rcu();
++
++	kfree(client);
++	file->private_data = NULL;
++
++	return 0;
++}
++
++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
++{
++	struct surface_dtx_client *client = file->private_data;
++	struct surface_dtx_dev *ddev = client->ddev;
++	struct surface_dtx_event event;
++	size_t read = 0;
++	int status = 0;
++
++	if (count != 0 && count < sizeof(struct surface_dtx_event)) {
++		return -EINVAL;
++	}
++
++	if (!ddev->active) {
++		return -ENODEV;
++	}
++
++	// check availability
++	if (client->buffer_head == client->buffer_tail){
++		if (file->f_flags & O_NONBLOCK) {
++			return -EAGAIN;
++		}
++
++		status = wait_event_interruptible(ddev->waitq,
++				client->buffer_head != client->buffer_tail ||
++				!ddev->active);
++		if (status) {
++			return status;
++		}
++
++		if (!ddev->active) {
++			return -ENODEV;
++		}
++	}
++
++	// copy events one by one
++	while (read + sizeof(struct surface_dtx_event) <= count) {
++		spin_lock_irq(&client->buffer_lock);
++
++		if(client->buffer_head == client->buffer_tail) {
++			spin_unlock_irq(&client->buffer_lock);
++			break;
++		}
++
++		// get one event
++		event = client->buffer[client->buffer_tail];
++		client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1);
++		spin_unlock_irq(&client->buffer_lock);
++
++		// copy to userspace
++		if(copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) {
++			return -EFAULT;
++		}
++
++		read += sizeof(struct surface_dtx_event);
++	}
++
++	return read;
++}
++
++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
++{
++	struct surface_dtx_client *client = file->private_data;
++	int mask;
++
++	poll_wait(file, &client->ddev->waitq, pt);
++
++	if (client->ddev->active) {
++		mask = EPOLLOUT | EPOLLWRNORM;
++	} else {
++		mask = EPOLLHUP | EPOLLERR;
++	}
++
++	if (client->buffer_head != client->buffer_tail) {
++		mask |= EPOLLIN | EPOLLRDNORM;
++	}
++
++	return mask;
++}
++
++static int surface_dtx_fasync(int fd, struct file *file, int on)
++{
++	struct surface_dtx_client *client = file->private_data;
++
++	return fasync_helper(fd, file, on, &client->fasync);
++}
++
++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++{
++	struct surface_dtx_client *client = file->private_data;
++	struct surface_dtx_dev *ddev = client->ddev;
++	int status;
++
++	status = mutex_lock_interruptible(&ddev->mutex);
++	if (status) {
++		return status;
++	}
++
++	if (!ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		return -ENODEV;
++	}
++
++	switch (cmd) {
++	case DTX_CMD_LATCH_LOCK:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_LOCK);
++		break;
++
++	case DTX_CMD_LATCH_UNLOCK:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_UNLOCK);
++		break;
++
++	case DTX_CMD_LATCH_REQUEST:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_REQUEST);
++		break;
++
++	case DTX_CMD_LATCH_OPEN:
++		status = dtx_cmd_simple(SAM_RQST_DTX_CID_LATCH_OPEN);
++		break;
++
++	case DTX_CMD_GET_OPMODE:
++		status = dtx_cmd_get_opmode((int __user *)arg);
++		break;
++
++	default:
++		status = -EINVAL;
++		break;
++	}
++
++	mutex_unlock(&ddev->mutex);
++	return status;
++}
++
++static const struct file_operations surface_dtx_fops = {
++	.owner          = THIS_MODULE,
++	.open           = surface_dtx_open,
++	.release        = surface_dtx_release,
++	.read           = surface_dtx_read,
++	.poll           = surface_dtx_poll,
++	.fasync         = surface_dtx_fasync,
++	.unlocked_ioctl = surface_dtx_ioctl,
++	.llseek         = no_llseek,
++};
++
++static struct surface_dtx_dev surface_dtx_dev = {
++	.mdev = {
++		.minor = MISC_DYNAMIC_MINOR,
++		.name = "surface_dtx",
++		.fops = &surface_dtx_fops,
++	},
++	.client_lock = __SPIN_LOCK_UNLOCKED(),
++	.input_lock = __SPIN_LOCK_UNLOCKED(),
++	.mutex  = __MUTEX_INITIALIZER(surface_dtx_dev.mutex),
++	.active = false,
++};
++
++
++static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event)
++{
++	struct surface_dtx_client *client;
++
++	rcu_read_lock();
++	list_for_each_entry_rcu(client, &ddev->client_list, node) {
++		spin_lock(&client->buffer_lock);
++
++		client->buffer[client->buffer_head++] = *event;
++		client->buffer_head &= DTX_CLIENT_BUF_SIZE - 1;
++
++		if (unlikely(client->buffer_head == client->buffer_tail)) {
++			printk(DTX_WARN "event buffer overrun\n");
++			client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1);
++		}
++
++		spin_unlock(&client->buffer_lock);
++
++		kill_fasync(&client->fasync, SIGIO, POLL_IN);
++	}
++	rcu_read_unlock();
++
++	wake_up_interruptible(&ddev->waitq);
++}
++
++
++static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev)
++{
++	struct surface_dtx_event event;
++	int opmode;
++
++	// get operation mode
++	opmode = surface_sam_query_opmpde();
++	if (opmode < 0) {
++		printk(DTX_ERR "EC request failed with error %d\n", opmode);
++	}
++
++	// send DTX event
++	event.type = 0x11;
++	event.code = 0x0D;
++	event.arg0 = opmode;
++	event.arg1 = 0x00;
++
++	surface_dtx_push_event(ddev, &event);
++
++	// send SW_TABLET_MODE event
++	spin_lock(&ddev->input_lock);
++	input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode == 0x00);
++	input_sync(ddev->input_dev);
++	spin_unlock(&ddev->input_lock);
++}
++
++static int surface_dtx_evt_dtx(struct surface_sam_ssh_event *in_event, void *data)
++{
++	struct surface_dtx_dev *ddev = data;
++	struct surface_dtx_event event;
++
++	switch (in_event->cid) {
++	case SAM_EVENT_DTX_CID_CONNECTION:
++	case SAM_EVENT_DTX_CID_BUTTON:
++	case SAM_EVENT_DTX_CID_ERROR:
++	case SAM_EVENT_DTX_CID_LATCH_STATUS:
++		if (in_event->len > 2) {
++			printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n",
++			       in_event->cid, in_event->len);
++			return 0;
++		}
++
++		event.type = in_event->tc;
++		event.code = in_event->cid;
++		event.arg0 = in_event->len >= 1 ? in_event->pld[0] : 0x00;
++		event.arg1 = in_event->len >= 2 ? in_event->pld[1] : 0x00;
++		surface_dtx_push_event(ddev, &event);
++		break;
++
++	default:
++		printk(DTX_WARN "unhandled dtx event (cid: %x)\n", in_event->cid);
++	}
++
++	// update device mode
++	if (in_event->cid == SAM_EVENT_DTX_CID_CONNECTION) {
++		if (in_event->pld[0]) {
++			// Note: we're already in a workqueue task
++			msleep(DTX_CONNECT_OPMODE_DELAY);
++		}
++
++		surface_dtx_update_opmpde(ddev);
++	}
++
++	return 0;
++}
++
++static int surface_dtx_events_setup(struct surface_dtx_dev *ddev)
++{
++	int status;
++
++	status = surface_sam_ssh_set_event_handler(SAM_EVENT_DTX_RQID, surface_dtx_evt_dtx, ddev);
++	if (status) {
++		goto err_handler;
++	}
++
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID);
++	if (status) {
++		goto err_source;
++	}
++
++	return 0;
++
++err_source:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID);
++err_handler:
++	return status;
++}
++
++static void surface_dtx_events_disable(void)
++{
++	surface_sam_ssh_disable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID);
++}
++
++
++static struct input_dev *surface_dtx_register_inputdev(struct platform_device *pdev)
++{
++	struct input_dev *input_dev;
++	int status;
++
++	input_dev = input_allocate_device();
++	if (!input_dev) {
++		return ERR_PTR(-ENOMEM);
++	}
++
++	input_dev->name = DTX_INPUT_NAME;
++	input_dev->dev.parent = &pdev->dev;
++	input_dev->id.bustype = BUS_VIRTUAL;
++	input_dev->id.vendor  = USB_VENDOR_ID_MICROSOFT;
++	input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION;
++
++	input_set_capability(input_dev, EV_SW, SW_TABLET_MODE);
++
++	status = surface_sam_query_opmpde();
++	if (status < 0) {
++		input_free_device(input_dev);
++		return ERR_PTR(status);
++	}
++
++	input_report_switch(input_dev, SW_TABLET_MODE, status == 0x00);
++
++	status = input_register_device(input_dev);
++	if (status) {
++		input_unregister_device(input_dev);
++		return ERR_PTR(status);
++	}
++
++	return input_dev;
++}
++
++
++static int surface_sam_dtx_probe(struct platform_device *pdev)
++{
++	struct surface_dtx_dev *ddev = &surface_dtx_dev;
++	struct input_dev *input_dev;
++	int status;
++
++	// link to ec
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
++	}
++
++	input_dev = surface_dtx_register_inputdev(pdev);
++	if (IS_ERR(input_dev)) {
++		return PTR_ERR(input_dev);
++	}
++
++	// initialize device
++	mutex_lock(&ddev->mutex);
++	if (ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		status = -ENODEV;
++		goto err_register;
++	}
++
++	INIT_LIST_HEAD(&ddev->client_list);
++	init_waitqueue_head(&ddev->waitq);
++	ddev->active = true;
++	ddev->input_dev = input_dev;
++	mutex_unlock(&ddev->mutex);
++
++	status = misc_register(&ddev->mdev);
++	if (status) {
++		goto err_register;
++	}
++
++	// enable events
++	status = surface_dtx_events_setup(ddev);
++	if (status) {
++		goto err_events_setup;
++	}
++
++	return 0;
++
++err_events_setup:
++	misc_deregister(&ddev->mdev);
++err_register:
++	input_unregister_device(ddev->input_dev);
++	return status;
++}
++
++static int surface_sam_dtx_remove(struct platform_device *pdev)
++{
++	struct surface_dtx_dev *ddev = &surface_dtx_dev;
++	struct surface_dtx_client *client;
++
++	mutex_lock(&ddev->mutex);
++	if (!ddev->active) {
++		mutex_unlock(&ddev->mutex);
++		return 0;
++	}
++
++	// mark as inactive
++	ddev->active = false;
++	mutex_unlock(&ddev->mutex);
++
++	// After this call we're guaranteed that no more input events will arive
++	surface_dtx_events_disable();
++
++	// wake up clients
++	spin_lock(&ddev->client_lock);
++	list_for_each_entry(client, &ddev->client_list, node) {
++		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
++	}
++	spin_unlock(&ddev->client_lock);
++
++	wake_up_interruptible(&ddev->waitq);
++
++	// unregister user-space devices
++	input_unregister_device(ddev->input_dev);
++	misc_deregister(&ddev->mdev);
++
++	return 0;
++}
++
++
++static const struct acpi_device_id surface_sam_dtx_match[] = {
++	{ "MSHW0133", 0 },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match);
++
++struct platform_driver surface_sam_dtx = {
++	.probe = surface_sam_dtx_probe,
++	.remove = surface_sam_dtx_remove,
++	.driver = {
++		.name = "surface_sam_dtx",
++		.acpi_match_table = ACPI_PTR(surface_sam_dtx_match),
++	},
++};
++module_platform_driver(surface_sam_dtx);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.c b/drivers/platform/x86/surface_sam/surface_sam_san.c
+new file mode 100644
+index 000000000000..9da7843167ad
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_san.c
+@@ -0,0 +1,708 @@
++/*
++ * Surface ACPI Notify (SAN) and ACPI integration driver for SAM.
++ * Translates communication from ACPI to SSH and back.
++ */
++
++#include <linux/acpi.h>
++#include <linux/delay.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/platform_device.h>
++
++#include "surface_sam_ssh.h"
++
++
++#define SAN_RQST_RETRY				5
++
++#define SAN_DSM_REVISION			0
++#define SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT	0x09
++
++static const guid_t SAN_DSM_UUID =
++	GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d,
++	          0x48, 0x7c, 0x91, 0xab, 0x3c);
++
++#define SAM_EVENT_DELAY_PWR_STATE	msecs_to_jiffies(5000)
++
++#define SAM_EVENT_PWR_TC		0x02
++#define SAM_EVENT_PWR_RQID		0x0002
++#define SAM_EVENT_PWR_CID_HWCHANGE	0x15
++#define SAM_EVENT_PWR_CID_CHARGING	0x16
++#define SAM_EVENT_PWR_CID_ADAPTER	0x17
++#define SAM_EVENT_PWR_CID_STATE		0x4f
++
++#define SAM_EVENT_TEMP_TC		0x03
++#define SAM_EVENT_TEMP_RQID		0x0003
++#define SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT	0x0b
++
++#define SAN_RQST_TAG			"surface_sam_san_rqst: "
++
++#define SAN_QUIRK_BASE_STATE_DELAY	1000
++
++
++struct san_acpi_consumer {
++	char *path;
++	bool  required;
++	u32   flags;
++};
++
++struct san_opreg_context {
++	struct acpi_connection_info connection;
++	struct device *dev;
++};
++
++struct san_consumer_link {
++	const struct san_acpi_consumer *properties;
++	struct device_link                         *link;
++};
++
++struct san_consumers {
++	u32                                   num;
++	struct san_consumer_link *links;
++};
++
++struct san_drvdata {
++	struct san_opreg_context opreg_ctx;
++	struct san_consumers     consumers;
++};
++
++struct gsb_data_in {
++	u8 cv;
++} __packed;
++
++struct gsb_data_rqsx {
++	u8 cv;				// command value (should be 0x01 or 0x03)
++	u8 tc;				// target controller
++	u8 tid;				// expected to be 0x01, could be revision
++	u8 iid;				// target sub-controller (e.g. primary vs. secondary battery)
++	u8 snc;				// expect-response-flag
++	u8 cid;				// command ID
++	u8 cdl;				// payload length
++	u8 _pad;			// padding
++	u8 pld[0];			// payload
++} __packed;
++
++struct gsb_data_etwl {
++	u8 cv;				// command value (should be 0x02)
++	u8 etw3;			// ?
++	u8 etw4;			// ?
++	u8 msg[0];			// error message (ASCIIZ)
++} __packed;
++
++struct gsb_data_out {
++	u8 status;			// _SSH communication status
++	u8 len;				// _SSH payload length
++	u8 pld[0];			// _SSH payload
++} __packed;
++
++union gsb_buffer_data {
++	struct gsb_data_in   in;	// common input
++	struct gsb_data_rqsx rqsx;	// RQSX input
++	struct gsb_data_etwl etwl;	// ETWL input
++	struct gsb_data_out  out;	// output
++};
++
++struct gsb_buffer {
++	u8 status;			// GSB AttribRawProcess status
++	u8 len;				// GSB AttribRawProcess length
++	union gsb_buffer_data data;
++} __packed;
++
++
++enum san_pwr_event {
++	SAN_PWR_EVENT_BAT1_STAT	= 0x03,
++	SAN_PWR_EVENT_BAT1_INFO	= 0x04,
++	SAN_PWR_EVENT_ADP1_STAT	= 0x05,
++	SAN_PWR_EVENT_ADP1_INFO	= 0x06,
++	SAN_PWR_EVENT_BAT2_STAT	= 0x07,
++	SAN_PWR_EVENT_BAT2_INFO	= 0x08,
++};
++
++
++static int san_acpi_notify_power_event(struct device *dev, enum san_pwr_event event)
++{
++	acpi_handle san = ACPI_HANDLE(dev);
++	union acpi_object *obj;
++
++	obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION,
++	                              (u8) event, NULL, ACPI_TYPE_BUFFER);
++
++	if (IS_ERR_OR_NULL(obj)) {
++		return obj ? PTR_ERR(obj) : -ENXIO;
++	}
++
++	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
++		dev_err(dev, "got unexpected result from _DSM\n");
++		return -EFAULT;
++	}
++
++	ACPI_FREE(obj);
++	return 0;
++}
++
++static int san_acpi_notify_sensor_trip_point(struct device *dev, u8 iid)
++{
++	acpi_handle san = ACPI_HANDLE(dev);
++	union acpi_object *obj;
++	union acpi_object param;
++
++	param.type = ACPI_TYPE_INTEGER;
++	param.integer.value = iid;
++
++	obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION,
++	                              SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT,
++				      &param, ACPI_TYPE_BUFFER);
++
++	if (IS_ERR_OR_NULL(obj)) {
++		return obj ? PTR_ERR(obj) : -ENXIO;
++	}
++
++	if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) {
++		dev_err(dev, "got unexpected result from _DSM\n");
++		return -EFAULT;
++	}
++
++	ACPI_FREE(obj);
++	return 0;
++}
++
++
++inline static int san_evt_power_adapter(struct device *dev, struct surface_sam_ssh_event *event)
++{
++	int status;
++
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_ADP1_STAT);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
++	}
++
++	return 0;
++}
++
++inline static int san_evt_power_hwchange(struct device *dev, struct surface_sam_ssh_event *event)
++{
++	enum san_pwr_event evcode;
++	int status;
++
++	if (event->iid == 0x02) {
++		evcode = SAN_PWR_EVENT_BAT2_INFO;
++	} else {
++		evcode = SAN_PWR_EVENT_BAT1_INFO;
++	}
++
++	status = san_acpi_notify_power_event(dev, evcode);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
++	}
++
++	return 0;
++}
++
++inline static int san_evt_power_state(struct device *dev, struct surface_sam_ssh_event *event)
++{
++	int status;
++
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT1_STAT);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
++	}
++
++	status = san_acpi_notify_power_event(dev, SAN_PWR_EVENT_BAT2_STAT);
++	if (status) {
++		dev_err(dev, "error handling power event (cid = %x)\n", event->cid);
++		return status;
++	}
++
++	return 0;
++}
++
++static unsigned long san_evt_power_delay(struct surface_sam_ssh_event *event, void *data)
++{
++	switch (event->cid) {
++	case SAM_EVENT_PWR_CID_CHARGING:
++	case SAM_EVENT_PWR_CID_STATE:
++		return SAM_EVENT_DELAY_PWR_STATE;
++
++	case SAM_EVENT_PWR_CID_ADAPTER:
++	case SAM_EVENT_PWR_CID_HWCHANGE:
++	default:
++		return 0;
++	}
++}
++
++static int san_evt_power(struct surface_sam_ssh_event *event, void *data)
++{
++	struct device *dev = (struct device *)data;
++
++	switch (event->cid) {
++	case SAM_EVENT_PWR_CID_HWCHANGE:
++		return san_evt_power_hwchange(dev, event);
++
++	case SAM_EVENT_PWR_CID_ADAPTER:
++		return san_evt_power_adapter(dev, event);
++
++	case SAM_EVENT_PWR_CID_CHARGING:
++	case SAM_EVENT_PWR_CID_STATE:
++		return san_evt_power_state(dev, event);
++
++	default:
++		dev_warn(dev, "unhandled power event (cid = %x)\n", event->cid);
++	}
++
++	return 0;
++}
++
++
++inline static int san_evt_thermal_notify(struct device *dev, struct surface_sam_ssh_event *event)
++{
++	int status;
++
++	status = san_acpi_notify_sensor_trip_point(dev, event->iid);
++	if (status) {
++		dev_err(dev, "error handling thermal event (cid = %x)\n", event->cid);
++		return status;
++	}
++
++	return 0;
++}
++
++static int san_evt_thermal(struct surface_sam_ssh_event *event, void *data)
++{
++	struct device *dev = (struct device *)data;
++
++	switch (event->cid) {
++	case SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT:
++		return san_evt_thermal_notify(dev, event);
++
++	default:
++		dev_warn(dev, "unhandled thermal event (cid = %x)\n", event->cid);
++	}
++
++	return 0;
++}
++
++
++static struct gsb_data_rqsx
++*san_validate_rqsx(struct device *dev, const char *type, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *rqsx = &buffer->data.rqsx;
++
++	if (buffer->len < 8) {
++		dev_err(dev, "invalid %s package (len = %d)\n",
++			type, buffer->len);
++		return NULL;
++	}
++
++	if (rqsx->cdl != buffer->len - 8) {
++		dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n",
++			type, buffer->len, rqsx->cdl);
++		return NULL;
++	}
++
++	if (rqsx->tid != 0x01) {
++		dev_warn(dev, "unsupported %s package (tid = 0x%02x)\n",
++			 type, rqsx->tid);
++		return NULL;
++	}
++
++	return rqsx;
++}
++
++static acpi_status
++san_etwl(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
++{
++	struct gsb_data_etwl *etwl = &buffer->data.etwl;
++
++	if (buffer->len < 3) {
++		dev_err(ctx->dev, "invalid ETWL package (len = %d)\n", buffer->len);
++		return AE_OK;
++	}
++
++	dev_err(ctx->dev, "ETWL(0x%02x, 0x%02x): %.*s\n",
++		etwl->etw3, etwl->etw4,
++		buffer->len - 3, (char *)etwl->msg);
++
++	// indicate success
++	buffer->status = 0x00;
++	buffer->len = 0x00;
++
++	return AE_OK;
++}
++
++static acpi_status
++san_rqst(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *gsb_rqst = san_validate_rqsx(ctx->dev, "RQST", buffer);
++	struct surface_sam_ssh_rqst rqst = {};
++	struct surface_sam_ssh_buf result = {};
++	int status = 0;
++	int try;
++
++	if (!gsb_rqst) {
++		return AE_OK;
++	}
++
++	rqst.tc  = gsb_rqst->tc;
++	rqst.iid = gsb_rqst->iid;
++	rqst.cid = gsb_rqst->cid;
++	rqst.snc = gsb_rqst->snc;
++	rqst.cdl = gsb_rqst->cdl;
++	rqst.pld = &gsb_rqst->pld[0];
++
++	result.cap  = SURFACE_SAM_SSH_MAX_RQST_RESPONSE;
++	result.len  = 0;
++	result.data = kzalloc(result.cap, GFP_KERNEL);
++
++	if (!result.data) {
++		return AE_NO_MEMORY;
++	}
++
++	for (try = 0; try < SAN_RQST_RETRY; try++) {
++		if (try) {
++			dev_warn(ctx->dev, SAN_RQST_TAG "IO error occured, trying again\n");
++		}
++
++		status = surface_sam_ssh_rqst(&rqst, &result);
++		if (status != -EIO) break;
++	}
++
++	if (rqst.tc == 0x11 && rqst.cid == 0x0D && status == -EPERM) {
++		/* Base state quirk:
++		 * The base state may be queried from ACPI when the EC is still
++		 * suspended. In this case it will return '-EPERM'. This query
++		 * will only be triggered from the ACPI lid GPE interrupt, thus
++		 * we are either in laptop or studio mode (base status 0x01 or
++		 * 0x02). Furthermore, we will only get here if the device (and
++		 * EC) have been suspended.
++		 *
++		 * We now assume that the device is in laptop mode (0x01). This
++		 * has the drawback that it will wake the device when unfolding
++		 * it in studio mode, but it also allows us to avoid actively
++		 * waiting for the EC to wake up, which may incur a notable
++		 * delay.
++		 */
++
++		buffer->status          = 0x00;
++		buffer->len             = 0x03;
++		buffer->data.out.status = 0x00;
++		buffer->data.out.len    = 0x01;
++		buffer->data.out.pld[0] = 0x01;
++
++	} else if (!status) {		// success
++		buffer->status          = 0x00;
++		buffer->len             = result.len + 2;
++		buffer->data.out.status = 0x00;
++		buffer->data.out.len    = result.len;
++		memcpy(&buffer->data.out.pld[0], result.data, result.len);
++
++	} else {			// failure
++		dev_err(ctx->dev, SAN_RQST_TAG "failed with error %d\n", status);
++		buffer->status          = 0x00;
++		buffer->len             = 0x02;
++		buffer->data.out.status = 0x01;		// indicate _SSH error
++		buffer->data.out.len    = 0x00;
++	}
++
++	kfree(result.data);
++
++	return AE_OK;
++}
++
++static acpi_status
++san_rqsg(struct san_opreg_context *ctx, struct gsb_buffer *buffer)
++{
++	struct gsb_data_rqsx *rqsg = san_validate_rqsx(ctx->dev, "RQSG", buffer);
++
++	if (!rqsg) {
++		return AE_OK;
++	}
++
++	// TODO: RQSG handler
++
++	dev_warn(ctx->dev, "unsupported request: RQSG(0x%02x, 0x%02x, 0x%02x)\n",
++		 rqsg->tc, rqsg->cid, rqsg->iid);
++
++	return AE_OK;
++}
++
++
++static acpi_status
++san_opreg_handler(u32 function, acpi_physical_address command,
++		  u32 bits, u64 *value64,
++		  void *opreg_context, void *region_context)
++{
++	struct san_opreg_context *context = opreg_context;
++	struct gsb_buffer *buffer = (struct gsb_buffer *)value64;
++	int accessor_type = (0xFFFF0000 & function) >> 16;
++
++	if (command != 0) {
++		dev_warn(context->dev, "unsupported command: 0x%02llx\n", command);
++		return AE_OK;
++	}
++
++	if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) {
++		dev_err(context->dev, "invalid access type: 0x%02x\n", accessor_type);
++		return AE_OK;
++	}
++
++	// buffer must have at least contain the command-value
++	if (buffer->len == 0) {
++		dev_err(context->dev, "request-package too small\n");
++		return AE_OK;
++	}
++
++	switch (buffer->data.in.cv) {
++	case 0x01:  return san_rqst(context, buffer);
++	case 0x02:  return san_etwl(context, buffer);
++	case 0x03:  return san_rqsg(context, buffer);
++	}
++
++	dev_warn(context->dev, "unsupported SAN0 request (cv: 0x%02x)\n", buffer->data.in.cv);
++	return AE_OK;
++}
++
++static int san_enable_events(struct device *dev)
++{
++	int status;
++
++	status = surface_sam_ssh_set_delayed_event_handler(
++			SAM_EVENT_PWR_RQID, san_evt_power,
++			san_evt_power_delay, dev);
++	if (status) {
++		goto err_handler_power;
++	}
++
++	status = surface_sam_ssh_set_event_handler(
++			SAM_EVENT_TEMP_RQID, san_evt_thermal,
++			dev);
++	if (status) {
++		goto err_handler_thermal;
++	}
++
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++	if (status) {
++		goto err_source_power;
++	}
++
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID);
++	if (status) {
++		goto err_source_thermal;
++	}
++
++	return 0;
++
++err_source_thermal:
++	surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++err_source_power:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID);
++err_handler_thermal:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID);
++err_handler_power:
++	return status;
++}
++
++static void san_disable_events(void)
++{
++	surface_sam_ssh_disable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID);
++	surface_sam_ssh_disable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_TEMP_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_PWR_RQID);
++}
++
++
++static int san_consumers_link(struct platform_device *pdev,
++			      const struct san_acpi_consumer *cons,
++			      struct san_consumers *out)
++{
++	const struct san_acpi_consumer *con;
++	struct san_consumer_link *links, *link;
++	struct acpi_device *adev;
++	acpi_handle handle;
++	u32 max_links = 0;
++	int status;
++
++	if (!cons) {
++		return 0;
++	}
++
++	// count links
++	for (con = cons; con->path; ++con) {
++		max_links += 1;
++	}
++
++	// allocate
++	links = kzalloc(max_links * sizeof(struct san_consumer_link), GFP_KERNEL);
++	link = &links[0];
++
++	if (!links) {
++		return -ENOMEM;
++	}
++
++	// create links
++	for (con = cons; con->path; ++con) {
++		status = acpi_get_handle(NULL, con->path, &handle);
++		if (status) {
++			if (con->required || status != AE_NOT_FOUND) {
++				status = -ENXIO;
++				goto cleanup;
++			} else {
++				continue;
++			}
++		}
++
++		status = acpi_bus_get_device(handle, &adev);
++		if (status) {
++			goto cleanup;
++		}
++
++		link->link = device_link_add(&adev->dev, &pdev->dev, con->flags);
++		if (!(link->link)) {
++			status = -EFAULT;
++			goto cleanup;
++		}
++		link->properties = con;
++
++		link += 1;
++	}
++
++	out->num = link - links;
++	out->links = links;
++
++	return 0;
++
++cleanup:
++	for (link = link - 1; link >= links; --link) {
++		if (link->properties->flags & DL_FLAG_STATELESS) {
++			device_link_del(link->link);
++		}
++	}
++
++	return status;
++}
++
++static void san_consumers_unlink(struct san_consumers *consumers) {
++	u32 i;
++
++	if (!consumers) {
++		return;
++	}
++
++	for (i = 0; i < consumers->num; ++i) {
++		if (consumers->links[i].properties->flags & DL_FLAG_STATELESS) {
++			device_link_del(consumers->links[i].link);
++		}
++	}
++
++	kfree(consumers->links);
++
++	consumers->num = 0;
++	consumers->links = NULL;
++}
++
++static int surface_sam_san_probe(struct platform_device *pdev)
++{
++	const struct san_acpi_consumer *cons;
++	struct san_drvdata *drvdata;
++	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
++	int status;
++
++	/*
++	 * Defer probe if the _SSH driver has not set up the controller yet. This
++	 * makes sure we do not fail any initial requests (e.g. _STA request without
++	 * which the battery does not get set up correctly). Otherwise register as
++	 * consumer to set up a device_link.
++	 */
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
++	}
++
++	drvdata = kzalloc(sizeof(struct san_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		return -ENOMEM;
++	}
++
++	drvdata->opreg_ctx.dev = &pdev->dev;
++
++	cons = acpi_device_get_match_data(&pdev->dev);
++	status = san_consumers_link(pdev, cons, &drvdata->consumers);
++	if (status) {
++		goto err_consumers;
++	}
++
++	platform_set_drvdata(pdev, drvdata);
++
++	status = acpi_install_address_space_handler(san,
++			ACPI_ADR_SPACE_GSBUS,
++			&san_opreg_handler,
++			NULL, &drvdata->opreg_ctx);
++
++	if (ACPI_FAILURE(status)) {
++		status = -ENODEV;
++		goto err_install_handler;
++	}
++
++	status = san_enable_events(&pdev->dev);
++	if (status) {
++		goto err_enable_events;
++	}
++
++	acpi_walk_dep_device_list(san);
++	return 0;
++
++err_enable_events:
++	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler);
++err_install_handler:
++	platform_set_drvdata(san, NULL);
++	san_consumers_unlink(&drvdata->consumers);
++err_consumers:
++	kfree(drvdata);
++	return status;
++}
++
++static int surface_sam_san_remove(struct platform_device *pdev)
++{
++	struct san_drvdata *drvdata = platform_get_drvdata(pdev);
++	acpi_handle san = ACPI_HANDLE(&pdev->dev);	// _SAN device node
++	acpi_status status = AE_OK;
++
++	acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler);
++	san_disable_events();
++
++	san_consumers_unlink(&drvdata->consumers);
++	kfree(drvdata);
++
++	platform_set_drvdata(pdev, NULL);
++	return status;
++}
++
++
++static const struct san_acpi_consumer san_mshw0091_consumers[] = {
++	{ "\\_SB.SRTC", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\ADP1",     true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\_SB.BAT1", true,  DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ "\\_SB.BAT2", false, DL_FLAG_PM_RUNTIME | DL_FLAG_STATELESS },
++	{ },
++};
++
++static const struct acpi_device_id surface_sam_san_match[] = {
++	{ "MSHW0091", (long unsigned int) san_mshw0091_consumers },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_san_match);
++
++struct platform_driver surface_sam_san = {
++	.probe = surface_sam_san_probe,
++	.remove = surface_sam_san_remove,
++	.driver = {
++		.name = "surface_sam_san",
++		.acpi_match_table = ACPI_PTR(surface_sam_san_match),
++	},
++};
++module_platform_driver(surface_sam_san);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid.c b/drivers/platform/x86/surface_sam/surface_sam_sid.c
+new file mode 100644
+index 000000000000..ff440619bcf2
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_sid.c
+@@ -0,0 +1,483 @@
++/*
++ * Surface/System Integration Device (SID) driver.
++ * Intended for device-dependent configuration and minor functionality.
++ * Handles performance-modes and wakeup via lid open.
++ */
++
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/dmi.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/moduleparam.h>
++#include <linux/platform_device.h>
++#include <linux/sysfs.h>
++
++#include "surface_sam_ssh.h"
++
++
++struct sid_lid_device {
++	const char *acpi_path;
++	const u32 gpe_number;
++};
++
++struct sid_device_info {
++	const bool has_perf_mode;
++	const struct sid_lid_device *lid_device;
++};
++
++
++static const struct sid_lid_device lid_device_l17 = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x17,
++};
++
++static const struct sid_lid_device lid_device_l4F = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x4F,
++};
++
++static const struct sid_lid_device lid_device_l57 = {
++	.acpi_path = "\\_SB.LID0",
++	.gpe_number = 0x57,
++};
++
++
++static const struct sid_device_info si_device_pro_4 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l17,
++};
++
++static const struct sid_device_info si_device_pro_5 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l4F,
++};
++
++static const struct sid_device_info si_device_pro_6 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l4F,
++};
++
++static const struct sid_device_info si_device_book_1 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l17,
++};
++
++static const struct sid_device_info si_device_book_2 = {
++	.has_perf_mode = true,
++	.lid_device = &lid_device_l17,
++};
++
++static const struct sid_device_info si_device_laptop_1 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l57,
++};
++
++static const struct sid_device_info si_device_laptop_2 = {
++	.has_perf_mode = false,
++	.lid_device = &lid_device_l57,
++};
++
++
++static const struct dmi_system_id dmi_lid_device_table[] = {
++	{
++		.ident = "Surface Pro 4",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
++		},
++		.driver_data = (void *)&si_device_pro_4,
++	},
++	{
++		.ident = "Surface Pro 5",
++		.matches = {
++			/* match for SKU here due to generic product name "Surface Pro" */
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
++		},
++		.driver_data = (void *)&si_device_pro_5,
++	},
++	{
++		.ident = "Surface Pro 5 (LTE)",
++		.matches = {
++			/* match for SKU here due to generic product name "Surface Pro" */
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
++		},
++		.driver_data = (void *)&si_device_pro_5,
++	},
++	{
++		.ident = "Surface Pro 6",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
++		},
++		.driver_data = (void *)&si_device_pro_6,
++	},
++	{
++		.ident = "Surface Book 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
++		},
++		.driver_data = (void *)&si_device_book_1,
++	},
++	{
++		.ident = "Surface Book 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
++		},
++		.driver_data = (void *)&si_device_book_2,
++	},
++	{
++		.ident = "Surface Laptop 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
++		},
++		.driver_data = (void *)&si_device_laptop_1,
++	},
++	{
++		.ident = "Surface Laptop 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
++		},
++		.driver_data = (void *)&si_device_laptop_2,
++	},
++	{ }
++};
++
++
++#define SID_PARAM_PERM		(S_IRUGO | S_IWUSR)
++
++enum sam_perf_mode {
++	SAM_PERF_MODE_NORMAL   = 1,
++	SAM_PERF_MODE_BATTERY  = 2,
++	SAM_PERF_MODE_PERF1    = 3,
++	SAM_PERF_MODE_PERF2    = 4,
++
++	__SAM_PERF_MODE__START = 1,
++	__SAM_PERF_MODE__END   = 4,
++};
++
++enum sid_param_perf_mode {
++	SID_PARAM_PERF_MODE_AS_IS    = 0,
++	SID_PARAM_PERF_MODE_NORMAL   = SAM_PERF_MODE_NORMAL,
++	SID_PARAM_PERF_MODE_BATTERY  = SAM_PERF_MODE_BATTERY,
++	SID_PARAM_PERF_MODE_PERF1    = SAM_PERF_MODE_PERF1,
++	SID_PARAM_PERF_MODE_PERF2    = SAM_PERF_MODE_PERF2,
++
++	__SID_PARAM_PERF_MODE__START = 0,
++	__SID_PARAM_PERF_MODE__END   = 4,
++};
++
++
++static int surface_sam_perf_mode_get(void)
++{
++	u8 result_buf[8] = { 0 };
++	int status;
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x02,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		.cap = ARRAY_SIZE(result_buf),
++		.len = 0,
++		.data = result_buf,
++	};
++
++	status = surface_sam_ssh_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (result.len != 8) {
++		return -EFAULT;
++	}
++
++	return get_unaligned_le32(&result.data[0]);
++}
++
++static int surface_sam_perf_mode_set(int perf_mode)
++{
++	u8 payload[4] = { 0 };
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x03,
++		.iid = 0x00,
++		.cid = 0x03,
++		.snc = 0x00,
++		.cdl = ARRAY_SIZE(payload),
++		.pld = payload,
++	};
++
++	if (perf_mode < __SAM_PERF_MODE__START || perf_mode > __SAM_PERF_MODE__END) {
++		return -EINVAL;
++	}
++
++	put_unaligned_le32(perf_mode, &rqst.pld[0]);
++	return surface_sam_ssh_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 < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_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 = SID_PARAM_PERF_MODE_AS_IS;
++static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS;
++
++module_param_cb(perf_mode_init, &param_perf_mode_ops, &param_perf_mode_init, SID_PARAM_PERM);
++module_param_cb(perf_mode_exit, &param_perf_mode_ops, &param_perf_mode_exit, SID_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 = surface_sam_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 = surface_sam_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 sid_perf_mode_setup(struct platform_device *pdev, const struct sid_device_info *info)
++{
++	int status;
++
++	if (!info->has_perf_mode)
++		return 0;
++
++	// link to ec
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
++	}
++
++	// set initial perf_mode
++	if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) {
++		status = surface_sam_perf_mode_set(param_perf_mode_init);
++		if (status) {
++			return status;
++		}
++	}
++
++	// 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:
++	surface_sam_perf_mode_set(param_perf_mode_exit);
++	return status;
++}
++
++static void sid_perf_mode_remove(struct platform_device *pdev, const struct sid_device_info *info)
++{
++	if (!info->has_perf_mode)
++		return;
++
++	// remove perf_mode attribute
++	sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr);
++
++	// set exit perf_mode
++	surface_sam_perf_mode_set(param_perf_mode_exit);
++}
++
++
++static int sid_lid_enable_wakeup(const struct sid_device_info *info, bool enable)
++{
++	int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE;
++	int status;
++
++	if (!info->lid_device)
++		return 0;
++
++	status = acpi_set_gpe_wake_mask(NULL, info->lid_device->gpe_number, action);
++	if (status)
++		return -EFAULT;
++
++	return 0;
++}
++
++static int sid_lid_device_setup(const struct sid_device_info *info)
++{
++	acpi_handle lid_handle;
++	int status;
++
++	if (!info->lid_device)
++		return 0;
++
++	status = acpi_get_handle(NULL, (acpi_string)info->lid_device->acpi_path, &lid_handle);
++	if (status)
++		return -EFAULT;
++
++	status = acpi_setup_gpe_for_wake(lid_handle, NULL, info->lid_device->gpe_number);
++	if (status)
++		return -EFAULT;
++
++	status = acpi_enable_gpe(NULL, info->lid_device->gpe_number);
++	if (status)
++		return -EFAULT;
++
++	return sid_lid_enable_wakeup(info, false);
++}
++
++static void sid_lid_device_remove(const struct sid_device_info *info)
++{
++	/* restore default behavior without this module */
++	sid_lid_enable_wakeup(info, false);
++}
++
++
++static int surface_sam_sid_suspend(struct device *dev)
++{
++	const struct sid_device_info *info = dev_get_drvdata(dev);
++	return sid_lid_enable_wakeup(info, true);
++}
++
++static int surface_sam_sid_resume(struct device *dev)
++{
++	const struct sid_device_info *info = dev_get_drvdata(dev);
++	return sid_lid_enable_wakeup(info, false);
++}
++
++static SIMPLE_DEV_PM_OPS(surface_sam_sid_pm, surface_sam_sid_suspend, surface_sam_sid_resume);
++
++
++static int surface_sam_sid_probe(struct platform_device *pdev)
++{
++	const struct dmi_system_id *dmi_match;
++	struct sid_device_info *info;
++	int status;
++
++	dmi_match = dmi_first_match(dmi_lid_device_table);
++	if (!dmi_match)
++		return -ENODEV;
++
++	info = dmi_match->driver_data;
++
++	platform_set_drvdata(pdev, info);
++
++	status = sid_perf_mode_setup(pdev, info);
++	if (status)
++		goto err_perf_mode;
++
++	status = sid_lid_device_setup(info);
++	if (status)
++		goto err_lid;
++
++	return 0;
++
++err_lid:
++	sid_perf_mode_remove(pdev, info);
++err_perf_mode:
++	return status;
++}
++
++static int surface_sam_sid_remove(struct platform_device *pdev)
++{
++	const struct sid_device_info *info = platform_get_drvdata(pdev);
++
++	sid_perf_mode_remove(pdev, info);
++	sid_lid_device_remove(info);
++
++	platform_set_drvdata(pdev, NULL);
++	return 0;
++}
++
++
++static const struct acpi_device_id surface_sam_sid_match[] = {
++	{ "MSHW0081", },	/* Surface Pro 4, 5, and 6 */
++	{ "MSHW0080", },	/* Surface Book 1 */
++	{ "MSHW0107", },	/* Surface Book 2 */
++	{ "MSHW0086", },	/* Surface Laptop 1 */
++	{ "MSHW0112", },	/* Surface Laptop 2 */
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_sid_match);
++
++struct platform_driver surface_sam_sid = {
++	.probe = surface_sam_sid_probe,
++	.remove = surface_sam_sid_remove,
++	.driver = {
++		.name = "surface_sam_sid",
++		.acpi_match_table = ACPI_PTR(surface_sam_sid_match),
++		.pm = &surface_sam_sid_pm,
++	},
++};
++module_platform_driver(surface_sam_sid);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Integration Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.c b/drivers/platform/x86/surface_sam/surface_sam_ssh.c
+new file mode 100644
+index 000000000000..f190efcf8705
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.c
+@@ -0,0 +1,1691 @@
++/*
++ * Surface Serial Hub (SSH) driver for communication with the Surface/System
++ * Aggregator Module.
++ */
++
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/completion.h>
++#include <linux/crc-ccitt.h>
++#include <linux/device.h>
++#include <linux/dmaengine.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/kfifo.h>
++#include <linux/mutex.h>
++#include <linux/pm.h>
++#include <linux/refcount.h>
++#include <linux/serdev.h>
++#include <linux/spinlock.h>
++#include <linux/workqueue.h>
++
++#include "surface_sam_ssh.h"
++
++
++#define SSH_RQST_TAG_FULL			"surface_sam_ssh_rqst: "
++#define SSH_RQST_TAG				"rqst: "
++#define SSH_EVENT_TAG				"event: "
++#define SSH_RECV_TAG				"recv: "
++
++#define SSH_SUPPORTED_FLOW_CONTROL_MASK		(~((u8) ACPI_UART_FLOW_CONTROL_HW))
++
++#define SSH_BYTELEN_SYNC			2
++#define SSH_BYTELEN_TERM			2
++#define SSH_BYTELEN_CRC				2
++#define SSH_BYTELEN_CTRL			4	// command-header, ACK, or RETRY
++#define SSH_BYTELEN_CMDFRAME			8	// without payload
++
++#define SSH_MAX_WRITE (				\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_CMDFRAME			\
++	+ SURFACE_SAM_SSH_MAX_RQST_PAYLOAD	\
++	+ SSH_BYTELEN_CRC			\
++)
++
++#define SSH_MSG_LEN_CTRL (			\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_TERM			\
++)
++
++#define SSH_MSG_LEN_CMD_BASE (			\
++	  SSH_BYTELEN_SYNC			\
++	+ SSH_BYTELEN_CTRL			\
++	+ SSH_BYTELEN_CRC			\
++	+ SSH_BYTELEN_CRC			\
++)	// without payload and command-frame
++
++#define SSH_WRITE_TIMEOUT		msecs_to_jiffies(1000)
++#define SSH_READ_TIMEOUT		msecs_to_jiffies(1000)
++#define SSH_NUM_RETRY			3
++
++#define SSH_WRITE_BUF_LEN		SSH_MAX_WRITE
++#define SSH_READ_BUF_LEN		512		// must be power of 2
++#define SSH_EVAL_BUF_LEN		SSH_MAX_WRITE	// also works for reading
++
++#define SSH_FRAME_TYPE_CMD		0x80
++#define SSH_FRAME_TYPE_ACK		0x40
++#define SSH_FRAME_TYPE_RETRY		0x04
++
++#define SSH_FRAME_OFFS_CTRL		SSH_BYTELEN_SYNC
++#define SSH_FRAME_OFFS_CTRL_CRC		(SSH_FRAME_OFFS_CTRL + SSH_BYTELEN_CTRL)
++#define SSH_FRAME_OFFS_TERM		(SSH_FRAME_OFFS_CTRL_CRC + SSH_BYTELEN_CRC)
++#define SSH_FRAME_OFFS_CMD		SSH_FRAME_OFFS_TERM	// either TERM or CMD
++#define SSH_FRAME_OFFS_CMD_PLD		(SSH_FRAME_OFFS_CMD + SSH_BYTELEN_CMDFRAME)
++
++/*
++ * A note on Request IDs (RQIDs):
++ *	0x0000 is not a valid RQID
++ *	0x0001 is valid, but reserved for Surface Laptop keyboard events
++ */
++#define SAM_NUM_EVENT_TYPES		((1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1)
++
++/*
++ * Sync:			aa 55
++ * Terminate:			ff ff
++ *
++ * Request Message:		sync cmd-hdr crc(cmd-hdr) cmd-rqst-frame crc(cmd-rqst-frame)
++ * Ack Message:			sync ack crc(ack) terminate
++ * Retry Message:		sync retry crc(retry) terminate
++ * Response Message:		sync cmd-hdr crc(cmd-hdr) cmd-resp-frame crc(cmd-resp-frame)
++ *
++ * Command Header:		80 LEN 00 SEQ
++ * Ack:				40 00 00 SEQ
++ * Retry:			04 00 00 00
++ * Command Request Frame:	80 RTC 01 00 RIID RQID RCID PLD
++ * Command Response Frame:	80 RTC 00 01 RIID RQID RCID PLD
++ */
++
++struct ssh_frame_ctrl {
++	u8 type;
++	u8 len;			// without crc
++	u8 pad;
++	u8 seq;
++} __packed;
++
++struct ssh_frame_cmd {
++	u8 type;
++	u8 tc;
++	u8 outgoing;		// assumed to be 0x01 for SSH to SAM messages, 0x00 otherwise
++	u8 incoming;		// assumed to be 0x01 for SAM to SSH messages, 0x00 otherwise
++	u8 iid;
++	u8 rqid_lo;		// id for request/response matching (low byte)
++	u8 rqid_hi;		// id for request/response matching (high byte)
++	u8 cid;
++} __packed;
++
++
++enum ssh_ec_state {
++	SSH_EC_UNINITIALIZED,
++	SSH_EC_INITIALIZED,
++	SSH_EC_SUSPENDED,
++};
++
++struct ssh_counters {
++	u8  seq;		// control sequence id
++	u16 rqid;		// id for request/response matching
++};
++
++struct ssh_writer {
++	u8 *data;
++	u8 *ptr;
++} __packed;
++
++enum ssh_receiver_state {
++	SSH_RCV_DISCARD,
++	SSH_RCV_CONTROL,
++	SSH_RCV_COMMAND,
++};
++
++struct ssh_receiver {
++	spinlock_t lock;
++	enum ssh_receiver_state state;
++	struct completion signal;
++	struct kfifo fifo;
++	struct {
++		bool pld;
++		u8 seq;
++		u16 rqid;
++	} expect;
++	struct {
++		u16 cap;
++		u16 len;
++		u8 *ptr;
++	} eval_buf;
++};
++
++struct ssh_event_handler {
++	surface_sam_ssh_event_handler_fn handler;
++	surface_sam_ssh_event_handler_delay delay;
++	void *data;
++};
++
++struct ssh_events {
++	spinlock_t lock;
++	struct workqueue_struct *queue_ack;
++	struct workqueue_struct *queue_evt;
++	struct ssh_event_handler handler[SAM_NUM_EVENT_TYPES];
++};
++
++struct sam_ssh_ec {
++	struct mutex lock;
++	enum ssh_ec_state state;
++	struct serdev_device *serdev;
++	struct ssh_counters counter;
++	struct ssh_writer writer;
++	struct ssh_receiver receiver;
++	struct ssh_events events;
++};
++
++struct ssh_fifo_packet {
++	u8 type;	// packet type (ACK/RETRY/CMD)
++	u8 seq;
++	u8 len;
++};
++
++struct ssh_event_work {
++	refcount_t refcount;
++	struct sam_ssh_ec *ec;
++	struct work_struct work_ack;
++	struct delayed_work work_evt;
++	struct surface_sam_ssh_event event;
++	u8 seq;
++};
++
++
++static struct sam_ssh_ec ssh_ec = {
++	.lock   = __MUTEX_INITIALIZER(ssh_ec.lock),
++	.state  = SSH_EC_UNINITIALIZED,
++	.serdev = NULL,
++	.counter = {
++		.seq  = 0,
++		.rqid = 0,
++	},
++	.writer = {
++		.data = NULL,
++		.ptr  = NULL,
++	},
++	.receiver = {
++		.lock = __SPIN_LOCK_UNLOCKED(),
++		.state = SSH_RCV_DISCARD,
++		.expect = {},
++	},
++	.events = {
++		.lock = __SPIN_LOCK_UNLOCKED(),
++		.handler = {},
++	}
++};
++
++
++static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec,
++					 const struct surface_sam_ssh_rqst *rqst,
++					 struct surface_sam_ssh_buf *result);
++
++
++inline static struct sam_ssh_ec *surface_sam_ssh_acquire(void)
++{
++	struct sam_ssh_ec *ec = &ssh_ec;
++
++	mutex_lock(&ec->lock);
++	return ec;
++}
++
++inline static void surface_sam_ssh_release(struct sam_ssh_ec *ec)
++{
++	mutex_unlock(&ec->lock);
++}
++
++inline static struct sam_ssh_ec *surface_sam_ssh_acquire_init(void)
++{
++	struct sam_ssh_ec *ec = surface_sam_ssh_acquire();
++
++	if (ec->state == SSH_EC_UNINITIALIZED) {
++		surface_sam_ssh_release(ec);
++		return NULL;
++	}
++
++	return ec;
++}
++
++int surface_sam_ssh_consumer_register(struct device *consumer)
++{
++	u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER;
++	struct sam_ssh_ec *ec;
++	struct device_link *link;
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
++	}
++
++	link = device_link_add(consumer, &ec->serdev->dev, flags);
++	if (!link) {
++		return -EFAULT;
++	}
++
++	surface_sam_ssh_release(ec);
++	return 0;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_consumer_register);
++
++
++inline static u16 sam_rqid_to_rqst(u16 rqid) {
++	return rqid << SURFACE_SAM_SSH_RQID_EVENT_BITS;
++}
++
++inline static bool sam_rqid_is_event(u16 rqid) {
++	const u16 mask = (1 << SURFACE_SAM_SSH_RQID_EVENT_BITS) - 1;
++	return rqid != 0 && (rqid | mask) == mask;
++}
++
++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid)
++{
++	struct sam_ssh_ec *ec;
++
++	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
++	u8 buf[1] = { 0x00 };
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x0b,
++		.snc = 0x01,
++		.cdl = 0x04,
++		.pld = pld,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
++
++	int status;
++
++	// only allow RQIDs that lie within event spectrum
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
++	}
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
++	}
++
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
++
++		surface_sam_ssh_release(ec);
++		return -EPERM;
++	}
++
++	status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
++
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while enabling event source: 0x%02x\n",
++			 buf[0]);
++	}
++
++	surface_sam_ssh_release(ec);
++	return status;
++
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_enable_event_source);
++
++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid)
++{
++	struct sam_ssh_ec *ec;
++
++	u8 pld[4] = { tc, unknown, rqid & 0xff, rqid >> 8 };
++	u8 buf[1] = { 0x00 };
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x0c,
++		.snc = 0x01,
++		.cdl = 0x04,
++		.pld = pld,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
++
++	int status;
++
++	// only allow RQIDs that lie within event spectrum
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
++	}
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
++	}
++
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
++
++		surface_sam_ssh_release(ec);
++		return -EPERM;
++	}
++
++	status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
++
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while disabling event source: 0x%02x\n",
++			 buf[0]);
++	}
++
++	surface_sam_ssh_release(ec);
++	return status;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_disable_event_source);
++
++int surface_sam_ssh_set_delayed_event_handler(
++		u16 rqid, surface_sam_ssh_event_handler_fn fn,
++		surface_sam_ssh_event_handler_delay delay,
++		void *data)
++{
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
++
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
++	}
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
++	}
++
++	spin_lock_irqsave(&ec->events.lock, flags);
++
++	// 0 is not a valid event RQID
++	ec->events.handler[rqid - 1].handler = fn;
++	ec->events.handler[rqid - 1].delay = delay;
++	ec->events.handler[rqid - 1].data = data;
++
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++	surface_sam_ssh_release(ec);
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_set_delayed_event_handler);
++
++int surface_sam_ssh_remove_event_handler(u16 rqid)
++{
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
++
++	if (!sam_rqid_is_event(rqid)) {
++		return -EINVAL;
++	}
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return -ENXIO;
++	}
++
++	spin_lock_irqsave(&ec->events.lock, flags);
++
++	// 0 is not a valid event RQID
++	ec->events.handler[rqid - 1].handler = NULL;
++	ec->events.handler[rqid - 1].delay = NULL;
++	ec->events.handler[rqid - 1].data = NULL;
++
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++	surface_sam_ssh_release(ec);
++
++	/*
++	 * Make sure that the handler is not in use any more after we've
++	 * removed it.
++	 */
++	flush_workqueue(ec->events.queue_evt);
++
++	return 0;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_remove_event_handler);
++
++
++inline static u16 ssh_crc(const u8 *buf, size_t size)
++{
++	return crc_ccitt_false(0xffff, buf, size);
++}
++
++inline static void ssh_write_u16(struct ssh_writer *writer, u16 in)
++{
++	put_unaligned_le16(in, writer->ptr);
++	writer->ptr += 2;
++}
++
++inline static void ssh_write_crc(struct ssh_writer *writer,
++				 const u8 *buf, size_t size)
++{
++	ssh_write_u16(writer, ssh_crc(buf, size));
++}
++
++inline static void ssh_write_syn(struct ssh_writer *writer)
++{
++	u8 *w = writer->ptr;
++
++	*w++ = 0xaa;
++	*w++ = 0x55;
++
++	writer->ptr = w;
++}
++
++inline static void ssh_write_ter(struct ssh_writer *writer)
++{
++	u8 *w = writer->ptr;
++
++	*w++ = 0xff;
++	*w++ = 0xff;
++
++	writer->ptr = w;
++}
++
++inline static void ssh_write_buf(struct ssh_writer *writer,
++				 u8 *in, size_t len)
++{
++	writer->ptr = memcpy(writer->ptr, in, len) + len;
++}
++
++inline static void ssh_write_hdr(struct ssh_writer *writer,
++				 const struct surface_sam_ssh_rqst *rqst,
++				 struct sam_ssh_ec *ec)
++{
++	struct ssh_frame_ctrl *hdr = (struct ssh_frame_ctrl *)writer->ptr;
++	u8 *begin = writer->ptr;
++
++	hdr->type = SSH_FRAME_TYPE_CMD;
++	hdr->len  = SSH_BYTELEN_CMDFRAME + rqst->cdl;	// without CRC
++	hdr->pad  = 0x00;
++	hdr->seq  = ec->counter.seq;
++
++	writer->ptr += sizeof(*hdr);
++
++	ssh_write_crc(writer, begin, writer->ptr - begin);
++}
++
++inline static void ssh_write_cmd(struct ssh_writer *writer,
++				 const struct surface_sam_ssh_rqst *rqst,
++				 struct sam_ssh_ec *ec)
++{
++	struct ssh_frame_cmd *cmd = (struct ssh_frame_cmd *)writer->ptr;
++	u8 *begin = writer->ptr;
++
++	u16 rqid = sam_rqid_to_rqst(ec->counter.rqid);
++	u8 rqid_lo = rqid & 0xFF;
++	u8 rqid_hi = rqid >> 8;
++
++	cmd->type     = SSH_FRAME_TYPE_CMD;
++	cmd->tc       = rqst->tc;
++	cmd->outgoing = 0x01;
++	cmd->incoming = 0x00;
++	cmd->iid      = rqst->iid;
++	cmd->rqid_lo  = rqid_lo;
++	cmd->rqid_hi  = rqid_hi;
++	cmd->cid      = rqst->cid;
++
++	writer->ptr += sizeof(*cmd);
++
++	ssh_write_buf(writer, rqst->pld, rqst->cdl);
++	ssh_write_crc(writer, begin, writer->ptr - begin);
++}
++
++inline static void ssh_write_ack(struct ssh_writer *writer, u8 seq)
++{
++	struct ssh_frame_ctrl *ack = (struct ssh_frame_ctrl *)writer->ptr;
++	u8 *begin = writer->ptr;
++
++	ack->type = SSH_FRAME_TYPE_ACK;
++	ack->len  = 0x00;
++	ack->pad  = 0x00;
++	ack->seq  = seq;
++
++	writer->ptr += sizeof(*ack);
++
++	ssh_write_crc(writer, begin, writer->ptr - begin);
++}
++
++inline static void ssh_writer_reset(struct ssh_writer *writer)
++{
++	writer->ptr = writer->data;
++}
++
++inline static int ssh_writer_flush(struct sam_ssh_ec *ec)
++{
++	struct ssh_writer *writer = &ec->writer;
++	struct serdev_device *serdev = ec->serdev;
++	int status;
++
++	size_t len = writer->ptr - writer->data;
++
++	dev_dbg(&ec->serdev->dev, "sending message\n");
++	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
++	                     writer->data, writer->ptr - writer->data, false);
++
++	status = serdev_device_write(serdev, writer->data, len, SSH_WRITE_TIMEOUT);
++	return status >= 0 ? 0 : status;
++}
++
++inline static void ssh_write_msg_cmd(struct sam_ssh_ec *ec,
++				     const struct surface_sam_ssh_rqst *rqst)
++{
++	ssh_writer_reset(&ec->writer);
++	ssh_write_syn(&ec->writer);
++	ssh_write_hdr(&ec->writer, rqst, ec);
++	ssh_write_cmd(&ec->writer, rqst, ec);
++}
++
++inline static void ssh_write_msg_ack(struct sam_ssh_ec *ec, u8 seq)
++{
++	ssh_writer_reset(&ec->writer);
++	ssh_write_syn(&ec->writer);
++	ssh_write_ack(&ec->writer, seq);
++	ssh_write_ter(&ec->writer);
++}
++
++inline static void ssh_receiver_restart(struct sam_ssh_ec *ec,
++					const struct surface_sam_ssh_rqst *rqst)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	reinit_completion(&ec->receiver.signal);
++	ec->receiver.state = SSH_RCV_CONTROL;
++	ec->receiver.expect.pld = rqst->snc;
++	ec->receiver.expect.seq = ec->counter.seq;
++	ec->receiver.expect.rqid = sam_rqid_to_rqst(ec->counter.rqid);
++	ec->receiver.eval_buf.len = 0;
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
++}
++
++inline static void ssh_receiver_discard(struct sam_ssh_ec *ec)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	ec->receiver.state = SSH_RCV_DISCARD;
++	ec->receiver.eval_buf.len = 0;
++	kfifo_reset(&ec->receiver.fifo);
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
++}
++
++static int surface_sam_ssh_rqst_unlocked(struct sam_ssh_ec *ec,
++					 const struct surface_sam_ssh_rqst *rqst,
++					 struct surface_sam_ssh_buf *result)
++{
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_fifo_packet packet = {};
++	int status;
++	int try;
++	unsigned int rem;
++
++	if (rqst->cdl > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD) {
++		dev_err(dev, SSH_RQST_TAG "request payload too large\n");
++		return -EINVAL;
++	}
++
++	// write command in buffer, we may need it multiple times
++	ssh_write_msg_cmd(ec, rqst);
++	ssh_receiver_restart(ec, rqst);
++
++	// send command, try to get an ack response
++	for (try = 0; try < SSH_NUM_RETRY; try++) {
++		status = ssh_writer_flush(ec);
++		if (status) {
++			goto out;
++		}
++
++		rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT);
++		if (rem) {
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
++
++			if (packet.type == SSH_FRAME_TYPE_ACK) {
++				break;
++			}
++		}
++	}
++
++	// check if we ran out of tries?
++	if (try >= SSH_NUM_RETRY) {
++		dev_err(dev, SSH_RQST_TAG "communication failed %d times, giving up\n", try);
++		status = -EIO;
++		goto out;
++	}
++
++	ec->counter.seq  += 1;
++	ec->counter.rqid += 1;
++
++	// get command response/payload
++	if (rqst->snc && result) {
++		rem = wait_for_completion_timeout(&ec->receiver.signal, SSH_READ_TIMEOUT);
++		if (rem) {
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, &packet, sizeof(packet));
++
++			if (result->cap < packet.len) {
++				status = -EINVAL;
++				goto out;
++			}
++
++			// completion assures valid packet, thus ignore returned length
++			(void) !kfifo_out(&ec->receiver.fifo, result->data, packet.len);
++			result->len = packet.len;
++		} else {
++			dev_err(dev, SSH_RQST_TAG "communication timed out\n");
++			status = -EIO;
++			goto out;
++		}
++
++		// send ACK
++		ssh_write_msg_ack(ec, packet.seq);
++		status = ssh_writer_flush(ec);
++		if (status) {
++			goto out;
++		}
++	}
++
++out:
++	ssh_receiver_discard(ec);
++	return status;
++}
++
++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result)
++{
++	struct sam_ssh_ec *ec;
++	int status;
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		printk(KERN_WARNING SSH_RQST_TAG_FULL "embedded controller is uninitialized\n");
++		return -ENXIO;
++	}
++
++	if (ec->state == SSH_EC_SUSPENDED) {
++		dev_warn(&ec->serdev->dev, SSH_RQST_TAG "embedded controller is suspended\n");
++
++		surface_sam_ssh_release(ec);
++		return -EPERM;
++	}
++
++	status = surface_sam_ssh_rqst_unlocked(ec, rqst, result);
++
++	surface_sam_ssh_release(ec);
++	return status;
++}
++EXPORT_SYMBOL_GPL(surface_sam_ssh_rqst);
++
++
++static int surface_sam_ssh_ec_resume(struct sam_ssh_ec *ec)
++{
++	u8 buf[1] = { 0x00 };
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x16,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
++
++	int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while trying to resume EC: 0x%02x\n",
++			 buf[0]);
++	}
++
++	return 0;
++}
++
++static int surface_sam_ssh_ec_suspend(struct sam_ssh_ec *ec)
++{
++	u8 buf[1] = { 0x00 };
++
++	struct surface_sam_ssh_rqst rqst = {
++		.tc  = 0x01,
++		.iid = 0x00,
++		.cid = 0x15,
++		.snc = 0x01,
++		.cdl = 0x00,
++		.pld = NULL,
++	};
++
++	struct surface_sam_ssh_buf result = {
++		result.cap = ARRAY_SIZE(buf),
++		result.len = 0,
++		result.data = buf,
++	};
++
++	int status = surface_sam_ssh_rqst_unlocked(ec, &rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	if (buf[0] != 0x00) {
++		dev_warn(&ec->serdev->dev,
++		         "unexpected result while trying to suspend EC: 0x%02x\n",
++			 buf[0]);
++	}
++
++	return 0;
++}
++
++
++inline static bool ssh_is_valid_syn(const u8 *ptr)
++{
++	return ptr[0] == 0xaa && ptr[1] == 0x55;
++}
++
++inline static bool ssh_is_valid_ter(const u8 *ptr)
++{
++	return ptr[0] == 0xff && ptr[1] == 0xff;
++}
++
++inline static bool ssh_is_valid_crc(const u8 *begin, const u8 *end)
++{
++	u16 crc = ssh_crc(begin, end - begin);
++	return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8));
++}
++
++
++static int surface_sam_ssh_send_ack(struct sam_ssh_ec *ec, u8 seq)
++{
++	int status;
++	u8 buf[SSH_MSG_LEN_CTRL];
++	u16 crc;
++
++	buf[0] = 0xaa;
++	buf[1] = 0x55;
++	buf[2] = 0x40;
++	buf[3] = 0x00;
++	buf[4] = 0x00;
++	buf[5] = seq;
++
++	crc = ssh_crc(buf + SSH_FRAME_OFFS_CTRL, SSH_BYTELEN_CTRL);
++	buf[6] = crc & 0xff;
++	buf[7] = crc >> 8;
++
++	buf[8] = 0xff;
++	buf[9] = 0xff;
++
++	dev_dbg(&ec->serdev->dev, "sending message\n");
++	print_hex_dump_debug("send: ", DUMP_PREFIX_OFFSET, 16, 1,
++	                     buf, SSH_MSG_LEN_CTRL, false);
++
++	status = serdev_device_write(ec->serdev, buf, SSH_MSG_LEN_CTRL, SSH_WRITE_TIMEOUT);
++	return status >= 0 ? 0 : status;
++}
++
++static void surface_sam_ssh_event_work_ack_handler(struct work_struct *_work)
++{
++	struct surface_sam_ssh_event *event;
++	struct ssh_event_work *work;
++	struct sam_ssh_ec *ec;
++	struct device *dev;
++	int status;
++
++	work = container_of(_work, struct ssh_event_work, work_ack);
++	event = &work->event;
++	ec = work->ec;
++	dev = &ec->serdev->dev;
++
++	// make sure we load a fresh ec state
++	smp_mb();
++
++	if (ec->state == SSH_EC_INITIALIZED) {
++		status = surface_sam_ssh_send_ack(ec, work->seq);
++		if (status) {
++			dev_err(dev, SSH_EVENT_TAG "failed to send ACK: %d\n", status);
++		}
++	}
++
++	if (refcount_dec_and_test(&work->refcount)) {
++		kfree(work);
++	}
++}
++
++static void surface_sam_ssh_event_work_evt_handler(struct work_struct *_work)
++{
++	struct delayed_work *dwork = (struct delayed_work *)_work;
++	struct ssh_event_work *work;
++	struct surface_sam_ssh_event *event;
++	struct sam_ssh_ec *ec;
++	struct device *dev;
++	unsigned long flags;
++
++	surface_sam_ssh_event_handler_fn handler;
++	void *handler_data;
++
++	int status = 0;
++
++	work = container_of(dwork, struct ssh_event_work, work_evt);
++	event = &work->event;
++	ec = work->ec;
++	dev = &ec->serdev->dev;
++
++	spin_lock_irqsave(&ec->events.lock, flags);
++	handler       = ec->events.handler[event->rqid - 1].handler;
++	handler_data  = ec->events.handler[event->rqid - 1].data;
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++
++	/*
++	 * During handler removal or driver release, we ensure every event gets
++	 * handled before return of that function. Thus a handler obtained here is
++	 * guaranteed to be valid at least until this function returns.
++	 */
++
++	if (handler) {
++		status = handler(event, handler_data);
++	} else {
++		dev_warn(dev, SSH_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid);
++	}
++
++	if (status) {
++		dev_err(dev, SSH_EVENT_TAG "error handling event: %d\n", status);
++	}
++
++	if (refcount_dec_and_test(&work->refcount)) {
++		kfree(work);
++	}
++}
++
++static void ssh_handle_event(struct sam_ssh_ec *ec, const u8 *buf)
++{
++	struct device *dev = &ec->serdev->dev;
++	const struct ssh_frame_ctrl *ctrl;
++	const struct ssh_frame_cmd *cmd;
++	struct ssh_event_work *work;
++	unsigned long flags;
++	u16 pld_len;
++
++	surface_sam_ssh_event_handler_delay delay_fn;
++	void *handler_data;
++	unsigned long delay = 0;
++
++	ctrl = (const struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL);
++	cmd  = (const struct ssh_frame_cmd  *)(buf + SSH_FRAME_OFFS_CMD);
++
++	pld_len = ctrl->len - SSH_BYTELEN_CMDFRAME;
++
++	work = kzalloc(sizeof(struct ssh_event_work) + pld_len, GFP_ATOMIC);
++	if (!work) {
++		dev_warn(dev, SSH_EVENT_TAG "failed to allocate memory, dropping event\n");
++		return;
++	}
++
++	refcount_set(&work->refcount, 2);
++	work->ec         = ec;
++	work->seq        = ctrl->seq;
++	work->event.rqid = (cmd->rqid_hi << 8) | cmd->rqid_lo;
++	work->event.tc   = cmd->tc;
++	work->event.iid  = cmd->iid;
++	work->event.cid  = cmd->cid;
++	work->event.len  = pld_len;
++	work->event.pld  = ((u8*) work) + sizeof(struct ssh_event_work);
++
++	memcpy(work->event.pld, buf + SSH_FRAME_OFFS_CMD_PLD, pld_len);
++
++	INIT_WORK(&work->work_ack, surface_sam_ssh_event_work_ack_handler);
++	queue_work(ec->events.queue_ack, &work->work_ack);
++
++	spin_lock_irqsave(&ec->events.lock, flags);
++	handler_data = ec->events.handler[work->event.rqid - 1].data;
++	delay_fn     = ec->events.handler[work->event.rqid - 1].delay;
++	if (delay_fn) {
++		delay = delay_fn(&work->event, handler_data);
++	}
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++
++	// immediate execution for high priority events (e.g. keyboard)
++	if (delay == SURFACE_SAM_SSH_EVENT_IMMEDIATE) {
++		surface_sam_ssh_event_work_evt_handler(&work->work_evt.work);
++	} else {
++		INIT_DELAYED_WORK(&work->work_evt, surface_sam_ssh_event_work_evt_handler);
++		queue_delayed_work(ec->events.queue_evt, &work->work_evt, delay);
++	}
++}
++
++static int ssh_receive_msg_ctrl(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
++{
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_receiver *rcv = &ec->receiver;
++	const struct ssh_frame_ctrl *ctrl;
++	struct ssh_fifo_packet packet;
++
++	const u8 *ctrl_begin = buf + SSH_FRAME_OFFS_CTRL;
++	const u8 *ctrl_end   = buf + SSH_FRAME_OFFS_CTRL_CRC;
++
++	ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin);
++
++	// actual length check
++	if (size < SSH_MSG_LEN_CTRL) {
++		return 0;			// need more bytes
++	}
++
++	// validate TERM
++	if (!ssh_is_valid_ter(buf + SSH_FRAME_OFFS_TERM)) {
++		dev_err(dev, SSH_RECV_TAG "invalid end of message\n");
++		return size;			// discard everything
++	}
++
++	// validate CRC
++	if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (ctrl)\n");
++		return SSH_MSG_LEN_CTRL;	// only discard message
++	}
++
++	// check if we expect the message
++	if (rcv->state != SSH_RCV_CONTROL) {
++		dev_err(dev, SSH_RECV_TAG "discarding message: ctrl not expected\n");
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
++
++	// check if it is for our request
++	if (ctrl->type == SSH_FRAME_TYPE_ACK && ctrl->seq != rcv->expect.seq) {
++		dev_err(dev, SSH_RECV_TAG "discarding message: ack does not match\n");
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
++
++	// we now have a valid & expected ACK/RETRY message
++	dev_dbg(dev, SSH_RECV_TAG "valid control message received (type: 0x%02x)\n", ctrl->type);
++
++	packet.type = ctrl->type;
++	packet.seq  = ctrl->seq;
++	packet.len  = 0;
++
++	if (kfifo_avail(&rcv->fifo) >= sizeof(packet)) {
++		kfifo_in(&rcv->fifo, (u8 *) &packet, sizeof(packet));
++
++	} else {
++		dev_warn(dev, SSH_RECV_TAG
++			 "dropping frame: not enough space in fifo (type = %d)\n",
++			 SSH_FRAME_TYPE_CMD);
++
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
++
++	// update decoder state
++	if (ctrl->type == SSH_FRAME_TYPE_ACK) {
++		rcv->state = rcv->expect.pld
++			? SSH_RCV_COMMAND
++			: SSH_RCV_DISCARD;
++	}
++
++	complete(&rcv->signal);
++	return SSH_MSG_LEN_CTRL;		// handled message
++}
++
++static int ssh_receive_msg_cmd(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
++{
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_receiver *rcv = &ec->receiver;
++	const struct ssh_frame_ctrl *ctrl;
++	const struct ssh_frame_cmd *cmd;
++	struct ssh_fifo_packet packet;
++
++	const u8 *ctrl_begin     = buf + SSH_FRAME_OFFS_CTRL;
++	const u8 *ctrl_end       = buf + SSH_FRAME_OFFS_CTRL_CRC;
++	const u8 *cmd_begin      = buf + SSH_FRAME_OFFS_CMD;
++	const u8 *cmd_begin_pld  = buf + SSH_FRAME_OFFS_CMD_PLD;
++	const u8 *cmd_end;
++
++	size_t msg_len;
++
++	ctrl = (const struct ssh_frame_ctrl *)(ctrl_begin);
++	cmd  = (const struct ssh_frame_cmd  *)(cmd_begin);
++
++	// we need at least a full control frame
++	if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL + SSH_BYTELEN_CRC)) {
++		return 0;		// need more bytes
++	}
++
++	// validate control-frame CRC
++	if (!ssh_is_valid_crc(ctrl_begin, ctrl_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-ctrl)\n");
++		/*
++		 * We can't be sure here if length is valid, thus
++		 * discard everything.
++		 */
++		return size;
++	}
++
++	// actual length check (ctrl->len contains command-frame but not crc)
++	msg_len = SSH_MSG_LEN_CMD_BASE + ctrl->len;
++	if (size < msg_len) {
++		return 0;			// need more bytes
++	}
++
++	cmd_end = cmd_begin + ctrl->len;
++
++	// validate command-frame type
++	if (cmd->type != SSH_FRAME_TYPE_CMD) {
++		dev_err(dev, SSH_RECV_TAG "expected command frame type but got 0x%02x\n", cmd->type);
++		return size;			// discard everything
++	}
++
++	// validate command-frame CRC
++	if (!ssh_is_valid_crc(cmd_begin, cmd_end)) {
++		dev_err(dev, SSH_RECV_TAG "invalid checksum (cmd-pld)\n");
++
++		/*
++		 * The message length is provided in the control frame. As we
++		 * already validated that, we can be sure here that it's
++		 * correct, so we only need to discard the message.
++		 */
++		return msg_len;
++	}
++
++	// check if we received an event notification
++	if (sam_rqid_is_event((cmd->rqid_hi << 8) | cmd->rqid_lo)) {
++		ssh_handle_event(ec, buf);
++		return msg_len;			// handled message
++	}
++
++	// check if we expect the message
++	if (rcv->state != SSH_RCV_COMMAND) {
++		dev_dbg(dev, SSH_RECV_TAG "discarding message: command not expected\n");
++		return msg_len;			// discard message
++	}
++
++	// check if response is for our request
++	if (rcv->expect.rqid != (cmd->rqid_lo | (cmd->rqid_hi << 8))) {
++		dev_dbg(dev, SSH_RECV_TAG "discarding message: command not a match\n");
++		return msg_len;			// discard message
++	}
++
++	// we now have a valid & expected command message
++	dev_dbg(dev, SSH_RECV_TAG "valid command message received\n");
++
++	packet.type = ctrl->type;
++	packet.seq = ctrl->seq;
++	packet.len = cmd_end - cmd_begin_pld;
++
++	if (kfifo_avail(&rcv->fifo) >= sizeof(packet) + packet.len) {
++		kfifo_in(&rcv->fifo, &packet, sizeof(packet));
++		kfifo_in(&rcv->fifo, cmd_begin_pld, packet.len);
++
++	} else {
++		dev_warn(dev, SSH_RECV_TAG
++			 "dropping frame: not enough space in fifo (type = %d)\n",
++			 SSH_FRAME_TYPE_CMD);
++
++		return SSH_MSG_LEN_CTRL;	// discard message
++	}
++
++	rcv->state = SSH_RCV_DISCARD;
++
++	complete(&rcv->signal);
++	return msg_len;				// handled message
++}
++
++static int ssh_eval_buf(struct sam_ssh_ec *ec, const u8 *buf, size_t size)
++{
++	struct device *dev = &ec->serdev->dev;
++	struct ssh_frame_ctrl *ctrl;
++
++	// we need at least a control frame to check what to do
++	if (size < (SSH_BYTELEN_SYNC + SSH_BYTELEN_CTRL)) {
++		return 0;		// need more bytes
++	}
++
++	// make sure we're actually at the start of a new message
++	if (!ssh_is_valid_syn(buf)) {
++		dev_err(dev, SSH_RECV_TAG "invalid start of message\n");
++		return size;		// discard everything
++	}
++
++	// handle individual message types seperately
++	ctrl = (struct ssh_frame_ctrl *)(buf + SSH_FRAME_OFFS_CTRL);
++
++	switch (ctrl->type) {
++	case SSH_FRAME_TYPE_ACK:
++	case SSH_FRAME_TYPE_RETRY:
++		return ssh_receive_msg_ctrl(ec, buf, size);
++
++	case SSH_FRAME_TYPE_CMD:
++		return ssh_receive_msg_cmd(ec, buf, size);
++
++	default:
++		dev_err(dev, SSH_RECV_TAG "unknown frame type 0x%02x\n", ctrl->type);
++		return size;		// discard everything
++	}
++}
++
++static int ssh_receive_buf(struct serdev_device *serdev,
++			   const unsigned char *buf, size_t size)
++{
++	struct sam_ssh_ec *ec = serdev_device_get_drvdata(serdev);
++	struct ssh_receiver *rcv = &ec->receiver;
++	unsigned long flags;
++	int offs = 0;
++	int used, n;
++
++	dev_dbg(&serdev->dev, SSH_RECV_TAG "received buffer (size: %zu)\n", size);
++	print_hex_dump_debug(SSH_RECV_TAG, DUMP_PREFIX_OFFSET, 16, 1, buf, size, false);
++
++	/*
++	 * The battery _BIX message gets a bit long, thus we have to add some
++	 * additional buffering here.
++	 */
++
++	spin_lock_irqsave(&rcv->lock, flags);
++
++	// copy to eval-buffer
++	used = min(size, (size_t)(rcv->eval_buf.cap - rcv->eval_buf.len));
++	memcpy(rcv->eval_buf.ptr + rcv->eval_buf.len, buf, used);
++	rcv->eval_buf.len += used;
++
++	// evaluate buffer until we need more bytes or eval-buf is empty
++	while (offs < rcv->eval_buf.len) {
++		n = rcv->eval_buf.len - offs;
++		n = ssh_eval_buf(ec, rcv->eval_buf.ptr + offs, n);
++		if (n <= 0) break;	// need more bytes
++
++		offs += n;
++	}
++
++	// throw away the evaluated parts
++	rcv->eval_buf.len -= offs;
++	memmove(rcv->eval_buf.ptr, rcv->eval_buf.ptr + offs, rcv->eval_buf.len);
++
++	spin_unlock_irqrestore(&rcv->lock, flags);
++
++	return used;
++}
++
++
++#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE
++
++#include <linux/sysfs.h>
++
++static char sam_ssh_debug_rqst_buf_sysfs[SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1] = { 0 };
++static char sam_ssh_debug_rqst_buf_pld[SURFACE_SAM_SSH_MAX_RQST_PAYLOAD] = { 0 };
++static char sam_ssh_debug_rqst_buf_res[SURFACE_SAM_SSH_MAX_RQST_RESPONSE] = { 0 };
++
++
++static ssize_t rqst_read(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
++                         char *buf, loff_t offs, size_t count)
++{
++	if (offs < 0 || count + offs > SURFACE_SAM_SSH_MAX_RQST_RESPONSE) {
++		return -EINVAL;
++	}
++
++	memcpy(buf, sam_ssh_debug_rqst_buf_sysfs + offs, count);
++	return count;
++}
++
++static ssize_t rqst_write(struct file *f, struct kobject *kobj, struct bin_attribute *attr,
++			  char *buf, loff_t offs, size_t count)
++{
++	struct surface_sam_ssh_rqst rqst = {};
++	struct surface_sam_ssh_buf result = {};
++	int status;
++
++	// check basic write constriants
++	if (offs != 0 || count > SURFACE_SAM_SSH_MAX_RQST_PAYLOAD + 5) {
++		return -EINVAL;
++	}
++
++	// payload length should be consistent with data provided
++	if (buf[4] + 5 != count) {
++		return -EINVAL;
++	}
++
++	rqst.tc  = buf[0];
++	rqst.iid = buf[1];
++	rqst.cid = buf[2];
++	rqst.snc = buf[3];
++	rqst.cdl = buf[4];
++	rqst.pld = sam_ssh_debug_rqst_buf_pld;
++	memcpy(sam_ssh_debug_rqst_buf_pld, buf + 5, count - 5);
++
++	result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE;
++	result.len = 0;
++	result.data = sam_ssh_debug_rqst_buf_res;
++
++	status = surface_sam_ssh_rqst(&rqst, &result);
++	if (status) {
++		return status;
++	}
++
++	sam_ssh_debug_rqst_buf_sysfs[0] = result.len;
++	memcpy(sam_ssh_debug_rqst_buf_sysfs + 1, result.data, result.len);
++	memset(sam_ssh_debug_rqst_buf_sysfs + result.len + 1, 0,
++	       SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1 - result.len);
++
++	return count;
++}
++
++static const BIN_ATTR_RW(rqst, SURFACE_SAM_SSH_MAX_RQST_RESPONSE + 1);
++
++
++int surface_sam_ssh_sysfs_register(struct device *dev)
++{
++	return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst);
++}
++
++void surface_sam_ssh_sysfs_unregister(struct device *dev)
++{
++	sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst);
++}
++
++#else	/* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */
++
++int surface_sam_ssh_sysfs_register(struct device *dev)
++{
++	return 0;
++}
++
++void surface_sam_ssh_sysfs_unregister(struct device *dev)
++{
++}
++
++#endif	/* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */
++
++
++static acpi_status
++ssh_setup_from_resource(struct acpi_resource *resource, void *context)
++{
++	struct serdev_device *serdev = context;
++	struct acpi_resource_common_serialbus *serial;
++	struct acpi_resource_uart_serialbus *uart;
++	int status = 0;
++
++	if (resource->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) {
++		return AE_OK;
++	}
++
++	serial = &resource->data.common_serial_bus;
++	if (serial->type != ACPI_RESOURCE_SERIAL_TYPE_UART) {
++		return AE_OK;
++	}
++
++	uart = &resource->data.uart_serial_bus;
++
++	// set up serdev device
++	serdev_device_set_baudrate(serdev, uart->default_baud_rate);
++
++	// serdev currently only supports RTSCTS flow control
++	if (uart->flow_control & SSH_SUPPORTED_FLOW_CONTROL_MASK) {
++		dev_warn(&serdev->dev, "unsupported flow control (value: 0x%02x)\n", uart->flow_control);
++	}
++
++	// set RTSCTS flow control
++	serdev_device_set_flow_control(serdev, uart->flow_control & ACPI_UART_FLOW_CONTROL_HW);
++
++	// serdev currently only supports EVEN/ODD parity
++	switch (uart->parity) {
++	case ACPI_UART_PARITY_NONE:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_NONE);
++		break;
++	case ACPI_UART_PARITY_EVEN:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_EVEN);
++		break;
++	case ACPI_UART_PARITY_ODD:
++		status = serdev_device_set_parity(serdev, SERDEV_PARITY_ODD);
++		break;
++	default:
++		dev_warn(&serdev->dev, "unsupported parity (value: 0x%02x)\n", uart->parity);
++		break;
++	}
++
++	if (status) {
++		dev_err(&serdev->dev, "failed to set parity (value: 0x%02x)\n", uart->parity);
++		return status;
++	}
++
++	return AE_CTRL_TERMINATE;       // we've found the resource and are done
++}
++
++
++static bool ssh_idma_filter(struct dma_chan *chan, void *param)
++{
++	// see dw8250_idma_filter
++	return param == chan->device->dev;
++}
++
++static int ssh_check_dma(struct serdev_device *serdev)
++{
++	struct device *dev = serdev->ctrl->dev.parent;
++	struct dma_chan *rx, *tx;
++	dma_cap_mask_t mask;
++	int status = 0;
++
++	/*
++	 * The EC UART requires DMA for proper communication. If we don't use DMA,
++	 * we'll drop bytes when the system has high load, e.g. during boot. This
++	 * causes some ugly behaviour, i.e. battery information (_BIX) messages
++	 * failing frequently. We're making sure the required DMA channels are
++	 * available here so serial8250_do_startup is able to grab them later
++	 * instead of silently falling back to a non-DMA approach.
++	 */
++
++	dma_cap_zero(mask);
++	dma_cap_set(DMA_SLAVE, mask);
++
++	rx = dma_request_slave_channel_compat(mask, ssh_idma_filter, dev->parent, dev, "rx");
++	if (IS_ERR_OR_NULL(rx)) {
++		status = rx ? PTR_ERR(rx) : -EPROBE_DEFER;
++		goto out;
++	}
++
++	tx = dma_request_slave_channel_compat(mask, ssh_idma_filter, dev->parent, dev, "tx");
++	if (IS_ERR_OR_NULL(tx)) {
++		status = tx ? PTR_ERR(tx) : -EPROBE_DEFER;
++		goto release_rx;
++	}
++
++	dma_release_channel(tx);
++release_rx:
++	dma_release_channel(rx);
++out:
++	return status;
++}
++
++
++static int surface_sam_ssh_suspend(struct device *dev)
++{
++	struct sam_ssh_ec *ec;
++	int status = 0;
++
++	dev_dbg(dev, "suspending\n");
++
++	ec = surface_sam_ssh_acquire_init();
++	if (ec) {
++		status = surface_sam_ssh_ec_suspend(ec);
++		if (status) {
++			dev_err(dev, "failed to suspend EC: %d\n", status);
++		}
++
++		ec->state = SSH_EC_SUSPENDED;
++		surface_sam_ssh_release(ec);
++	}
++
++	return status;
++}
++
++static int surface_sam_ssh_resume(struct device *dev)
++{
++	struct sam_ssh_ec *ec;
++	int status = 0;
++
++	dev_dbg(dev, "resuming\n");
++
++	ec = surface_sam_ssh_acquire_init();
++	if (ec) {
++		ec->state = SSH_EC_INITIALIZED;
++
++		status = surface_sam_ssh_ec_resume(ec);
++		if (status) {
++			dev_err(dev, "failed to resume EC: %d\n", status);
++		}
++
++		surface_sam_ssh_release(ec);
++	}
++
++	return status;
++}
++
++static SIMPLE_DEV_PM_OPS(surface_sam_ssh_pm_ops, surface_sam_ssh_suspend, surface_sam_ssh_resume);
++
++
++static const struct serdev_device_ops ssh_device_ops = {
++	.receive_buf  = ssh_receive_buf,
++	.write_wakeup = serdev_device_write_wakeup,
++};
++
++
++int surface_sam_ssh_sysfs_register(struct device *dev);
++void surface_sam_ssh_sysfs_unregister(struct device *dev);
++
++static int surface_sam_ssh_probe(struct serdev_device *serdev)
++{
++	struct sam_ssh_ec *ec;
++	struct workqueue_struct *event_queue_ack;
++	struct workqueue_struct *event_queue_evt;
++	u8 *write_buf;
++	u8 *read_buf;
++	u8 *eval_buf;
++	acpi_handle *ssh = ACPI_HANDLE(&serdev->dev);
++	acpi_status status;
++
++	dev_dbg(&serdev->dev, "probing\n");
++
++	// ensure DMA is ready before we set up the device
++	status = ssh_check_dma(serdev);
++	if (status) {
++		return status;
++	}
++
++	// allocate buffers
++	write_buf = kzalloc(SSH_WRITE_BUF_LEN, GFP_KERNEL);
++	if (!write_buf) {
++		status = -ENOMEM;
++		goto err_write_buf;
++	}
++
++	read_buf = kzalloc(SSH_READ_BUF_LEN, GFP_KERNEL);
++	if (!read_buf) {
++		status = -ENOMEM;
++		goto err_read_buf;
++	}
++
++	eval_buf = kzalloc(SSH_EVAL_BUF_LEN, GFP_KERNEL);
++	if (!eval_buf) {
++		status = -ENOMEM;
++		goto err_eval_buf;
++	}
++
++	event_queue_ack = create_singlethread_workqueue("surface_sh_ackq");
++	if (!event_queue_ack) {
++		status = -ENOMEM;
++		goto err_ackq;
++	}
++
++	event_queue_evt = create_workqueue("surface_sh_evtq");
++	if (!event_queue_evt) {
++		status = -ENOMEM;
++		goto err_evtq;
++	}
++
++	// set up EC
++	ec = surface_sam_ssh_acquire();
++	if (ec->state != SSH_EC_UNINITIALIZED) {
++		dev_err(&serdev->dev, "embedded controller already initialized\n");
++		surface_sam_ssh_release(ec);
++
++		status = -EBUSY;
++		goto err_busy;
++	}
++
++	ec->serdev      = serdev;
++	ec->writer.data = write_buf;
++	ec->writer.ptr  = write_buf;
++
++	// initialize receiver
++	init_completion(&ec->receiver.signal);
++	kfifo_init(&ec->receiver.fifo, read_buf, SSH_READ_BUF_LEN);
++	ec->receiver.eval_buf.ptr = eval_buf;
++	ec->receiver.eval_buf.cap = SSH_EVAL_BUF_LEN;
++	ec->receiver.eval_buf.len = 0;
++
++	// initialize event handling
++	ec->events.queue_ack = event_queue_ack;
++	ec->events.queue_evt = event_queue_evt;
++
++	ec->state = SSH_EC_INITIALIZED;
++
++	serdev_device_set_drvdata(serdev, ec);
++
++	// ensure everything is properly set-up before we open the device
++	smp_mb();
++
++	serdev_device_set_client_ops(serdev, &ssh_device_ops);
++	status = serdev_device_open(serdev);
++	if (status) {
++		goto err_open;
++	}
++
++	status = acpi_walk_resources(ssh, METHOD_NAME__CRS,
++	                             ssh_setup_from_resource, serdev);
++	if (ACPI_FAILURE(status)) {
++		goto err_devinit;
++	}
++
++	status = surface_sam_ssh_ec_resume(ec);
++	if (status) {
++		goto err_devinit;
++	}
++
++	status = surface_sam_ssh_sysfs_register(&serdev->dev);
++	if (status) {
++		goto err_devinit;
++	}
++
++	surface_sam_ssh_release(ec);
++
++	acpi_walk_dep_device_list(ssh);
++
++	return 0;
++
++err_devinit:
++	serdev_device_close(serdev);
++err_open:
++	ec->state = SSH_EC_UNINITIALIZED;
++	serdev_device_set_drvdata(serdev, NULL);
++	surface_sam_ssh_release(ec);
++err_busy:
++	destroy_workqueue(event_queue_evt);
++err_evtq:
++	destroy_workqueue(event_queue_ack);
++err_ackq:
++	kfree(eval_buf);
++err_eval_buf:
++	kfree(read_buf);
++err_read_buf:
++	kfree(write_buf);
++err_write_buf:
++	return status;
++}
++
++static void surface_sam_ssh_remove(struct serdev_device *serdev)
++{
++	struct sam_ssh_ec *ec;
++	unsigned long flags;
++	int status;
++
++	ec = surface_sam_ssh_acquire_init();
++	if (!ec) {
++		return;
++	}
++
++	surface_sam_ssh_sysfs_unregister(&serdev->dev);
++
++	// suspend EC and disable events
++	status = surface_sam_ssh_ec_suspend(ec);
++	if (status) {
++		dev_err(&serdev->dev, "failed to suspend EC: %d\n", status);
++	}
++
++	// make sure all events (received up to now) have been properly handled
++	flush_workqueue(ec->events.queue_ack);
++	flush_workqueue(ec->events.queue_evt);
++
++	// remove event handlers
++	spin_lock_irqsave(&ec->events.lock, flags);
++	memset(ec->events.handler, 0,
++	       sizeof(struct ssh_event_handler)
++	        * SAM_NUM_EVENT_TYPES);
++	spin_unlock_irqrestore(&ec->events.lock, flags);
++
++	// set device to deinitialized state
++	ec->state  = SSH_EC_UNINITIALIZED;
++	ec->serdev = NULL;
++
++	// ensure state and serdev get set before continuing
++	smp_mb();
++
++	/*
++	 * Flush any event that has not been processed yet to ensure we're not going to
++	 * use the serial device any more (e.g. for ACKing).
++	 */
++	flush_workqueue(ec->events.queue_ack);
++	flush_workqueue(ec->events.queue_evt);
++
++	serdev_device_close(serdev);
++
++	/*
++         * Only at this point, no new events can be received. Destroying the
++         * workqueue here flushes all remaining events. Those events will be
++         * silently ignored and neither ACKed nor any handler gets called.
++	 */
++	destroy_workqueue(ec->events.queue_ack);
++	destroy_workqueue(ec->events.queue_evt);
++
++	// free writer
++	kfree(ec->writer.data);
++	ec->writer.data = NULL;
++	ec->writer.ptr  = NULL;
++
++	// free receiver
++	spin_lock_irqsave(&ec->receiver.lock, flags);
++	ec->receiver.state = SSH_RCV_DISCARD;
++	kfifo_free(&ec->receiver.fifo);
++
++	kfree(ec->receiver.eval_buf.ptr);
++	ec->receiver.eval_buf.ptr = NULL;
++	ec->receiver.eval_buf.cap = 0;
++	ec->receiver.eval_buf.len = 0;
++	spin_unlock_irqrestore(&ec->receiver.lock, flags);
++
++	serdev_device_set_drvdata(serdev, NULL);
++	surface_sam_ssh_release(ec);
++}
++
++
++static const struct acpi_device_id surface_sam_ssh_match[] = {
++	{ "MSHW0084", 0 },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_ssh_match);
++
++struct serdev_device_driver surface_sam_ssh = {
++	.probe = surface_sam_ssh_probe,
++	.remove = surface_sam_ssh_remove,
++	.driver = {
++		.name = "surface_sam_ssh",
++		.acpi_match_table = ACPI_PTR(surface_sam_ssh_match),
++		.pm = &surface_sam_ssh_pm_ops,
++	},
++};
++module_serdev_device_driver(surface_sam_ssh);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.h b/drivers/platform/x86/surface_sam/surface_sam_ssh.h
+new file mode 100644
+index 000000000000..89407ec2d4a4
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.h
+@@ -0,0 +1,91 @@
++/*
++ * Interface for Surface Serial Hub (SSH).
++ *
++ * The SSH is the main communication hub for communication between host and
++ * the Surface/System Aggregator Module (SAM) on newer Microsoft Surface
++ * devices (Book 2, Pro 5, Laptops, ...). Also referred to as SAM-over-SSH.
++ * Older devices (Book 1, Pro 4) use SAM-over-I2C.
++ */
++
++#ifndef _SURFACE_SAM_SSH_H
++#define _SURFACE_SAM_SSH_H
++
++#include <linux/types.h>
++#include <linux/device.h>
++
++
++/*
++ * Maximum request payload size in bytes.
++ * Value based on ACPI (255 bytes minus header/status bytes).
++ */
++#define SURFACE_SAM_SSH_MAX_RQST_PAYLOAD	(255 - 10)
++
++/*
++ * Maximum response payload size in bytes.
++ * Value based on ACPI (255 bytes minus header/status bytes).
++ */
++#define SURFACE_SAM_SSH_MAX_RQST_RESPONSE	(255 - 4)
++
++/*
++ * The number of (lower) bits of the request ID (RQID) reserved for events.
++ * These bits may only be used exclusively for events sent from the EC to the
++ * host.
++ */
++#define SURFACE_SAM_SSH_RQID_EVENT_BITS		5
++
++/*
++ * Special event-handler delay value indicating that the corresponding event
++ * should be handled immediately in the interrupt and not be relayed through
++ * the workqueue. Intended for low-latency events, such as keyboard events.
++ */
++#define SURFACE_SAM_SSH_EVENT_IMMEDIATE		((unsigned long) -1)
++
++
++struct surface_sam_ssh_buf {
++	u8 cap;
++	u8 len;
++	u8 *data;
++};
++
++struct surface_sam_ssh_rqst {
++	u8 tc;
++	u8 iid;
++	u8 cid;
++	u8 snc;
++	u8 cdl;
++	u8 *pld;
++};
++
++struct surface_sam_ssh_event {
++	u16 rqid;
++	u8  tc;
++	u8  iid;
++	u8  cid;
++	u8  len;
++	u8 *pld;
++};
++
++
++typedef int (*surface_sam_ssh_event_handler_fn)(struct surface_sam_ssh_event *event, void *data);
++typedef unsigned long (*surface_sam_ssh_event_handler_delay)(struct surface_sam_ssh_event *event, void *data);
++
++int surface_sam_ssh_consumer_register(struct device *consumer);
++
++int surface_sam_ssh_rqst(const struct surface_sam_ssh_rqst *rqst, struct surface_sam_ssh_buf *result);
++
++int surface_sam_ssh_enable_event_source(u8 tc, u8 unknown, u16 rqid);
++int surface_sam_ssh_disable_event_source(u8 tc, u8 unknown, u16 rqid);
++int surface_sam_ssh_remove_event_handler(u16 rqid);
++
++int surface_sam_ssh_set_delayed_event_handler(u16 rqid,
++		surface_sam_ssh_event_handler_fn fn,
++		surface_sam_ssh_event_handler_delay delay,
++		void *data);
++
++static inline int surface_sam_ssh_set_event_handler(u16 rqid, surface_sam_ssh_event_handler_fn fn, void *data)
++{
++	return surface_sam_ssh_set_delayed_event_handler(rqid, fn, NULL, data);
++}
++
++
++#endif /* _SURFACE_SAM_SSH_H */
+diff --git a/drivers/platform/x86/surface_sam/surface_sam_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_vhf.c
+new file mode 100644
+index 000000000000..38851a07ea80
+--- /dev/null
++++ b/drivers/platform/x86/surface_sam/surface_sam_vhf.c
+@@ -0,0 +1,286 @@
++/*
++ * Virtual HID Framwork (VHF) driver for input events via SAM.
++ * Used for keyboard input events on the Surface Laptops.
++ */
++
++#include <linux/acpi.h>
++#include <linux/hid.h>
++#include <linux/input.h>
++#include <linux/platform_device.h>
++#include <linux/types.h>
++
++#include "surface_sam_ssh.h"
++
++
++#define USB_VENDOR_ID_MICROSOFT		0x045e
++#define USB_DEVICE_ID_MS_VHF		0xf001
++
++#define VHF_INPUT_NAME			"Microsoft Virtual HID Framework Device"
++
++/*
++ * Request ID for VHF events. This value is based on the output of the Surface
++ * EC and should not be changed.
++ */
++#define SAM_EVENT_VHF_RQID		0x0001
++#define SAM_EVENT_VHF_TC		0x08
++
++
++struct vhf_evtctx {
++	struct device     *dev;
++	struct hid_device *hid;
++};
++
++struct vhf_drvdata {
++	struct vhf_evtctx event_ctx;
++};
++
++
++/*
++ * These report descriptors have been extracted from a Surface Book 2.
++ * They seems to be similar enough to be usable on the Surface Laptop.
++ */
++static const u8 vhf_hid_desc[] = {
++	// keyboard descriptor (event command ID 0x03)
++	0x05, 0x01,             /*  Usage Page (Desktop),                   */
++	0x09, 0x06,             /*  Usage (Keyboard),                       */
++	0xA1, 0x01,             /*  Collection (Application),               */
++	0x85, 0x01,             /*      Report ID (1),                      */
++	0x15, 0x00,             /*      Logical Minimum (0),                */
++	0x25, 0x01,             /*      Logical Maximum (1),                */
++	0x75, 0x01,             /*      Report Size (1),                    */
++	0x95, 0x08,             /*      Report Count (8),                   */
++	0x05, 0x07,             /*      Usage Page (Keyboard),              */
++	0x19, 0xE0,             /*      Usage Minimum (KB Leftcontrol),     */
++	0x29, 0xE7,             /*      Usage Maximum (KB Right GUI),       */
++	0x81, 0x02,             /*      Input (Variable),                   */
++	0x75, 0x08,             /*      Report Size (8),                    */
++	0x95, 0x0A,             /*      Report Count (10),                  */
++	0x19, 0x00,             /*      Usage Minimum (None),               */
++	0x29, 0x91,             /*      Usage Maximum (KB LANG2),           */
++	0x26, 0xFF, 0x00,       /*      Logical Maximum (255),              */
++	0x81, 0x00,             /*      Input,                              */
++	0x05, 0x0C,             /*      Usage Page (Consumer),              */
++	0x0A, 0xC0, 0x02,       /*      Usage (02C0h),                      */
++	0xA1, 0x02,             /*      Collection (Logical),               */
++	0x1A, 0xC1, 0x02,       /*          Usage Minimum (02C1h),          */
++	0x2A, 0xC6, 0x02,       /*          Usage Maximum (02C6h),          */
++	0x95, 0x06,             /*          Report Count (6),               */
++	0xB1, 0x03,             /*          Feature (Constant, Variable),   */
++	0xC0,                   /*      End Collection,                     */
++	0x05, 0x08,             /*      Usage Page (LED),                   */
++	0x19, 0x01,             /*      Usage Minimum (01h),                */
++	0x29, 0x03,             /*      Usage Maximum (03h),                */
++	0x75, 0x01,             /*      Report Size (1),                    */
++	0x95, 0x03,             /*      Report Count (3),                   */
++	0x25, 0x01,             /*      Logical Maximum (1),                */
++	0x91, 0x02,             /*      Output (Variable),                  */
++	0x95, 0x05,             /*      Report Count (5),                   */
++	0x91, 0x01,             /*      Output (Constant),                  */
++	0xC0,                   /*  End Collection,                         */
++
++	// media key descriptor (event command ID 0x04)
++	0x05, 0x0C,             /*  Usage Page (Consumer),                  */
++	0x09, 0x01,             /*  Usage (Consumer Control),               */
++	0xA1, 0x01,             /*  Collection (Application),               */
++	0x85, 0x03,             /*      Report ID (3),                      */
++	0x75, 0x10,             /*      Report Size (16),                   */
++	0x15, 0x00,             /*      Logical Minimum (0),                */
++	0x26, 0xFF, 0x03,       /*      Logical Maximum (1023),             */
++	0x19, 0x00,             /*      Usage Minimum (00h),                */
++	0x2A, 0xFF, 0x03,       /*      Usage Maximum (03FFh),              */
++	0x81, 0x00,             /*      Input,                              */
++	0xC0,                   /*  End Collection,                         */
++};
++
++
++static int vhf_hid_start(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
++	return 0;
++}
++
++static void vhf_hid_stop(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
++}
++
++static int vhf_hid_open(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
++	return 0;
++}
++
++static void vhf_hid_close(struct hid_device *hid)
++{
++	hid_dbg(hid, "%s\n", __func__);
++}
++
++static int vhf_hid_parse(struct hid_device *hid)
++{
++	return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc));
++}
++
++static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum,
++			       u8 *buf, size_t len, unsigned char rtype,
++			       int reqtype)
++{
++	hid_dbg(hid, "%s\n", __func__);
++	return 0;
++}
++
++static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len)
++{
++	hid_dbg(hid, "%s\n", __func__);
++	print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false);
++
++	return len;
++}
++
++static struct hid_ll_driver vhf_hid_ll_driver = {
++	.start         = vhf_hid_start,
++	.stop          = vhf_hid_stop,
++	.open          = vhf_hid_open,
++	.close         = vhf_hid_close,
++	.parse         = vhf_hid_parse,
++	.raw_request   = vhf_hid_raw_request,
++	.output_report = vhf_hid_output_report,
++};
++
++
++static struct hid_device *vhf_create_hid_device(struct platform_device *pdev)
++{
++	struct hid_device *hid;
++
++	hid = hid_allocate_device();
++	if (IS_ERR(hid)) {
++		return hid;
++	}
++
++	hid->dev.parent = &pdev->dev;
++
++	hid->bus     = BUS_VIRTUAL;
++	hid->vendor  = USB_VENDOR_ID_MICROSOFT;
++	hid->product = USB_DEVICE_ID_MS_VHF;
++
++	hid->ll_driver = &vhf_hid_ll_driver;
++
++	sprintf(hid->name, "%s", VHF_INPUT_NAME);
++
++	return hid;
++}
++
++static int vhf_event_handler(struct surface_sam_ssh_event *event, void *data)
++{
++	struct vhf_evtctx *ctx = (struct vhf_evtctx *)data;
++
++	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
++		return hid_input_report(ctx->hid, HID_INPUT_REPORT, event->pld, event->len, 1);
++	}
++
++	dev_warn(ctx->dev, "unsupported event (tc = %d, cid = %d)\n", event->tc, event->cid);
++	return 0;
++}
++
++static unsigned long vhf_event_delay(struct surface_sam_ssh_event *event, void *data)
++{
++	// high priority immediate execution for keyboard events
++	if (event->tc == 0x08 && (event->cid == 0x03 || event->cid == 0x04)) {
++		return SURFACE_SAM_SSH_EVENT_IMMEDIATE;
++	}
++
++	return 0;
++}
++
++static int surface_sam_vhf_probe(struct platform_device *pdev)
++{
++	struct vhf_drvdata *drvdata;
++	struct hid_device *hid;
++	int status;
++
++	// add device link to EC
++	status = surface_sam_ssh_consumer_register(&pdev->dev);
++	if (status) {
++		return status == -ENXIO ? -EPROBE_DEFER : status;
++	}
++
++	drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL);
++	if (!drvdata) {
++		return -ENOMEM;
++	}
++
++	hid = vhf_create_hid_device(pdev);
++	if (IS_ERR(hid)) {
++		status = PTR_ERR(hid);
++		goto err_probe_hid;
++	}
++
++	status = hid_add_device(hid);
++	if (status) {
++		goto err_add_hid;
++	}
++
++	drvdata->event_ctx.dev = &pdev->dev;
++	drvdata->event_ctx.hid = hid;
++
++	platform_set_drvdata(pdev, drvdata);
++
++	status = surface_sam_ssh_set_delayed_event_handler(
++			SAM_EVENT_VHF_RQID,
++	                vhf_event_handler,
++	                vhf_event_delay,
++			&drvdata->event_ctx);
++	if (status) {
++		goto err_add_hid;
++	}
++
++	status = surface_sam_ssh_enable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID);
++	if (status) {
++		goto err_event_source;
++	}
++
++	return 0;
++
++err_event_source:
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID);
++err_add_hid:
++	hid_destroy_device(hid);
++	platform_set_drvdata(pdev, NULL);
++err_probe_hid:
++	kfree(drvdata);
++	return status;
++}
++
++static int surface_sam_vhf_remove(struct platform_device *pdev)
++{
++	struct vhf_drvdata *drvdata = platform_get_drvdata(pdev);
++
++	surface_sam_ssh_disable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID);
++	surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID);
++
++	hid_destroy_device(drvdata->event_ctx.hid);
++	kfree(drvdata);
++
++	platform_set_drvdata(pdev, NULL);
++	return 0;
++}
++
++
++static const struct acpi_device_id surface_sam_vhf_match[] = {
++	{ "MSHW0096" },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match);
++
++struct platform_driver surface_sam_vhf = {
++	.probe = surface_sam_vhf_probe,
++	.remove = surface_sam_vhf_remove,
++	.driver = {
++		.name = "surface_sam_vhf",
++		.acpi_match_table = ACPI_PTR(surface_sam_vhf_match),
++	},
++};
++module_platform_driver(surface_sam_vhf);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices");
++MODULE_LICENSE("GPL v2");
 diff --git a/include/linux/intel_ipts_fw.h b/include/linux/intel_ipts_fw.h
 new file mode 100644
 index 000000000000..adbfd29459a2