From 3172322f6422b08e50f32a657abf5f19c615289d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 21 Oct 2019 00:10:47 +0200 Subject: [PATCH] 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, ...) --- patches/4.19/0001-surface-acpi.patch | 6238 ++++++++-------- patches/4.19/0002-suspend.patch | 4 +- patches/4.19/0003-buttons.patch | 4 +- patches/4.19/0004-cameras.patch | 4 +- patches/4.19/0005-ipts.patch | 10 +- patches/4.19/0006-hid.patch | 4 +- patches/4.19/0007-sdcard-reader.patch | 4 +- patches/4.19/0008-wifi.patch | 4 +- patches/4.19/0009-surface3-power.patch | 12 +- patches/4.19/0010-mwlwifi.patch | 50 +- patches/4.19/0011-surface-lte.patch | 4 +- patches/4.19/0012-surfacebook2-dgpu.patch | 10 +- patches/5.3/0001-surface-acpi.patch | 4 +- patches/5.3/0002-buttons.patch | 2 +- patches/5.3/0003-surfacebook2-dgpu.patch | 2 +- patches/5.3/0004-hid.patch | 4 +- patches/5.3/0005-surface3-power.patch | 4 +- patches/5.3/0006-surface-lte.patch | 4 +- patches/5.3/0007-wifi.patch | 4 +- patches/5.3/0008-legacy-i915.patch | 4 +- patches/5.3/0009-ipts.patch | 8243 ++++++++++++++++++++- 21 files changed, 11381 insertions(+), 3238 deletions(-) diff --git a/patches/4.19/0001-surface-acpi.patch b/patches/4.19/0001-surface-acpi.patch index 120e46c40..7ed8374b0 100644 --- a/patches/4.19/0001-surface-acpi.patch +++ b/patches/4.19/0001-surface-acpi.patch @@ -1,17 +1,31 @@ -From bcb840821cd18b10375f24efb5b44f7f5adcc535 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz -Date: Wed, 2 Oct 2019 22:28:37 +0200 +From 789b866d520178ff70caef9ad977290305cd27db Mon Sep 17 00:00:00 2001 +From: qzed +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,1905 +225,714 @@ 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 ++++ 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 -+#include -+#include +#include -+#include -+#include -+#include +#include -+#include +#include +#include -+#include +#include -+#include +#include +#include -+#include -+#include -+#include -+#include +#include +#include -+#include -+#include ++#include +#include -+#include -+#include -+#include ++#include ++ ++#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 SURFACEGEN5_EVENT_IMMEDIATE ((unsigned long) -1) ++#define DTX_LATCH_CLOSED 0x00 ++#define DTX_LATCH_OPENED 0x01 + + -+struct surfacegen5_buf { -+ u8 cap; -+ u8 len; -+ u8 *data; -+}; ++// Warning: This must always be a power of 2! ++#define DTX_CLIENT_BUF_SIZE 16 + -+struct surfacegen5_rqst { -+ u8 tc; -+ u8 iid; -+ u8 cid; -+ u8 snc; -+ u8 cdl; -+ u8 *pld; -+}; ++#define DTX_CONNECT_OPMODE_DELAY 1000 + -+struct surfacegen5_event { -+ u16 rqid; -+ u8 tc; -+ u8 iid; -+ u8 cid; -+ u8 len; -+ u8 *pld; -+}; ++#define DTX_ERR KERN_ERR "surface_sam_dtx: " ++#define DTX_WARN KERN_WARNING "surface_sam_dtx: " + + -+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 { ++struct surface_dtx_event { + u8 type; -+ u8 len; // without crc -+ u8 pad; -+ u8 seq; ++ u8 code; ++ u8 arg0; ++ u8 arg1; +} __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 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 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; ++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 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 struct surface_dtx_dev surface_dtx_dev; + + -+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) ++static int surface_sam_query_opmpde(void) +{ -+ struct surfacegen5_ec *ec = &surfacegen5_ec; ++ u8 result_buf[1]; ++ int status; + -+ mutex_lock(&ec->lock); -+ return ec; -+} ++ 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, ++ }; + -+inline static void surfacegen5_ec_release(struct surfacegen5_ec *ec) -+{ -+ mutex_unlock(&ec->lock); -+} ++ struct surface_sam_ssh_buf result = { ++ .cap = 1, ++ .len = 0, ++ .data = result_buf, ++ }; + -+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; ++ status = surface_sam_ssh_rqst(&rqst, &result); ++ if (status) { ++ return status; + } + -+ 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) { ++ if (result.len != 1) { + return -EFAULT; + } + -+ surfacegen5_ec_release(ec); -+ return 0; ++ return result.data[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) ++static int dtx_cmd_simple(u8 cid) +{ -+ 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, ++ struct surface_sam_ssh_rqst rqst = { ++ .tc = SAM_RQST_DTX_TC, ++ .iid = 0, ++ .cid = cid, ++ .snc = 0, ++ .cdl = 0, + .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; ++ return surface_sam_ssh_rqst(&rqst, NULL); +} + -+static int surfacegen5_ssh_ec_suspend(struct surfacegen5_ec *ec) ++static int dtx_cmd_get_opmode(int __user *buf) +{ -+ 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; ++ int opmode = surface_sam_query_opmpde(); ++ if (opmode < 0) { ++ return opmode; + } + -+ if (buf[0] != 0x00) { -+ dev_warn(&ec->serdev->dev, -+ "unexpected result while trying to suspend EC: 0x%02x\n", -+ buf[0]); ++ if (put_user(opmode, buf)) { ++ return -EACCES; + } + + return 0; +} + + -+inline static bool surfacegen5_ssh_is_valid_syn(const u8 *ptr) ++static int surface_dtx_open(struct inode *inode, struct file *file) +{ -+ return ptr[0] == 0xaa && ptr[1] == 0x55; ++ 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; +} + -+inline static bool surfacegen5_ssh_is_valid_ter(const u8 *ptr) ++static int surface_dtx_release(struct inode *inode, struct file *file) +{ -+ return ptr[0] == 0xff && ptr[1] == 0xff; ++ 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; +} + -+inline static bool surfacegen5_ssh_is_valid_crc(const u8 *begin, const u8 *end) ++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) +{ -+ u16 crc = surfacegen5_ssh_crc(begin, end - begin); -+ return (end[0] == (crc & 0xff)) && (end[1] == (crc >> 8)); -+} ++ 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; ++ } + -+static int surfacegen5_ssh_send_ack(struct surfacegen5_ec *ec, u8 seq) -+{ -+ int status; -+ u8 buf[SG5_MSG_LEN_CTRL]; -+ u16 crc; ++ if (!ddev->active) { ++ return -ENODEV; ++ } + -+ buf[0] = 0xaa; -+ buf[1] = 0x55; -+ buf[2] = 0x40; -+ buf[3] = 0x00; -+ buf[4] = 0x00; -+ buf[5] = seq; ++ // check availability ++ if (client->buffer_head == client->buffer_tail){ ++ if (file->f_flags & O_NONBLOCK) { ++ return -EAGAIN; ++ } + -+ 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); ++ status = wait_event_interruptible(ddev->waitq, ++ client->buffer_head != client->buffer_tail || ++ !ddev->active); + if (status) { -+ dev_err(dev, SG5_EVENT_TAG "failed to send ACK: %d\n", status); ++ return status; ++ } ++ ++ if (!ddev->active) { ++ return -ENODEV; + } + } + -+ if (refcount_dec_and_test(&work->refcount)) { -+ kfree(work); ++ // 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 void surfacegen5_event_work_evt_handler(struct work_struct *_work) ++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) +{ -+ 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 surface_dtx_client *client = file->private_data; ++ int mask; + -+ surfacegen5_ec_event_handler_fn handler; -+ void *handler_data; ++ poll_wait(file, &client->ddev->waitq, pt); + -+ 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); ++ if (client->ddev->active) { ++ mask = EPOLLOUT | EPOLLWRNORM; + } else { -+ dev_warn(dev, SG5_EVENT_TAG "unhandled event (rqid: %04x)\n", event->rqid); ++ 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) { -+ 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; ++ if (!ddev->active) { ++ mutex_unlock(&ddev->mutex); ++ return -ENODEV; + } + -+ 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; ++ 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; + } + -+ dma_release_channel(tx); -+check_dma_release_rx: -+ dma_release_channel(rx); -+check_dma_out: ++ mutex_unlock(&ddev->mutex); + 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 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 int surfacegen5_acpi_ssh_probe(struct serdev_device *serdev) ++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 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; ++ struct surface_dtx_client *client; + -+ dev_dbg(&serdev->dev, "probing\n"); ++ rcu_read_lock(); ++ list_for_each_entry_rcu(client, &ddev->client_list, node) { ++ spin_lock(&client->buffer_lock); + -+ // ensure DMA is ready before we set up the device -+ status = surfacegen5_ssh_check_dma(serdev); ++ 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) { -+ return status; ++ goto err_handler; + } + -+ // 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); ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_DTX_TC, 0x01, SAM_EVENT_DTX_RQID); + if (status) { -+ goto err_probe_open; ++ goto err_source; + } + -+ 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: ++err_source: ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_DTX_RQID); ++err_handler: + return status; +} + -+static void surfacegen5_acpi_ssh_remove(struct serdev_device *serdev) ++static void surface_dtx_events_disable(void) +{ -+ 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); ++ 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 const struct acpi_device_id surfacegen5_acpi_ssh_match[] = { -+ { "MSHW0084", 0 }, ++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, surfacegen5_acpi_ssh_match); ++MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match); + -+struct serdev_device_driver surfacegen5_acpi_ssh = { -+ .probe = surfacegen5_acpi_ssh_probe, -+ .remove = surfacegen5_acpi_ssh_remove, ++struct platform_driver surface_sam_dtx = { ++ .probe = surface_sam_dtx_probe, ++ .remove = surface_sam_dtx_remove, + .driver = { -+ .name = "surfacegen5_acpi_ssh", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_ssh_match), -+ .pm = &surfacegen5_ssh_pm_ops, ++ .name = "surface_sam_dtx", ++ .acpi_match_table = ACPI_PTR(surface_sam_dtx_match), + }, +}; ++module_platform_driver(surface_sam_dtx); + -+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) ++MODULE_AUTHOR("Maximilian Luz "); ++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. + */ + -+#ifdef CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE ++#include ++#include ++#include ++#include ++#include + -+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); ++#include "surface_sam_ssh.h" + + -+inline int surfacegen5_ssh_sysfs_register(struct device *dev) -+{ -+ return sysfs_create_bin_file(&dev->kobj, &bin_attr_rqst); -+} ++#define SAN_RQST_RETRY 5 + -+inline void surfacegen5_ssh_sysfs_unregister(struct device *dev) -+{ -+ sysfs_remove_bin_file(&dev->kobj, &bin_attr_rqst); -+} ++#define SAN_DSM_REVISION 0 ++#define SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT 0x09 + -+#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 = ++static const guid_t 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 SAM_EVENT_DELAY_PWR_STATE 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 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 SG5_EVENT_TEMP_TC 0x03 -+#define SG5_EVENT_TEMP_RQID 0x0003 -+#define SG5_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT 0x0b ++#define SAM_EVENT_TEMP_TC 0x03 ++#define SAM_EVENT_TEMP_RQID 0x0003 ++#define SAM_EVENT_TEMP_CID_NOTIFY_SENSOR_TRIP_POINT 0x0b + -+#define SG5_SAN_RQST_TAG "surfacegen5_ec_rqst: " ++#define SAN_RQST_TAG "surface_sam_san_rqst: " + -+#define SG5_QUIRK_BASE_STATE_DELAY 1000 ++#define SAN_QUIRK_BASE_STATE_DELAY 1000 + + -+struct surfacegen5_san_acpi_consumer { ++struct san_acpi_consumer { + char *path; + bool required; + u32 flags; +}; + -+struct surfacegen5_san_opreg_context { ++struct san_opreg_context { + struct acpi_connection_info connection; + struct device *dev; +}; + -+struct surfacegen5_san_consumer_link { -+ const struct surfacegen5_san_acpi_consumer *properties; ++struct san_consumer_link { ++ const struct san_acpi_consumer *properties; + struct device_link *link; +}; + -+struct surfacegen5_san_consumers { ++struct san_consumers { + u32 num; -+ struct surfacegen5_san_consumer_link *links; ++ struct san_consumer_link *links; +}; + -+struct surfacegen5_san_drvdata { -+ struct surfacegen5_san_opreg_context opreg_ctx; -+ struct surfacegen5_san_consumers consumers; ++struct san_drvdata { ++ struct san_opreg_context opreg_ctx; ++ struct san_consumers consumers; +}; + +struct gsb_data_in { @@ -2127,22 +978,22 @@ index 000000000000..5dbf48a3d9b3 +} __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, ++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 surfacegen5_acpi_notify_power_event(struct device *dev, enum surfacegen5_pwr_event event) ++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, &SG5_SAN_DSM_UUID, SG5_SAN_DSM_REVISION, ++ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, + (u8) event, NULL, ACPI_TYPE_BUFFER); + + if (IS_ERR_OR_NULL(obj)) { @@ -2158,7 +1009,7 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+static int surfacegen5_acpi_notify_sensor_trip_point(struct device *dev, u8 iid) ++static int san_acpi_notify_sensor_trip_point(struct device *dev, u8 iid) +{ + acpi_handle san = ACPI_HANDLE(dev); + union acpi_object *obj; @@ -2167,8 +1018,8 @@ index 000000000000..5dbf48a3d9b3 + 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, ++ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, ++ SAN_DSM_FN_NOTIFY_SENSOR_TRIP_POINT, + ¶m, ACPI_TYPE_BUFFER); + + if (IS_ERR_OR_NULL(obj)) { @@ -2185,11 +1036,11 @@ index 000000000000..5dbf48a3d9b3 +} + + -+inline static int surfacegen5_evt_power_adapter(struct device *dev, struct surfacegen5_event *event) ++inline static int san_evt_power_adapter(struct device *dev, struct surface_sam_ssh_event *event) +{ + int status; + -+ status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_ADP1_STAT); ++ 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; @@ -2198,18 +1049,18 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+inline static int surfacegen5_evt_power_hwchange(struct device *dev, struct surfacegen5_event *event) ++inline static int san_evt_power_hwchange(struct device *dev, struct surface_sam_ssh_event *event) +{ -+ enum surfacegen5_pwr_event evcode; ++ enum san_pwr_event evcode; + int status; + + if (event->iid == 0x02) { -+ evcode = SURFACEGEN5_PWR_EVENT_BAT2_INFO; ++ evcode = SAN_PWR_EVENT_BAT2_INFO; + } else { -+ evcode = SURFACEGEN5_PWR_EVENT_BAT1_INFO; ++ evcode = SAN_PWR_EVENT_BAT1_INFO; + } + -+ status = surfacegen5_acpi_notify_power_event(dev, evcode); ++ status = san_acpi_notify_power_event(dev, evcode); + if (status) { + dev_err(dev, "error handling power event (cid = %x)\n", event->cid); + return status; @@ -2218,17 +1069,17 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+inline static int surfacegen5_evt_power_state(struct device *dev, struct surfacegen5_event *event) ++inline static int san_evt_power_state(struct device *dev, struct surface_sam_ssh_event *event) +{ + int status; + -+ status = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT1_STAT); ++ 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 = surfacegen5_acpi_notify_power_event(dev, SURFACEGEN5_PWR_EVENT_BAT2_STAT); ++ 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; @@ -2237,34 +1088,34 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+static unsigned long surfacegen5_evt_power_delay(struct surfacegen5_event *event, void *data) ++static unsigned long san_evt_power_delay(struct surface_sam_ssh_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 SAM_EVENT_PWR_CID_CHARGING: ++ case SAM_EVENT_PWR_CID_STATE: ++ return SAM_EVENT_DELAY_PWR_STATE; + -+ case SG5_EVENT_PWR_CID_ADAPTER: -+ case SG5_EVENT_PWR_CID_HWCHANGE: ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ case SAM_EVENT_PWR_CID_HWCHANGE: + default: + return 0; + } +} + -+static int surfacegen5_evt_power(struct surfacegen5_event *event, void *data) ++static int san_evt_power(struct surface_sam_ssh_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 SAM_EVENT_PWR_CID_HWCHANGE: ++ return san_evt_power_hwchange(dev, event); + -+ case SG5_EVENT_PWR_CID_ADAPTER: -+ return surfacegen5_evt_power_adapter(dev, event); ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ return san_evt_power_adapter(dev, event); + -+ case SG5_EVENT_PWR_CID_CHARGING: -+ case SG5_EVENT_PWR_CID_STATE: -+ return surfacegen5_evt_power_state(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); @@ -2274,11 +1125,11 @@ index 000000000000..5dbf48a3d9b3 +} + + -+inline static int surfacegen5_evt_thermal_notify(struct device *dev, struct surfacegen5_event *event) ++inline static int san_evt_thermal_notify(struct device *dev, struct surface_sam_ssh_event *event) +{ + int status; + -+ status = surfacegen5_acpi_notify_sensor_trip_point(dev, event->iid); ++ 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; @@ -2287,13 +1138,13 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+static int surfacegen5_evt_thermal(struct surfacegen5_event *event, void *data) ++static int san_evt_thermal(struct surface_sam_ssh_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); ++ 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); @@ -2303,8 +1154,8 @@ index 000000000000..5dbf48a3d9b3 +} + + -+static struct gsb_data_rqsx *surfacegen5_san_validate_rqsx( -+ struct device *dev, const char *type, struct gsb_buffer *buffer) ++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; + @@ -2330,7 +1181,7 @@ index 000000000000..5dbf48a3d9b3 +} + +static acpi_status -+surfacegen5_san_etwl(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) ++san_etwl(struct san_opreg_context *ctx, struct gsb_buffer *buffer) +{ + struct gsb_data_etwl *etwl = &buffer->data.etwl; + @@ -2351,11 +1202,11 @@ index 000000000000..5dbf48a3d9b3 +} + +static acpi_status -+surfacegen5_san_rqst(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) ++san_rqst(struct 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 = {}; ++ 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; + @@ -2370,7 +1221,7 @@ index 000000000000..5dbf48a3d9b3 + rqst.cdl = gsb_rqst->cdl; + rqst.pld = &gsb_rqst->pld[0]; + -+ result.cap = SURFACEGEN5_MAX_RQST_RESPONSE; ++ result.cap = SURFACE_SAM_SSH_MAX_RQST_RESPONSE; + result.len = 0; + result.data = kzalloc(result.cap, GFP_KERNEL); + @@ -2378,12 +1229,12 @@ index 000000000000..5dbf48a3d9b3 + return AE_NO_MEMORY; + } + -+ for (try = 0; try < SG5_SAN_RQST_RETRY; try++) { ++ for (try = 0; try < SAN_RQST_RETRY; try++) { + if (try) { -+ dev_warn(ctx->dev, SG5_SAN_RQST_TAG "IO error occured, trying again\n"); ++ dev_warn(ctx->dev, SAN_RQST_TAG "IO error occured, trying again\n"); + } + -+ status = surfacegen5_ec_rqst(&rqst, &result); ++ status = surface_sam_ssh_rqst(&rqst, &result); + if (status != -EIO) break; + } + @@ -2417,7 +1268,7 @@ index 000000000000..5dbf48a3d9b3 + 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); ++ 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 @@ -2430,9 +1281,9 @@ index 000000000000..5dbf48a3d9b3 +} + +static acpi_status -+surfacegen5_san_rqsg(struct surfacegen5_san_opreg_context *ctx, struct gsb_buffer *buffer) ++san_rqsg(struct san_opreg_context *ctx, struct gsb_buffer *buffer) +{ -+ struct gsb_data_rqsx *rqsg = surfacegen5_san_validate_rqsx(ctx->dev, "RQSG", buffer); ++ struct gsb_data_rqsx *rqsg = san_validate_rqsx(ctx->dev, "RQSG", buffer); + + if (!rqsg) { + return AE_OK; @@ -2448,11 +1299,11 @@ index 000000000000..5dbf48a3d9b3 + + +static acpi_status -+surfacegen5_san_opreg_handler(u32 function, acpi_physical_address command, -+ u32 bits, u64 *value64, -+ void *opreg_context, void *region_context) ++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 san_opreg_context *context = opreg_context; + struct gsb_buffer *buffer = (struct gsb_buffer *)value64; + int accessor_type = (0xFFFF0000 & function) >> 16; + @@ -2473,70 +1324,70 @@ index 000000000000..5dbf48a3d9b3 + } + + 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); ++ 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_san_enable_events(struct device *dev) ++static int 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); ++ status = surface_sam_ssh_set_delayed_event_handler( ++ SAM_EVENT_PWR_RQID, san_evt_power, ++ san_evt_power_delay, dev); + if (status) { -+ goto err_event_handler_power; ++ goto err_handler_power; + } + -+ status = surfacegen5_ec_set_event_handler( -+ SG5_EVENT_TEMP_RQID, surfacegen5_evt_thermal, ++ status = surface_sam_ssh_set_event_handler( ++ SAM_EVENT_TEMP_RQID, san_evt_thermal, + dev); + if (status) { -+ goto err_event_handler_thermal; ++ goto err_handler_thermal; + } + -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_PWR_TC, 0x01, SG5_EVENT_PWR_RQID); ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_PWR_TC, 0x01, SAM_EVENT_PWR_RQID); + if (status) { -+ goto err_event_source_power; ++ goto err_source_power; + } + -+ status = surfacegen5_ec_enable_event_source(SG5_EVENT_TEMP_TC, 0x01, SG5_EVENT_TEMP_RQID); ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_TEMP_TC, 0x01, SAM_EVENT_TEMP_RQID); + if (status) { -+ goto err_event_source_thermal; ++ goto err_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: ++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 surfacegen5_san_disable_events(void) ++static void 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); ++ 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 surfacegen5_san_consumers_link(struct platform_device *pdev, -+ const struct surfacegen5_san_acpi_consumer *cons, -+ struct surfacegen5_san_consumers *out) ++static int san_consumers_link(struct platform_device *pdev, ++ const struct san_acpi_consumer *cons, ++ struct san_consumers *out) +{ -+ const struct surfacegen5_san_acpi_consumer *con; -+ struct surfacegen5_san_consumer_link *links, *link; ++ const struct san_acpi_consumer *con; ++ struct san_consumer_link *links, *link; + struct acpi_device *adev; + acpi_handle handle; + u32 max_links = 0; @@ -2552,7 +1403,7 @@ index 000000000000..5dbf48a3d9b3 + } + + // allocate -+ links = kzalloc(max_links * sizeof(struct surfacegen5_san_consumer_link), GFP_KERNEL); ++ links = kzalloc(max_links * sizeof(struct san_consumer_link), GFP_KERNEL); + link = &links[0]; + + if (!links) { @@ -2565,7 +1416,7 @@ index 000000000000..5dbf48a3d9b3 + if (status) { + if (con->required || status != AE_NOT_FOUND) { + status = -ENXIO; -+ goto consumers_link_cleanup; ++ goto cleanup; + } else { + continue; + } @@ -2573,13 +1424,13 @@ index 000000000000..5dbf48a3d9b3 + + status = acpi_bus_get_device(handle, &adev); + if (status) { -+ goto consumers_link_cleanup; ++ goto cleanup; + } + + link->link = device_link_add(&adev->dev, &pdev->dev, con->flags); + if (!(link->link)) { + status = -EFAULT; -+ goto consumers_link_cleanup; ++ goto cleanup; + } + link->properties = con; + @@ -2591,7 +1442,7 @@ index 000000000000..5dbf48a3d9b3 + + return 0; + -+consumers_link_cleanup: ++cleanup: + for (link = link - 1; link >= links; --link) { + if (link->properties->flags & DL_FLAG_STATELESS) { + device_link_del(link->link); @@ -2601,7 +1452,7 @@ index 000000000000..5dbf48a3d9b3 + return status; +} + -+static void surfacegen5_san_consumers_unlink(struct surfacegen5_san_consumers *consumers) { ++static void san_consumers_unlink(struct san_consumers *consumers) { + u32 i; + + if (!consumers) { @@ -2620,10 +1471,10 @@ index 000000000000..5dbf48a3d9b3 + consumers->links = NULL; +} + -+static int surfacegen5_acpi_san_probe(struct platform_device *pdev) ++static int surface_sam_san_probe(struct platform_device *pdev) +{ -+ const struct surfacegen5_san_acpi_consumer *cons; -+ struct surfacegen5_san_drvdata *drvdata; ++ const struct san_acpi_consumer *cons; ++ struct san_drvdata *drvdata; + acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node + int status; + @@ -2633,12 +1484,12 @@ index 000000000000..5dbf48a3d9b3 + * 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); ++ status = surface_sam_ssh_consumer_register(&pdev->dev); + if (status) { + return status == -ENXIO ? -EPROBE_DEFER : status; + } + -+ drvdata = kzalloc(sizeof(struct surfacegen5_san_drvdata), GFP_KERNEL); ++ drvdata = kzalloc(sizeof(struct san_drvdata), GFP_KERNEL); + if (!drvdata) { + return -ENOMEM; + } @@ -2646,51 +1497,51 @@ index 000000000000..5dbf48a3d9b3 + drvdata->opreg_ctx.dev = &pdev->dev; + + cons = acpi_device_get_match_data(&pdev->dev); -+ status = surfacegen5_san_consumers_link(pdev, cons, &drvdata->consumers); ++ status = san_consumers_link(pdev, cons, &drvdata->consumers); + if (status) { -+ goto err_probe_consumers; ++ goto err_consumers; + } + + platform_set_drvdata(pdev, drvdata); + + status = acpi_install_address_space_handler(san, + ACPI_ADR_SPACE_GSBUS, -+ &surfacegen5_san_opreg_handler, ++ &san_opreg_handler, + NULL, &drvdata->opreg_ctx); + + if (ACPI_FAILURE(status)) { + status = -ENODEV; -+ goto err_probe_install_handler; ++ goto err_install_handler; + } + -+ status = surfacegen5_san_enable_events(&pdev->dev); ++ status = san_enable_events(&pdev->dev); + if (status) { -+ goto err_probe_enable_events; ++ goto err_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: ++err_enable_events: ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); ++err_install_handler: + platform_set_drvdata(san, NULL); -+ surfacegen5_san_consumers_unlink(&drvdata->consumers); -+err_probe_consumers: ++ san_consumers_unlink(&drvdata->consumers); ++err_consumers: + kfree(drvdata); + return status; +} + -+static int surfacegen5_acpi_san_remove(struct platform_device *pdev) ++static int surface_sam_san_remove(struct platform_device *pdev) +{ -+ struct surfacegen5_san_drvdata *drvdata = platform_get_drvdata(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, &surfacegen5_san_opreg_handler); -+ surfacegen5_san_disable_events(); ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, &san_opreg_handler); ++ san_disable_events(); + -+ surfacegen5_san_consumers_unlink(&drvdata->consumers); ++ san_consumers_unlink(&drvdata->consumers); + kfree(drvdata); + + platform_set_drvdata(pdev, NULL); @@ -2698,7 +1549,7 @@ index 000000000000..5dbf48a3d9b3 +} + + -+static const struct surfacegen5_san_acpi_consumer surfacegen5_mshw0091_consumers[] = { ++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 }, @@ -2706,69 +1557,2348 @@ index 000000000000..5dbf48a3d9b3 + { }, +}; + -+static const struct acpi_device_id surfacegen5_acpi_san_match[] = { -+ { "MSHW0091", (long unsigned int) surfacegen5_mshw0091_consumers }, ++static const struct acpi_device_id surface_sam_san_match[] = { ++ { "MSHW0091", (long unsigned int) san_mshw0091_consumers }, + { }, +}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_san_match); ++MODULE_DEVICE_TABLE(acpi, surface_sam_san_match); + -+struct platform_driver surfacegen5_acpi_san = { -+ .probe = surfacegen5_acpi_san_probe, -+ .remove = surfacegen5_acpi_san_remove, ++struct platform_driver surface_sam_san = { ++ .probe = surface_sam_san_probe, ++ .remove = surface_sam_san_remove, + .driver = { -+ .name = "surfacegen5_acpi_san", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_san_match), ++ .name = "surface_sam_san", ++ .acpi_match_table = ACPI_PTR(surface_sam_san_match), + }, +}; ++module_platform_driver(surface_sam_san); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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; ++}; + + -+inline int surfacegen5_acpi_san_register(void) ++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) +{ -+ return platform_driver_register(&surfacegen5_acpi_san); ++ 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]); +} + -+inline void surfacegen5_acpi_san_unregister(void) ++static int surface_sam_perf_mode_set(int perf_mode) +{ -+ platform_driver_unregister(&surfacegen5_acpi_san); ++ 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); +} + -+#else /* CONFIG_SURFACE_ACPI_SAN */ + -+inline int surfacegen5_acpi_san_register(void) ++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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_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 "); ++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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++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; +} + -+inline void surfacegen5_acpi_san_unregister(void) ++void surface_sam_ssh_sysfs_unregister(struct device *dev) +{ +} + -+#endif /* CONFIG_SURFACE_ACPI_SAN */ ++#endif /* CONFIG_SURFACE_ACPI_SSH_DEBUG_DEVICE */ + + -+/************************************************************************* -+ * Virtual HID Framework driver ++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 "); ++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. + */ + -+#ifdef CONFIG_SURFACE_ACPI_VHF ++#ifndef _SURFACE_SAM_SSH_H ++#define _SURFACE_SAM_SSH_H + -+#define SG5_VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" ++#include ++#include ++ ++ ++/* ++ * 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 ++#include ++#include ++#include ++#include ++ ++#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 SG5_EVENT_VHF_RQID 0x0001 -+#define SG5_EVENT_VHF_TC 0x08 ++#define SAM_EVENT_VHF_RQID 0x0001 ++#define SAM_EVENT_VHF_TC 0x08 + + -+struct surfacegen5_vhf_evtctx { ++struct vhf_evtctx { + struct device *dev; + struct hid_device *hid; +}; + -+struct surfacegen5_vhf_drvdata { -+ struct surfacegen5_vhf_evtctx event_ctx; ++struct vhf_drvdata { ++ struct vhf_evtctx event_ctx; +}; + + @@ -2884,7 +4014,7 @@ index 000000000000..5dbf48a3d9b3 +}; + + -+static struct hid_device *surfacegen5_vhf_create_hid_device(struct platform_device *pdev) ++static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) +{ + struct hid_device *hid; + @@ -2901,14 +4031,14 @@ index 000000000000..5dbf48a3d9b3 + + hid->ll_driver = &vhf_hid_ll_driver; + -+ sprintf(hid->name, "%s", SG5_VHF_INPUT_NAME); ++ sprintf(hid->name, "%s", VHF_INPUT_NAME); + + return hid; +} + -+static int surfacegen5_vhf_event_handler(struct surfacegen5_event *event, void *data) ++static int vhf_event_handler(struct surface_sam_ssh_event *event, void *data) +{ -+ struct surfacegen5_vhf_evtctx *ctx = (struct surfacegen5_vhf_evtctx *)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); @@ -2918,34 +4048,34 @@ index 000000000000..5dbf48a3d9b3 + return 0; +} + -+static unsigned long surfacegen5_vhf_event_delay(struct surfacegen5_event *event, void *data) ++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 SURFACEGEN5_EVENT_IMMEDIATE; ++ return SURFACE_SAM_SSH_EVENT_IMMEDIATE; + } + + return 0; +} + -+static int surfacegen5_acpi_vhf_probe(struct platform_device *pdev) ++static int surface_sam_vhf_probe(struct platform_device *pdev) +{ -+ struct surfacegen5_vhf_drvdata *drvdata; ++ struct vhf_drvdata *drvdata; + struct hid_device *hid; + int status; + + // add device link to EC -+ status = surfacegen5_ec_consumer_register(&pdev->dev); ++ status = surface_sam_ssh_consumer_register(&pdev->dev); + if (status) { + return status == -ENXIO ? -EPROBE_DEFER : status; + } + -+ drvdata = kzalloc(sizeof(struct surfacegen5_vhf_drvdata), GFP_KERNEL); ++ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); + if (!drvdata) { + return -ENOMEM; + } + -+ hid = surfacegen5_vhf_create_hid_device(pdev); ++ hid = vhf_create_hid_device(pdev); + if (IS_ERR(hid)) { + status = PTR_ERR(hid); + goto err_probe_hid; @@ -2961,20 +4091,16 @@ index 000000000000..5dbf48a3d9b3 + + 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, ++ 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 = surfacegen5_ec_enable_event_source(SG5_EVENT_VHF_TC, 0x01, SG5_EVENT_VHF_RQID); ++ status = surface_sam_ssh_enable_event_source(SAM_EVENT_VHF_TC, 0x01, SAM_EVENT_VHF_RQID); + if (status) { + goto err_event_source; + } @@ -2982,7 +4108,7 @@ index 000000000000..5dbf48a3d9b3 + return 0; + +err_event_source: -+ surfacegen5_ec_remove_event_handler(SG5_EVENT_VHF_RQID); ++ surface_sam_ssh_remove_event_handler(SAM_EVENT_VHF_RQID); +err_add_hid: + hid_destroy_device(hid); + platform_set_drvdata(pdev, NULL); @@ -2991,12 +4117,12 @@ index 000000000000..5dbf48a3d9b3 + return status; +} + -+static int surfacegen5_acpi_vhf_remove(struct platform_device *pdev) ++static int surface_sam_vhf_remove(struct platform_device *pdev) +{ -+ struct surfacegen5_vhf_drvdata *drvdata = platform_get_drvdata(pdev); ++ struct 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); ++ 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); @@ -3006,1214 +4132,24 @@ index 000000000000..5dbf48a3d9b3 +} + + -+static const struct acpi_device_id surfacegen5_acpi_vhf_match[] = { ++static const struct acpi_device_id surface_sam_vhf_match[] = { + { "MSHW0096" }, + { }, +}; -+MODULE_DEVICE_TABLE(acpi, surfacegen5_acpi_vhf_match); ++MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); + -+struct platform_driver surfacegen5_acpi_vhf = { -+ .probe = surfacegen5_acpi_vhf_probe, -+ .remove = surfacegen5_acpi_vhf_remove, ++struct platform_driver surface_sam_vhf = { ++ .probe = surface_sam_vhf_probe, ++ .remove = surface_sam_vhf_remove, + .driver = { -+ .name = "surfacegen5_acpi_vhf", -+ .acpi_match_table = ACPI_PTR(surfacegen5_acpi_vhf_match), ++ .name = "surface_sam_vhf", ++ .acpi_match_table = ACPI_PTR(surface_sam_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_l4F = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4F, -+}; -+ -+static const struct si_lid_device lid_device_l57 = { -+ .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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SG5_PARAM_PERM); -+module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SG5_PARAM_PERM); -+ -+MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); -+MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ int perf_mode; -+ -+ perf_mode = sg5_ec_perf_mode_get(); -+ if (perf_mode < 0) { -+ dev_err(dev, "failed to get current performance mode: %d", perf_mode); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", perf_mode); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ status = sg5_ec_perf_mode_set(perf_mode); -+ if (status) { -+ return status; -+ } -+ -+ // TODO: Should we notify ACPI here? -+ // -+ // There is a _DSM call described as -+ // WSID._DSM: Notify DPTF on Slider State change -+ // which calls -+ // ODV3 = ToInteger (Arg3) -+ // Notify(IETM, 0x88) -+ // IETM is an INT3400 Intel Dynamic Power Performance Management -+ // device, part of the DPTF framework. From the corresponding -+ // kernel driver, it looks like event 0x88 is being ignored. Also -+ // it is currently unknown what the consequecnes of setting ODV3 -+ // are. -+ -+ return count; -+} -+ -+const static DEVICE_ATTR_RW(perf_mode); -+ -+static int 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_platform_driver(surface_sam_vhf); + +MODULE_AUTHOR("Maximilian Luz "); -+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 diff --git a/patches/4.19/0002-suspend.patch b/patches/4.19/0002-suspend.patch index 018fa31f6..1dca5a50d 100644 --- a/patches/4.19/0002-suspend.patch +++ b/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 --- diff --git a/patches/4.19/0003-buttons.patch b/patches/4.19/0003-buttons.patch index fc3e23a72..f7a1b1418 100644 --- a/patches/4.19/0003-buttons.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:42:15 +0200 +Date: Sat, 27 Jul 2019 17:51:37 +0200 Subject: [PATCH 03/12] buttons --- diff --git a/patches/4.19/0004-cameras.patch b/patches/4.19/0004-cameras.patch index c66788c15..fa7ad7d75 100644 --- a/patches/4.19/0004-cameras.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:42:25 +0200 +Date: Sat, 28 Sep 2019 17:53:54 +0200 Subject: [PATCH 04/12] cameras --- diff --git a/patches/4.19/0005-ipts.patch b/patches/4.19/0005-ipts.patch index ee70a25fa..45804a4fb 100644 --- a/patches/4.19/0005-ipts.patch +++ b/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 +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[] = { diff --git a/patches/4.19/0006-hid.patch b/patches/4.19/0006-hid.patch index cdc5020fe..10f6e6e0b 100644 --- a/patches/4.19/0006-hid.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:42:50 +0200 +Date: Sat, 28 Sep 2019 17:58:43 +0200 Subject: [PATCH 06/12] hid --- diff --git a/patches/4.19/0007-sdcard-reader.patch b/patches/4.19/0007-sdcard-reader.patch index d0092db56..53071f2d4 100644 --- a/patches/4.19/0007-sdcard-reader.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:43:03 +0200 +Date: Sat, 28 Sep 2019 17:59:13 +0200 Subject: [PATCH 07/12] sdcard-reader --- diff --git a/patches/4.19/0008-wifi.patch b/patches/4.19/0008-wifi.patch index b9a907905..a612648e1 100644 --- a/patches/4.19/0008-wifi.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:43:14 +0200 +Date: Sat, 28 Sep 2019 18:00:19 +0200 Subject: [PATCH 08/12] wifi --- diff --git a/patches/4.19/0009-surface3-power.patch b/patches/4.19/0009-surface3-power.patch index 57db84c35..39c233144 100644 --- a/patches/4.19/0009-surface3-power.patch +++ b/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 -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 diff --git a/patches/4.19/0010-mwlwifi.patch b/patches/4.19/0010-mwlwifi.patch index 67fd972cc..af64ed7ca 100644 --- a/patches/4.19/0010-mwlwifi.patch +++ b/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 -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 + 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 diff --git a/patches/4.19/0011-surface-lte.patch b/patches/4.19/0011-surface-lte.patch index 00adf5eb4..2008ab637 100644 --- a/patches/4.19/0011-surface-lte.patch +++ b/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 -Date: Fri, 26 Jul 2019 03:43:57 +0200 +Date: Sat, 28 Sep 2019 18:02:03 +0200 Subject: [PATCH 11/12] surface-lte --- diff --git a/patches/4.19/0012-surfacebook2-dgpu.patch b/patches/4.19/0012-surfacebook2-dgpu.patch index 8b5e92297..ab2c62a0d 100644 --- a/patches/4.19/0012-surfacebook2-dgpu.patch +++ b/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 -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 diff --git a/patches/5.3/0001-surface-acpi.patch b/patches/5.3/0001-surface-acpi.patch index 9f3fb59af..6d28b1f2f 100644 --- a/patches/5.3/0001-surface-acpi.patch +++ b/patches/5.3/0001-surface-acpi.patch @@ -1,5 +1,5 @@ -From c7543d2458c5282bda2802701324cc77478146d8 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From 140c70aff419f61a2c2b6cca9fe2c1c5ade3629d Mon Sep 17 00:00:00 2001 +From: qzed Date: Mon, 26 Aug 2019 01:11:08 +0200 Subject: [PATCH 1/9] surface-acpi diff --git a/patches/5.3/0002-buttons.patch b/patches/5.3/0002-buttons.patch index 8f2d18304..2ad7387de 100644 --- a/patches/5.3/0002-buttons.patch +++ b/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 Date: Sat, 27 Jul 2019 17:51:37 +0200 Subject: [PATCH 2/9] buttons diff --git a/patches/5.3/0003-surfacebook2-dgpu.patch b/patches/5.3/0003-surfacebook2-dgpu.patch index d5b23dab5..f796d9128 100644 --- a/patches/5.3/0003-surfacebook2-dgpu.patch +++ b/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 Date: Tue, 2 Jul 2019 22:17:46 +0200 Subject: [PATCH 3/9] surfacebook2-dgpu diff --git a/patches/5.3/0004-hid.patch b/patches/5.3/0004-hid.patch index b216612c3..b23357d43 100644 --- a/patches/5.3/0004-hid.patch +++ b/patches/5.3/0004-hid.patch @@ -1,5 +1,5 @@ -From ea18400e859c6d7656b81e5d33314046743d34a1 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From 487a63fd1eb7055ae79248e60d9688cb81c0f30c Mon Sep 17 00:00:00 2001 +From: qzed Date: Tue, 17 Sep 2019 17:16:23 +0200 Subject: [PATCH 4/9] hid diff --git a/patches/5.3/0005-surface3-power.patch b/patches/5.3/0005-surface3-power.patch index 850f0edef..3eb20fa23 100644 --- a/patches/5.3/0005-surface3-power.patch +++ b/patches/5.3/0005-surface3-power.patch @@ -1,5 +1,5 @@ -From a39f413e5460c0d1fb901872518a33038b9d463c Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From 4f79244298b003c92cd9590b767769d5b84b1ee1 Mon Sep 17 00:00:00 2001 +From: qzed Date: Tue, 17 Sep 2019 17:17:56 +0200 Subject: [PATCH 5/9] surface3-power diff --git a/patches/5.3/0006-surface-lte.patch b/patches/5.3/0006-surface-lte.patch index 19d0cb853..04e4edbc7 100644 --- a/patches/5.3/0006-surface-lte.patch +++ b/patches/5.3/0006-surface-lte.patch @@ -1,5 +1,5 @@ -From 83e53749458cab7575105ddbae13f750a4cc09e4 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From e260eacab655d64cf9c1489dc37f89c410aa58a0 Mon Sep 17 00:00:00 2001 +From: qzed Date: Tue, 17 Sep 2019 17:21:43 +0200 Subject: [PATCH 6/9] surface-lte diff --git a/patches/5.3/0007-wifi.patch b/patches/5.3/0007-wifi.patch index b8e71bdd1..136ba74a4 100644 --- a/patches/5.3/0007-wifi.patch +++ b/patches/5.3/0007-wifi.patch @@ -1,5 +1,5 @@ -From 829c222814c0617dad16d036876f94c725d2d638 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From 2a01efb959a43a387dab97223bcccfe6be4e582f Mon Sep 17 00:00:00 2001 +From: qzed Date: Wed, 18 Sep 2019 03:18:25 +0200 Subject: [PATCH 7/9] wifi diff --git a/patches/5.3/0008-legacy-i915.patch b/patches/5.3/0008-legacy-i915.patch index 448fd4ee6..36a30ae18 100644 --- a/patches/5.3/0008-legacy-i915.patch +++ b/patches/5.3/0008-legacy-i915.patch @@ -1,5 +1,5 @@ -From 05296f2e99a9f6c9ef6fa47e7c879c73b07c7322 Mon Sep 17 00:00:00 2001 -From: Maximilian Luz +From cc0158b2e542fcb3c9f1102227e4b35316c25040 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll Date: Mon, 16 Sep 2019 04:10:51 +0200 Subject: [PATCH 8/9] legacy-i915 diff --git a/patches/5.3/0009-ipts.patch b/patches/5.3/0009-ipts.patch index 187c333c4..f49eb071d 100644 --- a/patches/5.3/0009-ipts.patch +++ b/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 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 +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +-#include +- +- +-#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, +- ¶m, 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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SG5_PARAM_PERM); +-module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SG5_PARAM_PERM); +- +-MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); +-MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); +- +-static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) +-{ +- int perf_mode; +- +- perf_mode = sg5_ec_perf_mode_get(); +- if (perf_mode < 0) { +- dev_err(dev, "failed to get current performance mode: %d", perf_mode); +- return -EIO; +- } +- +- return sprintf(data, "%d\n", perf_mode); +-} +- +-static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, +- const char *data, size_t count) +-{ +- int perf_mode; +- int status; +- +- status = kstrtoint(data, 0, &perf_mode); +- if (status) { +- return status; +- } +- +- status = sg5_ec_perf_mode_set(perf_mode); +- if (status) { +- return status; +- } +- +- // TODO: Should we notify ACPI here? +- // +- // There is a _DSM call described as +- // WSID._DSM: Notify DPTF on Slider State change +- // which calls +- // ODV3 = ToInteger (Arg3) +- // Notify(IETM, 0x88) +- // IETM is an INT3400 Intel Dynamic Power Performance Management +- // device, part of the DPTF framework. From the corresponding +- // kernel driver, it looks like event 0x88 is being ignored. Also +- // it is currently unknown what the consequecnes of setting ODV3 +- // are. +- +- return count; +-} +- +-const static DEVICE_ATTR_RW(perf_mode); +- +-static int 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 "); +-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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++#include ++#include ++#include ++#include ++ ++#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, ++ ¶m, 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 "); ++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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_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 "); ++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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++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 "); ++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 ++#include ++ ++ ++/* ++ * 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 ++#include ++#include ++#include ++#include ++ ++#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 "); ++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