Selaa lähdekoodia

Add patches for v5.12

Links:
 - kernel: https://github.com/linux-surface/kernel/commit/b8227ed525b8a609ae311c7c7d310498c29e94fd
 - SAM: https://github.com/linux-surface/surface-aggregator-module/commit/77eadd698cb8bf8dd3fe40873f4bf168bcd8e7e1
 - SAM-gen4: https://github.com/linux-surface/surface-aggregator-module-gen4/commit/e321205faaf5f39675ccb8c2314b171c2319b9bc
 - IPTS: https://github.com/linux-surface/intel-precise-touch/commit/e66f2fe9fe79732c8083104413300cfa36f5b579
Maximilian Luz 4 vuotta sitten
vanhempi
commit
ad85768061

+ 60 - 0
configs/surface-5.12.config

@@ -0,0 +1,60 @@
+#
+# Surface Aggregator Module
+#
+CONFIG_SURFACE_AGGREGATOR=m
+CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION=n
+CONFIG_SURFACE_AGGREGATOR_BUS=y
+CONFIG_SURFACE_AGGREGATOR_CDEV=m
+CONFIG_SURFACE_AGGREGATOR_REGISTRY=m
+
+CONFIG_SURFACE_ACPI_NOTIFY=m
+CONFIG_SURFACE_DTX=m
+CONFIG_SURFACE_PLATFORM_PROFILE=m
+
+CONFIG_SURFACE_HID=m
+CONFIG_SURFACE_KBD=m
+
+CONFIG_BATTERY_SURFACE=m
+CONFIG_CHARGER_SURFACE=m
+
+#
+# Surface Hotplug
+#
+CONFIG_SURFACE_HOTPLUG=m
+
+#
+# IPTS touchscreen
+#
+# This only enables the user interface for IPTS data.
+# For the touchscreen to work, you need to install iptsd.
+#
+CONFIG_MISC_IPTS=m
+
+#
+# Cameras: IPU3
+#
+CONFIG_VIDEO_IPU3_IMGU=m
+CONFIG_VIDEO_IPU3_CIO2=m
+CONFIG_CIO2_BRIDGE=y
+CONFIG_INTEL_SKL_INT3472=m
+
+#
+# Cameras: Sensor drivers
+#
+CONFIG_VIDEO_OV5693=m
+CONFIG_VIDEO_OV8865=m
+
+#
+# ALS Sensor for Surface Book 3, Surface Laptop 3, Surface Pro 7
+#
+CONFIG_APDS9960=m
+
+#
+# Other Drivers
+#
+CONFIG_INPUT_SOC_BUTTON_ARRAY=m
+CONFIG_SURFACE_3_BUTTON=m
+CONFIG_SURFACE_3_POWER_OPREGION=m
+CONFIG_SURFACE_PRO3_BUTTON=m
+CONFIG_SURFACE_GPE=m
+CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m

+ 101 - 0
patches/5.12/0001-surface3-oemb.patch

@@ -0,0 +1,101 @@
+From 3c43528f8c4fa28ea93781f553014a2c5b709396 Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Sun, 18 Oct 2020 16:42:44 +0900
+Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI
+ table
+
+On some Surface 3, the DMI table gets corrupted for unknown reasons
+and breaks existing DMI matching used for device-specific quirks.
+
+This commit adds the (broken) DMI data into dmi_system_id tables used
+for quirks so that each driver can enable quirks even on the affected
+systems.
+
+On affected systems, DMI data will look like this:
+    $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\
+    chassis_vendor,product_name,sys_vendor}
+    /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc.
+    /sys/devices/virtual/dmi/id/board_name:OEMB
+    /sys/devices/virtual/dmi/id/board_vendor:OEMB
+    /sys/devices/virtual/dmi/id/chassis_vendor:OEMB
+    /sys/devices/virtual/dmi/id/product_name:OEMB
+    /sys/devices/virtual/dmi/id/sys_vendor:OEMB
+
+Expected:
+    $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\
+    chassis_vendor,product_name,sys_vendor}
+    /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc.
+    /sys/devices/virtual/dmi/id/board_name:Surface 3
+    /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation
+    /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation
+    /sys/devices/virtual/dmi/id/product_name:Surface 3
+    /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: surface3-oemb
+---
+ drivers/platform/surface/surface3-wmi.c           | 7 +++++++
+ sound/soc/codecs/rt5645.c                         | 9 +++++++++
+ sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++
+ 3 files changed, 24 insertions(+)
+
+diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c
+index fcd1d4fb94d5..ee26a5998b07 100644
+--- a/drivers/platform/surface/surface3-wmi.c
++++ b/drivers/platform/surface/surface3-wmi.c
+@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = {
+ 			DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ 		},
+ 	},
++	{
++		.matches = {
++			DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
++			DMI_MATCH(DMI_SYS_VENDOR, "OEMB"),
++			DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"),
++		},
++	},
+ #endif
+ 	{ }
+ };
+diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c
+index 63a7e052eaa0..9806fd800020 100644
+--- a/sound/soc/codecs/rt5645.c
++++ b/sound/soc/codecs/rt5645.c
+@@ -3694,6 +3694,15 @@ static const struct dmi_system_id dmi_platform_data[] = {
+ 		},
+ 		.driver_data = (void *)&intel_braswell_platform_data,
+ 	},
++	{
++		.ident = "Microsoft Surface 3",
++		.matches = {
++			DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
++			DMI_MATCH(DMI_SYS_VENDOR, "OEMB"),
++			DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"),
++		},
++		.driver_data = (void *)&intel_braswell_platform_data,
++	},
+ 	{
+ 		/*
+ 		 * Match for the GPDwin which unfortunately uses somewhat
+diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c
+index 227424236fd5..1013a57be89a 100644
+--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c
++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c
+@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = {
+ 			DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ 		},
+ 	},
++	{
++		.callback = cht_surface_quirk_cb,
++		.matches = {
++			DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
++			DMI_MATCH(DMI_SYS_VENDOR, "OEMB"),
++			DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"),
++		},
++	},
+ 	{ }
+ };
+ 
+-- 
+2.31.1
+

+ 2520 - 0
patches/5.12/0002-mwifiex.patch

@@ -0,0 +1,2520 @@
+From 839c1d7437de97b253482ab0526200330a87b49e Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Mon, 28 Sep 2020 17:46:49 +0900
+Subject: [PATCH] mwifiex: pcie: add DMI-based quirk impl for Surface devices
+
+This commit adds quirk implementation based on DMI matching with DMI
+table for Surface devices.
+
+This implementation can be used for quirks later.
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/Makefile |   1 +
+ drivers/net/wireless/marvell/mwifiex/pcie.c   |   4 +
+ drivers/net/wireless/marvell/mwifiex/pcie.h   |   1 +
+ .../wireless/marvell/mwifiex/pcie_quirks.c    | 114 ++++++++++++++++++
+ .../wireless/marvell/mwifiex/pcie_quirks.h    |  11 ++
+ 5 files changed, 131 insertions(+)
+ create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+ create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/Makefile b/drivers/net/wireless/marvell/mwifiex/Makefile
+index 162d557b78af..2bd00f40958e 100644
+--- a/drivers/net/wireless/marvell/mwifiex/Makefile
++++ b/drivers/net/wireless/marvell/mwifiex/Makefile
+@@ -49,6 +49,7 @@ mwifiex_sdio-y += sdio.o
+ obj-$(CONFIG_MWIFIEX_SDIO) += mwifiex_sdio.o
+ 
+ mwifiex_pcie-y += pcie.o
++mwifiex_pcie-y += pcie_quirks.o
+ obj-$(CONFIG_MWIFIEX_PCIE) += mwifiex_pcie.o
+ 
+ mwifiex_usb-y += usb.o
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index 94228b316df1..02fdce926de5 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -27,6 +27,7 @@
+ #include "wmm.h"
+ #include "11n.h"
+ #include "pcie.h"
++#include "pcie_quirks.h"
+ 
+ #define PCIE_VERSION	"1.0"
+ #define DRV_NAME        "Marvell mwifiex PCIe"
+@@ -410,6 +411,9 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev,
+ 			return ret;
+ 	}
+ 
++	/* check quirks */
++	mwifiex_initialize_quirks(card);
++
+ 	if (mwifiex_add_card(card, &card->fw_done, &pcie_ops,
+ 			     MWIFIEX_PCIE, &pdev->dev)) {
+ 		pr_err("%s failed\n", __func__);
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.h b/drivers/net/wireless/marvell/mwifiex/pcie.h
+index 5ed613d65709..981e330c77d7 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.h
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.h
+@@ -244,6 +244,7 @@ struct pcie_service_card {
+ 	unsigned long work_flags;
+ 
+ 	bool pci_reset_ongoing;
++	unsigned long quirks;
+ };
+ 
+ static inline int
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+new file mode 100644
+index 000000000000..929aee2b0a60
+--- /dev/null
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -0,0 +1,114 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * File for PCIe quirks.
++ */
++
++/* The low-level PCI operations will be performed in this file. Therefore,
++ * let's use dev_*() instead of mwifiex_dbg() here to avoid troubles (e.g.
++ * to avoid using mwifiex_adapter struct before init or wifi is powered
++ * down, or causes NULL ptr deref).
++ */
++
++#include <linux/dmi.h>
++
++#include "pcie_quirks.h"
++
++/* quirk table based on DMI matching */
++static const struct dmi_system_id mwifiex_quirk_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 = 0,
++	},
++	{
++		.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 = 0,
++	},
++	{
++		.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 = 0,
++	},
++	{
++		.ident = "Surface Pro 6",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface Book 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface Book 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface Laptop 1",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface Laptop 2",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface 3",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
++		},
++		.driver_data = 0,
++	},
++	{
++		.ident = "Surface Pro 3",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 3"),
++		},
++		.driver_data = 0,
++	},
++	{}
++};
++
++void mwifiex_initialize_quirks(struct pcie_service_card *card)
++{
++	struct pci_dev *pdev = card->dev;
++	const struct dmi_system_id *dmi_id;
++
++	dmi_id = dmi_first_match(mwifiex_quirk_table);
++	if (dmi_id)
++		card->quirks = (uintptr_t)dmi_id->driver_data;
++
++	if (!card->quirks)
++		dev_info(&pdev->dev, "no quirks enabled\n");
++}
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+new file mode 100644
+index 000000000000..5326ae7e5671
+--- /dev/null
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+@@ -0,0 +1,11 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/*
++ * Header file for PCIe quirks.
++ */
++
++#include "pcie.h"
++
++/* quirks */
++// quirk flags can be added here
++
++void mwifiex_initialize_quirks(struct pcie_service_card *card);
+-- 
+2.31.1
+
+From acdc19b03d9c4f21fe4a2dfa0cf3c1f8cf85f083 Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Tue, 29 Sep 2020 17:25:22 +0900
+Subject: [PATCH] mwifiex: pcie: add reset_d3cold quirk for Surface gen4+
+ devices
+
+To reset mwifiex on Surface gen4+ (Pro 4 or later gen) devices, it
+seems that putting the wifi device into D3cold is required according
+to errata.inf file on Windows installation (Windows/INF/errata.inf).
+
+This patch adds a function that performs power-cycle (put into D3cold
+then D0) and call the function at the end of reset_prepare().
+
+Note: Need to also reset the parent device (bridge) of wifi on SB1;
+it might be because the bridge of wifi always reports it's in D3hot.
+When I tried to reset only the wifi device (not touching parent), it gave
+the following error and the reset failed:
+
+    acpi device:4b: Cannot transition to power state D0 for parent in D3hot
+    mwifiex_pcie 0000:03:00.0: can't change power state from D3cold to D0 (config space inaccessible)
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c   |  7 ++
+ .../wireless/marvell/mwifiex/pcie_quirks.c    | 73 +++++++++++++++++--
+ .../wireless/marvell/mwifiex/pcie_quirks.h    |  3 +-
+ 3 files changed, 74 insertions(+), 9 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index 02fdce926de5..d9acfea395ad 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -528,6 +528,13 @@ static void mwifiex_pcie_reset_prepare(struct pci_dev *pdev)
+ 	mwifiex_shutdown_sw(adapter);
+ 	clear_bit(MWIFIEX_IFACE_WORK_DEVICE_DUMP, &card->work_flags);
+ 	clear_bit(MWIFIEX_IFACE_WORK_CARD_RESET, &card->work_flags);
++
++	/* For Surface gen4+ devices, we need to put wifi into D3cold right
++	 * before performing FLR
++	 */
++	if (card->quirks & QUIRK_FW_RST_D3COLD)
++		mwifiex_pcie_reset_d3cold_quirk(pdev);
++
+ 	mwifiex_dbg(adapter, INFO, "%s, successful\n", __func__);
+ 
+ 	card->pci_reset_ongoing = true;
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+index 929aee2b0a60..edc739c542fe 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -21,7 +21,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5",
+@@ -30,7 +30,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5 (LTE)",
+@@ -39,7 +39,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Pro 6",
+@@ -47,7 +47,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Book 1",
+@@ -55,7 +55,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Book 2",
+@@ -63,7 +63,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 1",
+@@ -71,7 +71,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 2",
+@@ -79,7 +79,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
+ 	},
+ 	{
+ 		.ident = "Surface 3",
+@@ -111,4 +111,61 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card)
+ 
+ 	if (!card->quirks)
+ 		dev_info(&pdev->dev, "no quirks enabled\n");
++	if (card->quirks & QUIRK_FW_RST_D3COLD)
++		dev_info(&pdev->dev, "quirk reset_d3cold enabled\n");
++}
++
++static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev)
++{
++	dev_info(&pdev->dev, "putting into D3cold...\n");
++
++	pci_save_state(pdev);
++	if (pci_is_enabled(pdev))
++		pci_disable_device(pdev);
++	pci_set_power_state(pdev, PCI_D3cold);
++}
++
++static int mwifiex_pcie_set_power_d0(struct pci_dev *pdev)
++{
++	int ret;
++
++	dev_info(&pdev->dev, "putting into D0...\n");
++
++	pci_set_power_state(pdev, PCI_D0);
++	ret = pci_enable_device(pdev);
++	if (ret) {
++		dev_err(&pdev->dev, "pci_enable_device failed\n");
++		return ret;
++	}
++	pci_restore_state(pdev);
++
++	return 0;
++}
++
++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev)
++{
++	struct pci_dev *parent_pdev = pci_upstream_bridge(pdev);
++	int ret;
++
++	/* Power-cycle (put into D3cold then D0) */
++	dev_info(&pdev->dev, "Using reset_d3cold quirk to perform FW reset\n");
++
++	/* We need to perform power-cycle also for bridge of wifi because
++	 * on some devices (e.g. Surface Book 1), the OS for some reasons
++	 * can't know the real power state of the bridge.
++	 * When tried to power-cycle only wifi, the reset failed with the
++	 * following dmesg log:
++	 * "Cannot transition to power state D0 for parent in D3hot".
++	 */
++	mwifiex_pcie_set_power_d3cold(pdev);
++	mwifiex_pcie_set_power_d3cold(parent_pdev);
++
++	ret = mwifiex_pcie_set_power_d0(parent_pdev);
++	if (ret)
++		return ret;
++	ret = mwifiex_pcie_set_power_d0(pdev);
++	if (ret)
++		return ret;
++
++	return 0;
+ }
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+index 5326ae7e5671..8b9dcb5070d8 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+@@ -6,6 +6,7 @@
+ #include "pcie.h"
+ 
+ /* quirks */
+-// quirk flags can be added here
++#define QUIRK_FW_RST_D3COLD	BIT(0)
+ 
+ void mwifiex_initialize_quirks(struct pcie_service_card *card);
++int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev);
+-- 
+2.31.1
+
+From 609432b3d8ffc0875aa1662633324ae5d202e41f Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Tue, 29 Sep 2020 17:32:22 +0900
+Subject: [PATCH] mwifiex: pcie: add reset_wsid quirk for Surface 3
+
+This commit adds reset_wsid quirk and uses this quirk for Surface 3 on
+card reset.
+
+To reset mwifiex on Surface 3, it seems that calling the _DSM method
+exists in \_SB.WSID [1] device is required.
+
+On Surface 3, calling the _DSM method removes/re-probes the card by
+itself. So, need to place the reset function before performing FLR and
+skip performing any other reset-related works.
+
+Note that Surface Pro 3 also has the WSID device [2], but it seems to need
+more work. This commit only supports Surface 3 yet.
+
+[1] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_3/dsdt.dsl#L11947-L12011
+[2] https://github.com/linux-surface/acpidumps/blob/05cba925f3a515f222acb5b3551a032ddde958fe/surface_pro_3/dsdt.dsl#L12164-L12216
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c   | 10 +++
+ .../wireless/marvell/mwifiex/pcie_quirks.c    | 77 ++++++++++++++++++-
+ .../wireless/marvell/mwifiex/pcie_quirks.h    |  5 ++
+ 3 files changed, 91 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index d9acfea395ad..6e049236ae1a 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -2969,6 +2969,16 @@ static void mwifiex_pcie_card_reset_work(struct mwifiex_adapter *adapter)
+ {
+ 	struct pcie_service_card *card = adapter->card;
+ 
++	/* On Surface 3, reset_wsid method removes then re-probes card by
++	 * itself. So, need to place it here and skip performing any other
++	 * reset-related works.
++	 */
++	if (card->quirks & QUIRK_FW_RST_WSID_S3) {
++		mwifiex_pcie_reset_wsid_quirk(card->dev);
++		/* skip performing any other reset-related works */
++		return;
++	}
++
+ 	/* We can't afford to wait here; remove() might be waiting on us. If we
+ 	 * can't grab the device lock, maybe we'll get another chance later.
+ 	 */
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+index edc739c542fe..f0a6fa0a7ae5 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -9,10 +9,21 @@
+  * down, or causes NULL ptr deref).
+  */
+ 
++#include <linux/acpi.h>
+ #include <linux/dmi.h>
+ 
+ #include "pcie_quirks.h"
+ 
++/* For reset_wsid quirk */
++#define ACPI_WSID_PATH		"\\_SB.WSID"
++#define WSID_REV		0x0
++#define WSID_FUNC_WIFI_PWR_OFF	0x1
++#define WSID_FUNC_WIFI_PWR_ON	0x2
++/* WSID _DSM UUID: "534ea3bf-fcc2-4e7a-908f-a13978f0c7ef" */
++static const guid_t wsid_dsm_guid =
++	GUID_INIT(0x534ea3bf, 0xfcc2, 0x4e7a,
++		  0x90, 0x8f, 0xa1, 0x39, 0x78, 0xf0, 0xc7, 0xef);
++
+ /* quirk table based on DMI matching */
+ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 	{
+@@ -87,7 +98,7 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"),
+ 		},
+-		.driver_data = 0,
++		.driver_data = (void *)QUIRK_FW_RST_WSID_S3,
+ 	},
+ 	{
+ 		.ident = "Surface Pro 3",
+@@ -113,6 +124,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card)
+ 		dev_info(&pdev->dev, "no quirks enabled\n");
+ 	if (card->quirks & QUIRK_FW_RST_D3COLD)
+ 		dev_info(&pdev->dev, "quirk reset_d3cold enabled\n");
++	if (card->quirks & QUIRK_FW_RST_WSID_S3)
++		dev_info(&pdev->dev,
++			 "quirk reset_wsid for Surface 3 enabled\n");
+ }
+ 
+ static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev)
+@@ -169,3 +183,64 @@ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev)
+ 
+ 	return 0;
+ }
++
++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev)
++{
++	acpi_handle handle;
++	union acpi_object *obj;
++	acpi_status status;
++
++	dev_info(&pdev->dev, "Using reset_wsid quirk to perform FW reset\n");
++
++	status = acpi_get_handle(NULL, ACPI_WSID_PATH, &handle);
++	if (ACPI_FAILURE(status)) {
++		dev_err(&pdev->dev, "No ACPI handle for path %s\n",
++			ACPI_WSID_PATH);
++		return -ENODEV;
++	}
++
++	if (!acpi_has_method(handle, "_DSM")) {
++		dev_err(&pdev->dev, "_DSM method not found\n");
++		return -ENODEV;
++	}
++
++	if (!acpi_check_dsm(handle, &wsid_dsm_guid,
++			    WSID_REV, WSID_FUNC_WIFI_PWR_OFF)) {
++		dev_err(&pdev->dev,
++			"_DSM method doesn't support wifi power off func\n");
++		return -ENODEV;
++	}
++
++	if (!acpi_check_dsm(handle, &wsid_dsm_guid,
++			    WSID_REV, WSID_FUNC_WIFI_PWR_ON)) {
++		dev_err(&pdev->dev,
++			"_DSM method doesn't support wifi power on func\n");
++		return -ENODEV;
++	}
++
++	/* card will be removed immediately after this call on Surface 3 */
++	dev_info(&pdev->dev, "turning wifi off...\n");
++	obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid,
++				WSID_REV, WSID_FUNC_WIFI_PWR_OFF,
++				NULL);
++	if (!obj) {
++		dev_err(&pdev->dev,
++			"device _DSM execution failed for turning wifi off\n");
++		return -EIO;
++	}
++	ACPI_FREE(obj);
++
++	/* card will be re-probed immediately after this call on Surface 3 */
++	dev_info(&pdev->dev, "turning wifi on...\n");
++	obj = acpi_evaluate_dsm(handle, &wsid_dsm_guid,
++				WSID_REV, WSID_FUNC_WIFI_PWR_ON,
++				NULL);
++	if (!obj) {
++		dev_err(&pdev->dev,
++			"device _DSM execution failed for turning wifi on\n");
++		return -EIO;
++	}
++	ACPI_FREE(obj);
++
++	return 0;
++}
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+index 8b9dcb5070d8..3ef7440418e3 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+@@ -7,6 +7,11 @@
+ 
+ /* quirks */
+ #define QUIRK_FW_RST_D3COLD	BIT(0)
++/* Surface 3 and Surface Pro 3 have the same _DSM method but need to
++ * be handled differently. Currently, only S3 is supported.
++ */
++#define QUIRK_FW_RST_WSID_S3	BIT(1)
+ 
+ void mwifiex_initialize_quirks(struct pcie_service_card *card);
+ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev);
++int mwifiex_pcie_reset_wsid_quirk(struct pci_dev *pdev);
+-- 
+2.31.1
+
+From 24a1ae55645ca4d7d2bde47d1943b4535e656566 Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Wed, 30 Sep 2020 18:08:24 +0900
+Subject: [PATCH] mwifiex: pcie: (OEMB) add quirk for Surface 3 with broken DMI
+ table
+
+(made referring to http://git.osdn.net/view?p=android-x86/kernel.git;a=commitdiff;h=18e2e857c57633b25b3b4120f212224a108cd883)
+
+On some Surface 3, the DMI table gets corrupted for unknown reasons
+and breaks existing DMI matching used for device-specific quirks.
+
+This commit adds the (broken) DMI info for the affected Surface 3.
+
+On affected systems, DMI info will look like this:
+    $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\
+    chassis_vendor,product_name,sys_vendor}
+    /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc.
+    /sys/devices/virtual/dmi/id/board_name:OEMB
+    /sys/devices/virtual/dmi/id/board_vendor:OEMB
+    /sys/devices/virtual/dmi/id/chassis_vendor:OEMB
+    /sys/devices/virtual/dmi/id/product_name:OEMB
+    /sys/devices/virtual/dmi/id/sys_vendor:OEMB
+
+Expected:
+    $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\
+    chassis_vendor,product_name,sys_vendor}
+    /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc.
+    /sys/devices/virtual/dmi/id/board_name:Surface 3
+    /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation
+    /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation
+    /sys/devices/virtual/dmi/id/product_name:Surface 3
+    /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie_quirks.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+index f0a6fa0a7ae5..34dcd84f02a6 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -100,6 +100,15 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 		},
+ 		.driver_data = (void *)QUIRK_FW_RST_WSID_S3,
+ 	},
++	{
++		.ident = "Surface 3",
++		.matches = {
++			DMI_EXACT_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."),
++			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "OEMB"),
++			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OEMB"),
++		},
++		.driver_data = (void *)QUIRK_FW_RST_WSID_S3,
++	},
+ 	{
+ 		.ident = "Surface Pro 3",
+ 		.matches = {
+-- 
+2.31.1
+
+From 0bfa4441da97c18a79e9466e4f796b5ff5f084d9 Mon Sep 17 00:00:00 2001
+From: Tsuchiya Yuto <kitakar@gmail.com>
+Date: Sun, 4 Oct 2020 00:11:49 +0900
+Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+
+
+Currently, mwifiex fw will crash after suspend on recent kernel series.
+On Windows, it seems that the root port of wifi will never enter D3 state
+(stay on D0 state). And on Linux, disabling the D3 state for the
+bridge fixes fw crashing after suspend.
+
+This commit disables the D3 state of root port on driver initialization
+and fixes fw crashing after suspend.
+
+Signed-off-by: Tsuchiya Yuto <kitakar@gmail.com>
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c   |  7 +++++
+ .../wireless/marvell/mwifiex/pcie_quirks.c    | 27 +++++++++++++------
+ .../wireless/marvell/mwifiex/pcie_quirks.h    |  1 +
+ 3 files changed, 27 insertions(+), 8 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index 6e049236ae1a..d027c875d7a0 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -379,6 +379,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev,
+ 					const struct pci_device_id *ent)
+ {
+ 	struct pcie_service_card *card;
++	struct pci_dev *parent_pdev = pci_upstream_bridge(pdev);
+ 	int ret;
+ 
+ 	pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n",
+@@ -420,6 +421,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev,
+ 		return -1;
+ 	}
+ 
++	/* disable bridge_d3 for Surface gen4+ devices to fix fw crashing
++	 * after suspend
++	 */
++	if (card->quirks & QUIRK_NO_BRIDGE_D3)
++		parent_pdev->bridge_d3 = false;
++
+ 	return 0;
+ }
+ 
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+index 34dcd84f02a6..a2aeb2af907e 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -32,7 +32,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5",
+@@ -41,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5 (LTE)",
+@@ -50,7 +52,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 6",
+@@ -58,7 +61,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Book 1",
+@@ -66,7 +70,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Book 2",
+@@ -74,7 +79,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 1",
+@@ -82,7 +88,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 2",
+@@ -90,7 +97,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
+ 		},
+-		.driver_data = (void *)QUIRK_FW_RST_D3COLD,
++		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
++					QUIRK_NO_BRIDGE_D3),
+ 	},
+ 	{
+ 		.ident = "Surface 3",
+@@ -136,6 +144,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card)
+ 	if (card->quirks & QUIRK_FW_RST_WSID_S3)
+ 		dev_info(&pdev->dev,
+ 			 "quirk reset_wsid for Surface 3 enabled\n");
++	if (card->quirks & QUIRK_NO_BRIDGE_D3)
++		dev_info(&pdev->dev,
++			 "quirk no_brigde_d3 enabled\n");
+ }
+ 
+ static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev)
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+index 3ef7440418e3..a95ebac06e13 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+@@ -11,6 +11,7 @@
+  * be handled differently. Currently, only S3 is supported.
+  */
+ #define QUIRK_FW_RST_WSID_S3	BIT(1)
++#define QUIRK_NO_BRIDGE_D3	BIT(2)
+ 
+ void mwifiex_initialize_quirks(struct pcie_service_card *card);
+ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev);
+-- 
+2.31.1
+
+From b0c566722c6b7ce45b68f67c6b94554fbc3f12fb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 3 Nov 2020 13:28:04 +0100
+Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface
+ devices
+
+The most recent firmware of the 88W8897 card reports a hardcoded LTR
+value to the system during initialization, probably as an (unsuccessful)
+attempt of the developers to fix firmware crashes. This LTR value
+prevents most of the Microsoft Surface devices from entering deep
+powersaving states (either platform C-State 10 or S0ix state), because
+the exit latency of that state would be higher than what the card can
+tolerate.
+
+Turns out the card works just the same (including the firmware crashes)
+no matter if that hardcoded LTR value is reported or not, so it's kind
+of useless and only prevents us from saving power.
+
+To get rid of those hardcoded LTR reports, it's possible to reset the
+PCI bridge device after initializing the cards firmware. I'm not exactly
+sure why that works, maybe the power management subsystem of the PCH
+resets its stored LTR values when doing a function level reset of the
+bridge device. Doing the reset once after starting the wifi firmware
+works very well, probably because the firmware only reports that LTR
+value a single time during firmware startup.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c   | 12 +++++++++
+ .../wireless/marvell/mwifiex/pcie_quirks.c    | 26 +++++++++++++------
+ .../wireless/marvell/mwifiex/pcie_quirks.h    |  1 +
+ 3 files changed, 31 insertions(+), 8 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index d027c875d7a0..8a99e243aff2 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -1757,9 +1757,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb)
+ static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter)
+ {
+ 	struct pcie_service_card *card = adapter->card;
++	struct pci_dev *pdev = card->dev;
++	struct pci_dev *parent_pdev = pci_upstream_bridge(pdev);
+ 	const struct mwifiex_pcie_card_reg *reg = card->pcie.reg;
+ 	int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask;
+ 
++	/* Trigger a function level reset of the PCI bridge device, this makes
++	 * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value
++	 * that prevents the system from entering package C10 and S0ix powersaving
++	 * states.
++	 * We need to do it here because it must happen after firmware
++	 * initialization and this function is called after that is done.
++	 */
++	if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE)
++		pci_reset_function(parent_pdev);
++
+ 	/* Write the RX ring read pointer in to reg->rx_rdptr */
+ 	if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr |
+ 			      tx_wrap)) {
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+index a2aeb2af907e..6885575826a6 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c
+@@ -33,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5",
+@@ -43,7 +44,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 5 (LTE)",
+@@ -53,7 +55,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Pro 6",
+@@ -62,7 +65,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Book 1",
+@@ -71,7 +75,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Book 2",
+@@ -80,7 +85,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 1",
+@@ -89,7 +95,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface Laptop 2",
+@@ -98,7 +105,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = {
+ 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"),
+ 		},
+ 		.driver_data = (void *)(QUIRK_FW_RST_D3COLD |
+-					QUIRK_NO_BRIDGE_D3),
++					QUIRK_NO_BRIDGE_D3 |
++					QUIRK_DO_FLR_ON_BRIDGE),
+ 	},
+ 	{
+ 		.ident = "Surface 3",
+@@ -147,6 +155,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card)
+ 	if (card->quirks & QUIRK_NO_BRIDGE_D3)
+ 		dev_info(&pdev->dev,
+ 			 "quirk no_brigde_d3 enabled\n");
++	if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE)
++		dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n");
+ }
+ 
+ static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev)
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+index a95ebac06e13..4ec2ae72f632 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h
+@@ -12,6 +12,7 @@
+  */
+ #define QUIRK_FW_RST_WSID_S3	BIT(1)
+ #define QUIRK_NO_BRIDGE_D3	BIT(2)
++#define QUIRK_DO_FLR_ON_BRIDGE	BIT(3)
+ 
+ void mwifiex_initialize_quirks(struct pcie_service_card *card);
+ int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev);
+-- 
+2.31.1
+
+From 36605f7215e9625de1b5f23253fc60af3ae93c04 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Thu, 25 Mar 2021 11:33:02 +0100
+Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell
+ 88W8897
+
+The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version)
+is used in a lot of Microsoft Surface devices, and all those devices
+suffer from very low 2.4GHz wifi connection speeds while bluetooth is
+enabled. The reason for that is that the default passive scanning
+interval for Bluetooth Low Energy devices is quite high in Linux
+(interval of 60 msec and scan window of 30 msec, see hci_core.c), and
+the Marvell chip is known for its bad bt+wifi coexisting performance.
+
+So decrease that passive scan interval and make the scan window shorter
+on this particular device to allow for spending more time transmitting
+wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and
+the new scan window is 6.25 msec (0xa * 0,625 msec).
+
+This change has a very large impact on the 2.4GHz wifi speeds and gets
+it up to performance comparable with the Windows driver, which seems to
+apply a similar quirk.
+
+The interval and window length were tested and found to work very well
+with a lot of Bluetooth Low Energy devices, including the Surface Pen, a
+Bluetooth Speaker and two modern Bluetooth headphones. All devices were
+discovered immediately after turning them on. Even lower values were
+also tested, but they introduced longer delays until devices get
+discovered.
+
+Patchset: mwifiex
+---
+ drivers/bluetooth/btusb.c | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
+index 5cbfbd948f67..824512fafa23 100644
+--- a/drivers/bluetooth/btusb.c
++++ b/drivers/bluetooth/btusb.c
+@@ -61,6 +61,7 @@ static struct usb_driver btusb_driver;
+ #define BTUSB_VALID_LE_STATES   0x800000
+ #define BTUSB_QCA_WCN6855	0x1000000
+ #define BTUSB_INTEL_NEWGEN	0x2000000
++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(26)
+ 
+ static const struct usb_device_id btusb_table[] = {
+ 	/* Generic Bluetooth USB device */
+@@ -357,6 +358,7 @@ static const struct usb_device_id blacklist_table[] = {
+ 	{ USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL },
+ 	{ USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL },
+ 	{ USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL },
++	{ USB_DEVICE(0x1286, 0x204c), .driver_info = BTUSB_LOWER_LESCAN_INTERVAL },
+ 
+ 	/* Intel Bluetooth devices */
+ 	{ USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_NEW |
+@@ -4713,6 +4715,19 @@ static int btusb_probe(struct usb_interface *intf,
+ 	if (id->driver_info & BTUSB_MARVELL)
+ 		hdev->set_bdaddr = btusb_set_bdaddr_marvell;
+ 
++	/* The Marvell 88W8897 combined wifi and bluetooth card is known for
++	 * very bad bt+wifi coexisting performance.
++	 *
++	 * Decrease the passive BT Low Energy scan interval a bit
++	 * (0x0190 * 0.625 msec = 250 msec) and make the scan window shorter
++	 * (0x000a * 0,625 msec = 6.25 msec). This allows for significantly
++	 * higher wifi throughput while passively scanning for BT LE devices.
++	 */
++	if (id->driver_info & BTUSB_LOWER_LESCAN_INTERVAL) {
++		hdev->le_scan_interval = 0x0190;
++		hdev->le_scan_window = 0x000a;
++	}
++
+ 	if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) &&
+ 	    (id->driver_info & BTUSB_MEDIATEK)) {
+ 		hdev->setup = btusb_mtk_setup;
+-- 
+2.31.1
+
+From 85492c085d47cdd5499f0f2123577c8fc23dad4e Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 12:31:26 +0100
+Subject: [PATCH] mwifiex: Small cleanup for handling virtual interface type
+ changes
+
+Handle the obvious invalid virtual interface type changes with a general
+check instead of looking at the individual change.
+
+For type changes from P2P_CLIENT to P2P_GO and the other way round, this
+changes the behavior slightly: We now still do nothing, but return
+-EOPNOTSUPP instead of 0. Now that behavior was incorrect before and
+still is, because type changes between these two types are actually
+possible and supported, which we'll fix in a following commit.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 39 +++++++------------
+ 1 file changed, 14 insertions(+), 25 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index a2ed268ce0da..789de1b0c5b1 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1141,6 +1141,20 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		return -EBUSY;
+ 	}
+ 
++	if (type == NL80211_IFTYPE_UNSPECIFIED) {
++		mwifiex_dbg(priv->adapter, INFO,
++			    "%s: no new type specified, keeping old type %d\n",
++			    dev->name, curr_iftype);
++		return 0;
++	}
++
++	if (curr_iftype == type) {
++		mwifiex_dbg(priv->adapter, INFO,
++			    "%s: interface already is of type %d\n",
++			    dev->name, curr_iftype);
++		return 0;
++	}
++
+ 	switch (curr_iftype) {
+ 	case NL80211_IFTYPE_ADHOC:
+ 		switch (type) {
+@@ -1160,12 +1174,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		case NL80211_IFTYPE_AP:
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+-		case NL80211_IFTYPE_UNSPECIFIED:
+-			mwifiex_dbg(priv->adapter, INFO,
+-				    "%s: kept type as IBSS\n", dev->name);
+-			fallthrough;
+-		case NL80211_IFTYPE_ADHOC:	/* This shouldn't happen */
+-			return 0;
+ 		default:
+ 			mwifiex_dbg(priv->adapter, ERROR,
+ 				    "%s: changing to %d not supported\n",
+@@ -1191,12 +1199,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		case NL80211_IFTYPE_AP:
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+-		case NL80211_IFTYPE_UNSPECIFIED:
+-			mwifiex_dbg(priv->adapter, INFO,
+-				    "%s: kept type as STA\n", dev->name);
+-			fallthrough;
+-		case NL80211_IFTYPE_STATION:	/* This shouldn't happen */
+-			return 0;
+ 		default:
+ 			mwifiex_dbg(priv->adapter, ERROR,
+ 				    "%s: changing to %d not supported\n",
+@@ -1214,12 +1216,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		case NL80211_IFTYPE_P2P_GO:
+ 			return mwifiex_change_vif_to_p2p(dev, curr_iftype,
+ 							 type, params);
+-		case NL80211_IFTYPE_UNSPECIFIED:
+-			mwifiex_dbg(priv->adapter, INFO,
+-				    "%s: kept type as AP\n", dev->name);
+-			fallthrough;
+-		case NL80211_IFTYPE_AP:		/* This shouldn't happen */
+-			return 0;
+ 		default:
+ 			mwifiex_dbg(priv->adapter, ERROR,
+ 				    "%s: changing to %d not supported\n",
+@@ -1254,13 +1250,6 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 				return -EFAULT;
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+-		case NL80211_IFTYPE_UNSPECIFIED:
+-			mwifiex_dbg(priv->adapter, INFO,
+-				    "%s: kept type as P2P\n", dev->name);
+-			fallthrough;
+-		case NL80211_IFTYPE_P2P_CLIENT:
+-		case NL80211_IFTYPE_P2P_GO:
+-			return 0;
+ 		default:
+ 			mwifiex_dbg(priv->adapter, ERROR,
+ 				    "%s: changing to %d not supported\n",
+-- 
+2.31.1
+
+From f1fea75c641564a6c18d4d9a953879fd027c0e04 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 10 Nov 2020 12:49:56 +0100
+Subject: [PATCH] mwifiex: Use non-posted PCI register writes
+
+On the 88W8897 card it's very important the TX ring write pointer is
+updated correctly to its new value before setting the TX ready
+interrupt, otherwise the firmware appears to crash (probably because
+it's trying to DMA-read from the wrong place).
+
+Since PCI uses "posted writes" when writing to a register, it's not
+guaranteed that a write will happen immediately. That means the pointer
+might be outdated when setting the TX ready interrupt, leading to
+firmware crashes especially when ASPM L1 and L1 substates are enabled
+(because of the higher link latency, the write will probably take
+longer).
+
+So fix those firmware crashes by always forcing non-posted writes. We do
+that by simply reading back the register after writing it, just as a lot
+of other drivers do.
+
+There are two reproducers that are fixed with this patch:
+
+1) During rx/tx traffic and with ASPM L1 substates enabled (the enabled
+substates are platform dependent), the firmware crashes and eventually a
+command timeout appears in the logs. That crash is fixed by using a
+non-posted write in mwifiex_pcie_send_data().
+
+2) When sending lots of commands to the card, waking it up from sleep in
+very quick intervals, the firmware eventually crashes. That crash
+appears to be fixed by some other non-posted write included here.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index 8a99e243aff2..84b1d30e07e4 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -237,6 +237,12 @@ static int mwifiex_write_reg(struct mwifiex_adapter *adapter, int reg, u32 data)
+ 
+ 	iowrite32(data, card->pci_mmap1 + reg);
+ 
++	/* Do a read-back, which makes the write non-posted, ensuring the
++	 * completion before returning.
++	 * The firmware of the 88W8897 card is buggy and this avoids crashes.
++	 */
++	ioread32(card->pci_mmap1 + reg);
++
+ 	return 0;
+ }
+ 
+-- 
+2.31.1
+
+From 3933d77762a306930dfb0ba1ff6242c1395f7690 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 12:44:39 +0100
+Subject: [PATCH] mwifiex: Use function to check whether interface type change
+ is allowed
+
+Instead of bailing out in the function which is supposed to do the type
+change, detect invalid changes beforehand using a generic function and
+return an error if the change is not allowed.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 139 ++++++++++++------
+ 1 file changed, 92 insertions(+), 47 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 789de1b0c5b1..13698818e58a 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -939,6 +939,76 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv,
+ 	return 0;
+ }
+ 
++static bool
++is_vif_type_change_allowed(struct mwifiex_adapter *adapter,
++			   enum nl80211_iftype old_iftype,
++			   enum nl80211_iftype new_iftype)
++{
++	switch (old_iftype) {
++	case NL80211_IFTYPE_ADHOC:
++		switch (new_iftype) {
++		case NL80211_IFTYPE_STATION:
++			return true;
++		case NL80211_IFTYPE_P2P_CLIENT:
++		case NL80211_IFTYPE_P2P_GO:
++			return adapter->curr_iface_comb.p2p_intf !=
++			       adapter->iface_limit.p2p_intf;
++		case NL80211_IFTYPE_AP:
++			return adapter->curr_iface_comb.uap_intf !=
++			       adapter->iface_limit.uap_intf;
++		default:
++			return false;
++		}
++
++	case NL80211_IFTYPE_STATION:
++		switch (new_iftype) {
++		case NL80211_IFTYPE_ADHOC:
++			return true;
++		case NL80211_IFTYPE_P2P_CLIENT:
++		case NL80211_IFTYPE_P2P_GO:
++			return adapter->curr_iface_comb.p2p_intf !=
++			       adapter->iface_limit.p2p_intf;
++		case NL80211_IFTYPE_AP:
++			return adapter->curr_iface_comb.uap_intf !=
++			       adapter->iface_limit.uap_intf;
++		default:
++			return false;
++		}
++
++	case NL80211_IFTYPE_AP:
++		switch (new_iftype) {
++		case NL80211_IFTYPE_ADHOC:
++		case NL80211_IFTYPE_STATION:
++			return adapter->curr_iface_comb.sta_intf !=
++			       adapter->iface_limit.sta_intf;
++		case NL80211_IFTYPE_P2P_CLIENT:
++		case NL80211_IFTYPE_P2P_GO:
++			return adapter->curr_iface_comb.p2p_intf !=
++			       adapter->iface_limit.p2p_intf;
++		default:
++			return false;
++		}
++
++	case NL80211_IFTYPE_P2P_CLIENT:
++	case NL80211_IFTYPE_P2P_GO:
++		switch (new_iftype) {
++		case NL80211_IFTYPE_ADHOC:
++		case NL80211_IFTYPE_STATION:
++			return true;
++		case NL80211_IFTYPE_AP:
++			return adapter->curr_iface_comb.uap_intf !=
++			       adapter->iface_limit.uap_intf;
++		default:
++			return false;
++		}
++
++	default:
++		break;
++	}
++
++	return false;
++}
++
+ static int
+ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 			  enum nl80211_iftype curr_iftype,
+@@ -955,13 +1025,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 
+ 	adapter = priv->adapter;
+ 
+-	if (adapter->curr_iface_comb.p2p_intf ==
+-	    adapter->iface_limit.p2p_intf) {
+-		mwifiex_dbg(adapter, ERROR,
+-			    "cannot create multiple P2P ifaces\n");
+-		return -1;
+-	}
+-
+ 	mwifiex_dbg(adapter, INFO,
+ 		    "%s: changing role to p2p\n", dev->name);
+ 
+@@ -1027,15 +1090,6 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev,
+ 
+ 	adapter = priv->adapter;
+ 
+-	if ((curr_iftype != NL80211_IFTYPE_P2P_CLIENT &&
+-	     curr_iftype != NL80211_IFTYPE_P2P_GO) &&
+-	    (adapter->curr_iface_comb.sta_intf ==
+-	     adapter->iface_limit.sta_intf)) {
+-		mwifiex_dbg(adapter, ERROR,
+-			    "cannot create multiple station/adhoc ifaces\n");
+-		return -1;
+-	}
+-
+ 	if (type == NL80211_IFTYPE_STATION)
+ 		mwifiex_dbg(adapter, INFO,
+ 			    "%s: changing role to station\n", dev->name);
+@@ -1086,13 +1140,6 @@ mwifiex_change_vif_to_ap(struct net_device *dev,
+ 
+ 	adapter = priv->adapter;
+ 
+-	if (adapter->curr_iface_comb.uap_intf ==
+-	    adapter->iface_limit.uap_intf) {
+-		mwifiex_dbg(adapter, ERROR,
+-			    "cannot create multiple AP ifaces\n");
+-		return -1;
+-	}
+-
+ 	mwifiex_dbg(adapter, INFO,
+ 		    "%s: changing role to AP\n", dev->name);
+ 
+@@ -1155,6 +1202,13 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		return 0;
+ 	}
+ 
++	if (!is_vif_type_change_allowed(priv->adapter, curr_iftype, type)) {
++		mwifiex_dbg(priv->adapter, ERROR,
++			    "%s: change from type %d to %d is not allowed\n",
++			    dev->name, curr_iftype, type);
++		return -EOPNOTSUPP;
++	}
++
+ 	switch (curr_iftype) {
+ 	case NL80211_IFTYPE_ADHOC:
+ 		switch (type) {
+@@ -1175,12 +1229,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+ 		default:
+-			mwifiex_dbg(priv->adapter, ERROR,
+-				    "%s: changing to %d not supported\n",
+-				    dev->name, type);
+-			return -EOPNOTSUPP;
++			goto errnotsupp;
+ 		}
+-		break;
++
+ 	case NL80211_IFTYPE_STATION:
+ 		switch (type) {
+ 		case NL80211_IFTYPE_ADHOC:
+@@ -1200,12 +1251,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+ 		default:
+-			mwifiex_dbg(priv->adapter, ERROR,
+-				    "%s: changing to %d not supported\n",
+-				    dev->name, type);
+-			return -EOPNOTSUPP;
++			goto errnotsupp;
+ 		}
+-		break;
++
+ 	case NL80211_IFTYPE_AP:
+ 		switch (type) {
+ 		case NL80211_IFTYPE_ADHOC:
+@@ -1217,12 +1265,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 			return mwifiex_change_vif_to_p2p(dev, curr_iftype,
+ 							 type, params);
+ 		default:
+-			mwifiex_dbg(priv->adapter, ERROR,
+-				    "%s: changing to %d not supported\n",
+-				    dev->name, type);
+-			return -EOPNOTSUPP;
++			goto errnotsupp;
+ 		}
+-		break;
++
+ 	case NL80211_IFTYPE_P2P_CLIENT:
+ 	case NL80211_IFTYPE_P2P_GO:
+ 		switch (type) {
+@@ -1251,21 +1296,21 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+ 		default:
+-			mwifiex_dbg(priv->adapter, ERROR,
+-				    "%s: changing to %d not supported\n",
+-				    dev->name, type);
+-			return -EOPNOTSUPP;
++			goto errnotsupp;
+ 		}
+-		break;
++
+ 	default:
+-		mwifiex_dbg(priv->adapter, ERROR,
+-			    "%s: unknown iftype: %d\n",
+-			    dev->name, dev->ieee80211_ptr->iftype);
+-		return -EOPNOTSUPP;
++		goto errnotsupp;
+ 	}
+ 
+ 
+ 	return 0;
++
++errnotsupp:
++	mwifiex_dbg(priv->adapter, ERROR,
++		    "unsupported interface type transition: %d to %d\n",
++		    curr_iftype, type);
++	return -EOPNOTSUPP;
+ }
+ 
+ static void
+-- 
+2.31.1
+
+From 29e7e389e5242dcfa8a622ea3ca01a44824990fc Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 13:33:04 +0100
+Subject: [PATCH] mwifiex: Run SET_BSS_MODE when changing from P2P to STATION
+ vif-type
+
+We currently handle changing from the P2P to the STATION virtual
+interface type slightly different than changing from P2P to ADHOC: When
+changing to STATION, we don't send the SET_BSS_MODE command. We do send
+that command on all other type-changes though, and it probably makes
+sense to send the command since after all we just changed our BSS_MODE.
+Looking at prior changes to this part of the code, it seems that this is
+simply a leftover from old refactorings.
+
+Since sending the SET_BSS_MODE command is the only difference between
+mwifiex_change_vif_to_sta_adhoc() and the current code, we can now use
+mwifiex_change_vif_to_sta_adhoc() for both switching to ADHOC and
+STATION interface type.
+
+This does not fix any particular bug and just "looked right", so there's
+a small chance it might be a regression.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 22 ++++---------------
+ 1 file changed, 4 insertions(+), 18 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 13698818e58a..f5b9f1d26114 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1270,29 +1270,15 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 
+ 	case NL80211_IFTYPE_P2P_CLIENT:
+ 	case NL80211_IFTYPE_P2P_GO:
++		if (mwifiex_cfg80211_deinit_p2p(priv))
++			return -EFAULT;
++
+ 		switch (type) {
+-		case NL80211_IFTYPE_STATION:
+-			if (mwifiex_cfg80211_deinit_p2p(priv))
+-				return -EFAULT;
+-			priv->adapter->curr_iface_comb.p2p_intf--;
+-			priv->adapter->curr_iface_comb.sta_intf++;
+-			dev->ieee80211_ptr->iftype = type;
+-			if (mwifiex_deinit_priv_params(priv))
+-				return -1;
+-			if (mwifiex_init_new_priv_params(priv, dev, type))
+-				return -1;
+-			if (mwifiex_sta_init_cmd(priv, false, false))
+-				return -1;
+-			break;
+ 		case NL80211_IFTYPE_ADHOC:
+-			if (mwifiex_cfg80211_deinit_p2p(priv))
+-				return -EFAULT;
++		case NL80211_IFTYPE_STATION:
+ 			return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype,
+ 							       type, params);
+-			break;
+ 		case NL80211_IFTYPE_AP:
+-			if (mwifiex_cfg80211_deinit_p2p(priv))
+-				return -EFAULT;
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+ 		default:
+-- 
+2.31.1
+
+From 278002e59e1bfad3f9f849fb2402547d0ff445db Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 14:42:54 +0100
+Subject: [PATCH] mwifiex: Use helper function for counting interface types
+
+Use a small helper function to increment and decrement the counter of
+the interface types we currently manage. This makes the code that
+actually changes and sets up the interface type a bit less messy and
+also helps avoiding mistakes in case someone increments/decrements a
+counter wrongly.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 110 ++++++------------
+ 1 file changed, 35 insertions(+), 75 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index f5b9f1d26114..44cff715bf29 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1009,6 +1009,32 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter,
+ 	return false;
+ }
+ 
++static void
++update_vif_type_counter(struct mwifiex_adapter *adapter,
++			enum nl80211_iftype iftype,
++			int change)
++{
++	switch (iftype) {
++	case NL80211_IFTYPE_UNSPECIFIED:
++	case NL80211_IFTYPE_ADHOC:
++	case NL80211_IFTYPE_STATION:
++		adapter->curr_iface_comb.sta_intf += change;
++		break;
++	case NL80211_IFTYPE_AP:
++		adapter->curr_iface_comb.uap_intf += change;
++		break;
++	case NL80211_IFTYPE_P2P_CLIENT:
++	case NL80211_IFTYPE_P2P_GO:
++		adapter->curr_iface_comb.p2p_intf += change;
++		break;
++	default:
++		mwifiex_dbg(adapter, ERROR,
++			    "%s: Unsupported iftype passed: %d\n",
++			    __func__, iftype);
++		break;
++	}
++}
++
+ static int
+ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 			  enum nl80211_iftype curr_iftype,
+@@ -1056,19 +1082,8 @@ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	switch (curr_iftype) {
+-	case NL80211_IFTYPE_STATION:
+-	case NL80211_IFTYPE_ADHOC:
+-		adapter->curr_iface_comb.sta_intf--;
+-		break;
+-	case NL80211_IFTYPE_AP:
+-		adapter->curr_iface_comb.uap_intf--;
+-		break;
+-	default:
+-		break;
+-	}
+-
+-	adapter->curr_iface_comb.p2p_intf++;
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
+ 	dev->ieee80211_ptr->iftype = type;
+ 
+ 	return 0;
+@@ -1107,20 +1122,10 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev,
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	switch (curr_iftype) {
+-	case NL80211_IFTYPE_P2P_CLIENT:
+-	case NL80211_IFTYPE_P2P_GO:
+-		adapter->curr_iface_comb.p2p_intf--;
+-		break;
+-	case NL80211_IFTYPE_AP:
+-		adapter->curr_iface_comb.uap_intf--;
+-		break;
+-	default:
+-		break;
+-	}
+-
+-	adapter->curr_iface_comb.sta_intf++;
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
+ 	dev->ieee80211_ptr->iftype = type;
++
+ 	return 0;
+ }
+ 
+@@ -1153,20 +1158,8 @@ mwifiex_change_vif_to_ap(struct net_device *dev,
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	switch (curr_iftype) {
+-	case NL80211_IFTYPE_P2P_CLIENT:
+-	case NL80211_IFTYPE_P2P_GO:
+-		adapter->curr_iface_comb.p2p_intf--;
+-		break;
+-	case NL80211_IFTYPE_STATION:
+-	case NL80211_IFTYPE_ADHOC:
+-		adapter->curr_iface_comb.sta_intf--;
+-		break;
+-	default:
+-		break;
+-	}
+-
+-	adapter->curr_iface_comb.uap_intf++;
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
+ 	dev->ieee80211_ptr->iftype = type;
+ 	return 0;
+ }
+@@ -3131,23 +3124,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy,
+ 	mwifiex_dev_debugfs_init(priv);
+ #endif
+ 
+-	switch (type) {
+-	case NL80211_IFTYPE_UNSPECIFIED:
+-	case NL80211_IFTYPE_STATION:
+-	case NL80211_IFTYPE_ADHOC:
+-		adapter->curr_iface_comb.sta_intf++;
+-		break;
+-	case NL80211_IFTYPE_AP:
+-		adapter->curr_iface_comb.uap_intf++;
+-		break;
+-	case NL80211_IFTYPE_P2P_CLIENT:
+-		adapter->curr_iface_comb.p2p_intf++;
+-		break;
+-	default:
+-		/* This should be dead code; checked above */
+-		mwifiex_dbg(adapter, ERROR, "type not supported\n");
+-		return ERR_PTR(-EINVAL);
+-	}
++	update_vif_type_counter(adapter, type, +1);
+ 
+ 	return &priv->wdev;
+ 
+@@ -3213,24 +3190,7 @@ int mwifiex_del_virtual_intf(struct wiphy *wiphy, struct wireless_dev *wdev)
+ 	/* Clear the priv in adapter */
+ 	priv->netdev = NULL;
+ 
+-	switch (priv->bss_mode) {
+-	case NL80211_IFTYPE_UNSPECIFIED:
+-	case NL80211_IFTYPE_STATION:
+-	case NL80211_IFTYPE_ADHOC:
+-		adapter->curr_iface_comb.sta_intf--;
+-		break;
+-	case NL80211_IFTYPE_AP:
+-		adapter->curr_iface_comb.uap_intf--;
+-		break;
+-	case NL80211_IFTYPE_P2P_CLIENT:
+-	case NL80211_IFTYPE_P2P_GO:
+-		adapter->curr_iface_comb.p2p_intf--;
+-		break;
+-	default:
+-		mwifiex_dbg(adapter, ERROR,
+-			    "del_virtual_intf: type not supported\n");
+-		break;
+-	}
++	update_vif_type_counter(adapter, priv->bss_mode, -1);
+ 
+ 	priv->bss_mode = NL80211_IFTYPE_UNSPECIFIED;
+ 
+-- 
+2.31.1
+
+From 86ce2f912a9ee6b579e33dee1a13279511bcf34a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Fri, 26 Mar 2021 15:56:58 +0100
+Subject: [PATCH] mwifiex: Update virtual interface counters right after
+ setting bss_type
+
+In mwifiex_init_new_priv_params() we update our private driver state to
+reflect the currently selected virtual interface type. Most notably we
+set the bss_mode to the mode we're going to put the firmware in.
+
+Now after we updated the driver state we actually start talking to the
+firmware and instruct it to set up the new mode. Those commands can and
+will sometimes fail, in which case we return with an error from
+mwifiex_change_vif_to_*. We currently update our virtual interface type
+counters after this return, which means the code is never reached when a
+firmware error happens and we never update the counters. Since we have
+updated our bss_mode earlier though, the counters now no longer reflect
+the actual state of the driver.
+
+This will break things on the next virtual interface change, because the
+virtual interface type we're switching away from didn't get its counter
+incremented, and we end up decrementing a 0-counter.
+
+To fix this, simply update the virtual interface type counters right
+after updating our driver structures, so that they are always in sync.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 25 +++++++++++--------
+ 1 file changed, 14 insertions(+), 11 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 44cff715bf29..e637129a411f 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1059,6 +1059,10 @@ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 	if (mwifiex_init_new_priv_params(priv, dev, type))
+ 		return -1;
+ 
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
++	dev->ieee80211_ptr->iftype = type;
++
+ 	switch (type) {
+ 	case NL80211_IFTYPE_P2P_CLIENT:
+ 		if (mwifiex_cfg80211_init_p2p_client(priv))
+@@ -1082,10 +1086,6 @@ mwifiex_change_vif_to_p2p(struct net_device *dev,
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	update_vif_type_counter(adapter, curr_iftype, -1);
+-	update_vif_type_counter(adapter, type, +1);
+-	dev->ieee80211_ptr->iftype = type;
+-
+ 	return 0;
+ }
+ 
+@@ -1116,16 +1116,17 @@ mwifiex_change_vif_to_sta_adhoc(struct net_device *dev,
+ 		return -1;
+ 	if (mwifiex_init_new_priv_params(priv, dev, type))
+ 		return -1;
++
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
++	dev->ieee80211_ptr->iftype = type;
++
+ 	if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE,
+ 			     HostCmd_ACT_GEN_SET, 0, NULL, true))
+ 		return -1;
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	update_vif_type_counter(adapter, curr_iftype, -1);
+-	update_vif_type_counter(adapter, type, +1);
+-	dev->ieee80211_ptr->iftype = type;
+-
+ 	return 0;
+ }
+ 
+@@ -1152,15 +1153,17 @@ mwifiex_change_vif_to_ap(struct net_device *dev,
+ 		return -1;
+ 	if (mwifiex_init_new_priv_params(priv, dev, type))
+ 		return -1;
++
++	update_vif_type_counter(adapter, curr_iftype, -1);
++	update_vif_type_counter(adapter, type, +1);
++	dev->ieee80211_ptr->iftype = type;
++
+ 	if (mwifiex_send_cmd(priv, HostCmd_CMD_SET_BSS_MODE,
+ 			     HostCmd_ACT_GEN_SET, 0, NULL, true))
+ 		return -1;
+ 	if (mwifiex_sta_init_cmd(priv, false, false))
+ 		return -1;
+ 
+-	update_vif_type_counter(adapter, curr_iftype, -1);
+-	update_vif_type_counter(adapter, type, +1);
+-	dev->ieee80211_ptr->iftype = type;
+ 	return 0;
+ }
+ /*
+-- 
+2.31.1
+
+From 851692b24cb46c2f5450d78583d6b148ad4f0f59 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 13:42:40 +0100
+Subject: [PATCH] mwifiex: Allow switching interface type from P2P_CLIENT to
+ P2P_GO
+
+It's possible to change virtual interface type between P2P_CLIENT and
+P2P_GO, the card supports that just fine, and it happens for example
+when using miracast with the miraclecast software.
+
+So allow type changes between P2P_CLIENT and P2P_GO and simply call into
+mwifiex_change_vif_to_p2p(), which handles this just fine. We have to
+call mwifiex_cfg80211_deinit_p2p() before though to make sure the old
+p2p mode is properly uninitialized.
+
+Patchset: mwifiex
+---
+ .../net/wireless/marvell/mwifiex/cfg80211.c   | 36 +++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index e637129a411f..395573db6405 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -990,11 +990,26 @@ is_vif_type_change_allowed(struct mwifiex_adapter *adapter,
+ 		}
+ 
+ 	case NL80211_IFTYPE_P2P_CLIENT:
++		switch (new_iftype) {
++		case NL80211_IFTYPE_ADHOC:
++		case NL80211_IFTYPE_STATION:
++			return true;
++		case NL80211_IFTYPE_P2P_GO:
++			return true;
++		case NL80211_IFTYPE_AP:
++			return adapter->curr_iface_comb.uap_intf !=
++			       adapter->iface_limit.uap_intf;
++		default:
++			return false;
++		}
++
+ 	case NL80211_IFTYPE_P2P_GO:
+ 		switch (new_iftype) {
+ 		case NL80211_IFTYPE_ADHOC:
+ 		case NL80211_IFTYPE_STATION:
+ 			return true;
++		case NL80211_IFTYPE_P2P_CLIENT:
++			return true;
+ 		case NL80211_IFTYPE_AP:
+ 			return adapter->curr_iface_comb.uap_intf !=
+ 			       adapter->iface_limit.uap_intf;
+@@ -1265,6 +1280,24 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		}
+ 
+ 	case NL80211_IFTYPE_P2P_CLIENT:
++		if (mwifiex_cfg80211_deinit_p2p(priv))
++			return -EFAULT;
++
++		switch (type) {
++		case NL80211_IFTYPE_ADHOC:
++		case NL80211_IFTYPE_STATION:
++			return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype,
++							       type, params);
++		case NL80211_IFTYPE_P2P_GO:
++			return mwifiex_change_vif_to_p2p(dev, curr_iftype,
++							 type, params);
++		case NL80211_IFTYPE_AP:
++			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
++							params);
++		default:
++			goto errnotsupp;
++		}
++
+ 	case NL80211_IFTYPE_P2P_GO:
+ 		if (mwifiex_cfg80211_deinit_p2p(priv))
+ 			return -EFAULT;
+@@ -1274,6 +1307,9 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 		case NL80211_IFTYPE_STATION:
+ 			return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype,
+ 							       type, params);
++		case NL80211_IFTYPE_P2P_CLIENT:
++			return mwifiex_change_vif_to_p2p(dev, curr_iftype,
++							 type, params);
+ 		case NL80211_IFTYPE_AP:
+ 			return mwifiex_change_vif_to_ap(dev, curr_iftype, type,
+ 							params);
+-- 
+2.31.1
+
+From 42a9b01e72f6ced95f6b27101ad9db53bd4a0215 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Fri, 26 Mar 2021 15:31:08 +0100
+Subject: [PATCH] mwifiex: Handle interface type changes from AP to STATION
+
+Looks like this case was simply overseen, so handle it, too.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cfg80211.c | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 395573db6405..90a757aa0b25 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -1268,6 +1268,7 @@ mwifiex_cfg80211_change_virtual_intf(struct wiphy *wiphy,
+ 	case NL80211_IFTYPE_AP:
+ 		switch (type) {
+ 		case NL80211_IFTYPE_ADHOC:
++		case NL80211_IFTYPE_STATION:
+ 			return mwifiex_change_vif_to_sta_adhoc(dev, curr_iftype,
+ 							       type, params);
+ 			break;
+-- 
+2.31.1
+
+From 0c02cc80b25f90ab410103a493d73fd023f05abb Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Fri, 26 Mar 2021 15:32:16 +0100
+Subject: [PATCH] mwifiex: Properly initialize private structure on interface
+ type changes
+
+When creating a new virtual interface in mwifiex_add_virtual_intf(), we
+update our internal driver states like bss_type, bss_priority, bss_role
+and bss_mode to reflect the mode the firmware will be set to.
+
+When switching virtual interface mode using
+mwifiex_init_new_priv_params() though, we currently only update bss_mode
+and bss_role. In order for the interface mode switch to actually work,
+we also need to update bss_type to its proper value, so do that.
+
+This fixes a crash of the firmware (because the driver tries to execute
+commands that are invalid in AP mode) when switching from station mode
+to AP mode.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 90a757aa0b25..0c01d8f9048e 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -908,16 +908,20 @@ mwifiex_init_new_priv_params(struct mwifiex_private *priv,
+ 	switch (type) {
+ 	case NL80211_IFTYPE_STATION:
+ 	case NL80211_IFTYPE_ADHOC:
+-		priv->bss_role =  MWIFIEX_BSS_ROLE_STA;
++		priv->bss_role = MWIFIEX_BSS_ROLE_STA;
++		priv->bss_type = MWIFIEX_BSS_TYPE_STA;
+ 		break;
+ 	case NL80211_IFTYPE_P2P_CLIENT:
+-		priv->bss_role =  MWIFIEX_BSS_ROLE_STA;
++		priv->bss_role = MWIFIEX_BSS_ROLE_STA;
++		priv->bss_type = MWIFIEX_BSS_TYPE_P2P;
+ 		break;
+ 	case NL80211_IFTYPE_P2P_GO:
+-		priv->bss_role =  MWIFIEX_BSS_ROLE_UAP;
++		priv->bss_role = MWIFIEX_BSS_ROLE_UAP;
++		priv->bss_type = MWIFIEX_BSS_TYPE_P2P;
+ 		break;
+ 	case NL80211_IFTYPE_AP:
+ 		priv->bss_role = MWIFIEX_BSS_ROLE_UAP;
++		priv->bss_type = MWIFIEX_BSS_TYPE_UAP;
+ 		break;
+ 	default:
+ 		mwifiex_dbg(adapter, ERROR,
+-- 
+2.31.1
+
+From d2574c9f7fd3d0efc7bf8c07f8415c8bf18b9b74 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Sat, 27 Mar 2021 12:19:14 +0100
+Subject: [PATCH] mwifiex: Fix copy-paste mistake when creating virtual
+ interface
+
+The BSS priority here for a new P2P_CLIENT device was accidentally set
+to an enum that's certainly not meant for this. Since
+MWIFIEX_BSS_ROLE_STA is 0 anyway, we can just set the bss_priority to 0
+instead here.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 0c01d8f9048e..8c472b2d982a 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -3057,7 +3057,7 @@ struct wireless_dev *mwifiex_add_virtual_intf(struct wiphy *wiphy,
+ 		priv->bss_type = MWIFIEX_BSS_TYPE_P2P;
+ 
+ 		priv->frame_type = MWIFIEX_DATA_FRAME_TYPE_ETH_II;
+-		priv->bss_priority = MWIFIEX_BSS_ROLE_STA;
++		priv->bss_priority = 0;
+ 		priv->bss_role = MWIFIEX_BSS_ROLE_STA;
+ 		priv->bss_started = 0;
+ 
+-- 
+2.31.1
+
+From 8a39e3cce26f0df0242d7fc5b8f91c3116c406e8 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Sun, 28 Mar 2021 21:10:06 +0200
+Subject: [PATCH] mwifiex: Try waking the firmware until we get an interrupt
+
+It seems that the firmware of the 88W8897 card sometimes ignores or
+misses when we try to wake it up by reading the firmware status
+register. This leads to the firmware wakeup timeout expiring and the
+driver resetting the card because we assume the firmware has hung up or
+crashed (unfortunately that's not unlikely with this card).
+
+Turns out that most of the time the firmware actually didn't hang up,
+but simply "missed" our wakeup request and doesn't send us an AWAKE
+event.
+
+Trying again to read the firmware status register after a short timeout
+usually makes the firmware wake we up as expected, so add a small retry
+loop to mwifiex_pm_wakeup_card() that looks at the interrupt status to
+check whether the card woke up.
+
+The number of tries and timeout lengths for this were determined
+experimentally: The firmware usually takes about 500 us to wake up
+after we attempt to read the status register. In some cases where the
+firmware is very busy (for example while doing a bluetooth scan) it
+might even miss our requests for multiple milliseconds, which is why
+after 15 tries the waiting time gets increased to 10 ms. The maximum
+number of tries it took to wake the firmware when testing this was
+around 20, so a maximum number of 50 tries should give us plenty of
+safety margin.
+
+A good reproducer for this issue is letting the firmware sleep and wake
+up in very short intervals, for example by pinging an device on the
+network every 0.1 seconds.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/pcie.c | 29 ++++++++++++++++-----
+ 1 file changed, 23 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c
+index 84b1d30e07e4..88d30ec6d57d 100644
+--- a/drivers/net/wireless/marvell/mwifiex/pcie.c
++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c
+@@ -665,6 +665,7 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter)
+ {
+ 	struct pcie_service_card *card = adapter->card;
+ 	const struct mwifiex_pcie_card_reg *reg = card->pcie.reg;
++	int n_tries = 0;
+ 
+ 	mwifiex_dbg(adapter, EVENT,
+ 		    "event: Wakeup device...\n");
+@@ -672,12 +673,28 @@ static int mwifiex_pm_wakeup_card(struct mwifiex_adapter *adapter)
+ 	if (reg->sleep_cookie)
+ 		mwifiex_pcie_dev_wakeup_delay(adapter);
+ 
+-	/* Accessing fw_status register will wakeup device */
+-	if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) {
+-		mwifiex_dbg(adapter, ERROR,
+-			    "Writing fw_status register failed\n");
+-		return -1;
+-	}
++	/* Access the fw_status register to wake up the device.
++	 * Since the 88W8897 firmware sometimes appears to ignore or miss
++	 * that wakeup request, we continue trying until we receive an
++	 * interrupt from the card.
++	 */
++	do {
++		if (mwifiex_write_reg(adapter, reg->fw_status, FIRMWARE_READY_PCIE)) {
++			mwifiex_dbg(adapter, ERROR,
++				    "Writing fw_status register failed\n");
++			return -1;
++		}
++
++		n_tries++;
++
++		if (n_tries <= 15)
++			usleep_range(400, 700);
++		else
++			msleep(10);
++	} while (n_tries <= 50 && READ_ONCE(adapter->int_status) == 0);
++
++	mwifiex_dbg(adapter, EVENT,
++		    "event: Tried %d times until firmware woke up\n", n_tries);
+ 
+ 	if (reg->sleep_cookie) {
+ 		mwifiex_pcie_dev_wakeup_delay(adapter);
+-- 
+2.31.1
+
+From 2eb1eb70eceb55d17d4948ee8b95159bac444c2a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 13 Apr 2021 14:30:28 +0200
+Subject: [PATCH] mwifiex: Deactive host sleep using HSCFG after it was
+ activated manually
+
+When powersaving (so either wifi powersaving or deep sleep, depending on
+which state the firmware is in) is disabled, the way the firmware goes
+into host sleep is different: Usually the firmware implicitely enters
+host sleep on the next SLEEP event we get when we configured host sleep
+via HSCFG before. When powersaving is disabled though, there are no
+SLEEP events, the way we enter host sleep in that case is different: The
+firmware will send us a HS_ACT_REQ event and after that we "manually"
+make the firmware enter host sleep by sending it another HSCFG command
+with the action HS_ACTIVATE.
+
+Now waking up from host sleep appears to be different depending on
+whether powersaving is enabled again: When powersaving is enabled, the
+firmware implicitely leaves host sleep as soon as it wakes up and sends
+us an AWAKE event. When powersaving is disabled though, it apparently
+doesn't implicitely leave host sleep, but instead we need to send it a
+HSCFG command with the HS_CONFIGURE action and the HS_CFG_CANCEL
+condition. We didn't do that so far, which is why waking up from host
+sleep was broken when powersaving is disabled.
+
+So add some additional state to mwifiex_adapter where we keep track of
+whether host sleep was activated manually via HS_ACTIVATE, and if that
+was the case, deactivate it manually again via HS_CFG_CANCEL.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cmdevt.c | 21 +++++++++++++++++++
+ drivers/net/wireless/marvell/mwifiex/main.c   | 18 ++++++++++++++++
+ drivers/net/wireless/marvell/mwifiex/main.h   |  1 +
+ .../net/wireless/marvell/mwifiex/sta_cmd.c    |  4 ++++
+ 4 files changed, 44 insertions(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cmdevt.c b/drivers/net/wireless/marvell/mwifiex/cmdevt.c
+index 3a11342a6bde..5487df8f994d 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cmdevt.c
++++ b/drivers/net/wireless/marvell/mwifiex/cmdevt.c
+@@ -608,6 +608,11 @@ int mwifiex_send_cmd(struct mwifiex_private *priv, u16 cmd_no,
+ 		return -1;
+ 	}
+ 
++	if (priv->adapter->hs_activated_manually &&
++	    cmd_no != HostCmd_CMD_802_11_HS_CFG_ENH) {
++		mwifiex_cancel_hs(priv, MWIFIEX_ASYNC_CMD);
++		priv->adapter->hs_activated_manually = false;
++	}
+ 
+ 	/* Get a new command node */
+ 	cmd_node = mwifiex_get_cmd_node(adapter);
+@@ -714,6 +719,15 @@ mwifiex_insert_cmd_to_pending_q(struct mwifiex_adapter *adapter,
+ 		}
+ 	}
+ 
++	/* Same with exit host sleep cmd, luckily that can't happen at the same time as EXIT_PS */
++	if (command == HostCmd_CMD_802_11_HS_CFG_ENH) {
++		struct host_cmd_ds_802_11_hs_cfg_enh *hs_cfg =
++			&host_cmd->params.opt_hs_cfg;
++
++		if (le16_to_cpu(hs_cfg->action) == HS_ACTIVATE)
++				add_tail = false;
++	}
++
+ 	spin_lock_bh(&adapter->cmd_pending_q_lock);
+ 	if (add_tail)
+ 		list_add_tail(&cmd_node->list, &adapter->cmd_pending_q);
+@@ -1216,6 +1230,13 @@ mwifiex_process_hs_config(struct mwifiex_adapter *adapter)
+ 		    __func__);
+ 
+ 	adapter->if_ops.wakeup(adapter);
++
++	if (adapter->hs_activated_manually) {
++		mwifiex_cancel_hs(mwifiex_get_priv (adapter, MWIFIEX_BSS_ROLE_ANY),
++				  MWIFIEX_ASYNC_CMD);
++		adapter->hs_activated_manually = false;
++	}
++
+ 	adapter->hs_activated = false;
+ 	clear_bit(MWIFIEX_IS_HS_CONFIGURED, &adapter->work_flags);
+ 	clear_bit(MWIFIEX_IS_SUSPENDED, &adapter->work_flags);
+diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c
+index 529dfd8b7ae8..56e4385927b4 100644
+--- a/drivers/net/wireless/marvell/mwifiex/main.c
++++ b/drivers/net/wireless/marvell/mwifiex/main.c
+@@ -401,6 +401,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter)
+ 		     !adapter->scan_processing) &&
+ 		    !adapter->data_sent &&
+ 		    !skb_queue_empty(&adapter->tx_data_q)) {
++			if (adapter->hs_activated_manually) {
++				mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY),
++						  MWIFIEX_ASYNC_CMD);
++				adapter->hs_activated_manually = false;
++			}
++
+ 			mwifiex_process_tx_queue(adapter);
+ 			if (adapter->hs_activated) {
+ 				clear_bit(MWIFIEX_IS_HS_CONFIGURED,
+@@ -418,6 +424,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter)
+ 		    !mwifiex_bypass_txlist_empty(adapter) &&
+ 		    !mwifiex_is_tdls_chan_switching
+ 			(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) {
++			if (adapter->hs_activated_manually) {
++				mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY),
++						  MWIFIEX_ASYNC_CMD);
++				adapter->hs_activated_manually = false;
++			}
++
+ 			mwifiex_process_bypass_tx(adapter);
+ 			if (adapter->hs_activated) {
+ 				clear_bit(MWIFIEX_IS_HS_CONFIGURED,
+@@ -434,6 +446,12 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter)
+ 		    !adapter->data_sent && !mwifiex_wmm_lists_empty(adapter) &&
+ 		    !mwifiex_is_tdls_chan_switching
+ 			(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA))) {
++			if (adapter->hs_activated_manually) {
++				mwifiex_cancel_hs(mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_ANY),
++						  MWIFIEX_ASYNC_CMD);
++				adapter->hs_activated_manually = false;
++			}
++
+ 			mwifiex_wmm_process_tx(adapter);
+ 			if (adapter->hs_activated) {
+ 				clear_bit(MWIFIEX_IS_HS_CONFIGURED,
+diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h
+index 5923c5c14c8d..90012cbcfd15 100644
+--- a/drivers/net/wireless/marvell/mwifiex/main.h
++++ b/drivers/net/wireless/marvell/mwifiex/main.h
+@@ -986,6 +986,7 @@ struct mwifiex_adapter {
+ 	struct timer_list wakeup_timer;
+ 	struct mwifiex_hs_config_param hs_cfg;
+ 	u8 hs_activated;
++	u8 hs_activated_manually;
+ 	u16 hs_activate_wait_q_woken;
+ 	wait_queue_head_t hs_activate_wait_q;
+ 	u8 event_body[MAX_EVENT_SIZE];
+diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c
+index d3a968ef21ef..76db9a7b8199 100644
+--- a/drivers/net/wireless/marvell/mwifiex/sta_cmd.c
++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmd.c
+@@ -396,6 +396,10 @@ mwifiex_cmd_802_11_hs_cfg(struct mwifiex_private *priv,
+ 	if (hs_activate) {
+ 		hs_cfg->action = cpu_to_le16(HS_ACTIVATE);
+ 		hs_cfg->params.hs_activate.resp_ctrl = cpu_to_le16(RESP_NEEDED);
++
++		adapter->hs_activated_manually = true;
++		mwifiex_dbg(priv->adapter, CMD,
++			    "cmd: Activating host sleep manually\n");
+ 	} else {
+ 		hs_cfg->action = cpu_to_le16(HS_CONFIGURE);
+ 		hs_cfg->params.hs_config.conditions = hscfg_param->conditions;
+-- 
+2.31.1
+
+From 2d7964c9df5107c53d241816afd826683052d4d2 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 13 Apr 2021 14:23:05 +0200
+Subject: [PATCH] mwifiex: Add quirk to disable deep sleep with certain
+ hardware revision
+
+The 88W8897 pcie card with the hardware revision 20 apparently has a
+hardware issue where the card wakes up from deep sleep randomly and very
+often, somewhat depending on the card activity, maybe the hardware has a
+floating wakeup pin or something.
+
+Those continuous wakeups prevent the card from entering host sleep when
+the computer suspends. And because the host won't answer to events from
+the card anymore while it's suspended, the firmwares internal
+powersaving state machine seems to get confused and the card can't sleep
+anymore at all after that.
+
+Since we can't work around that hardware bug in the firmware, let's
+get the hardware revision string from the firmware and match it with
+known bad revisions. Then disable auto deep sleep for those revisions,
+which makes sure we no longer get those spurious wakeups.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/main.c      | 14 ++++++++++++++
+ drivers/net/wireless/marvell/mwifiex/main.h      |  1 +
+ .../net/wireless/marvell/mwifiex/sta_cmdresp.c   | 16 ++++++++++++++++
+ 3 files changed, 31 insertions(+)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c
+index 56e4385927b4..a6be59ac1e27 100644
+--- a/drivers/net/wireless/marvell/mwifiex/main.c
++++ b/drivers/net/wireless/marvell/mwifiex/main.c
+@@ -226,6 +226,19 @@ static int mwifiex_process_rx(struct mwifiex_adapter *adapter)
+ 	return 0;
+ }
+ 
++static void maybe_quirk_fw_disable_ds(struct mwifiex_adapter *adapter)
++{
++	struct mwifiex_private *priv = mwifiex_get_priv(adapter, MWIFIEX_BSS_ROLE_STA);
++	struct mwifiex_ver_ext ver_ext;
++
++	set_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &adapter->work_flags);
++
++	memset(&ver_ext, 0, sizeof(ver_ext));
++	ver_ext.version_str_sel = 1;
++	mwifiex_send_cmd(priv, HostCmd_CMD_VERSION_EXT,
++			 HostCmd_ACT_GEN_GET, 0, &ver_ext, false);
++}
++
+ /*
+  * The main process.
+  *
+@@ -356,6 +369,7 @@ int mwifiex_main_process(struct mwifiex_adapter *adapter)
+ 			if (adapter->hw_status == MWIFIEX_HW_STATUS_INIT_DONE) {
+ 				adapter->hw_status = MWIFIEX_HW_STATUS_READY;
+ 				mwifiex_init_fw_complete(adapter);
++				maybe_quirk_fw_disable_ds(adapter);
+ 			}
+ 		}
+ 
+diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h
+index 90012cbcfd15..1e829d84b1f6 100644
+--- a/drivers/net/wireless/marvell/mwifiex/main.h
++++ b/drivers/net/wireless/marvell/mwifiex/main.h
+@@ -524,6 +524,7 @@ enum mwifiex_adapter_work_flags {
+ 	MWIFIEX_IS_SUSPENDED,
+ 	MWIFIEX_IS_HS_CONFIGURED,
+ 	MWIFIEX_IS_HS_ENABLING,
++	MWIFIEX_IS_REQUESTING_FW_VEREXT,
+ };
+ 
+ struct mwifiex_band_config {
+diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c
+index 6b5d35d9e69f..8e49ebca1847 100644
+--- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c
++++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c
+@@ -708,6 +708,22 @@ static int mwifiex_ret_ver_ext(struct mwifiex_private *priv,
+ {
+ 	struct host_cmd_ds_version_ext *ver_ext = &resp->params.verext;
+ 
++	if (test_and_clear_bit(MWIFIEX_IS_REQUESTING_FW_VEREXT, &priv->adapter->work_flags)) {
++		if (strncmp(ver_ext->version_str, "ChipRev:20, BB:9b(10.00), RF:40(21)", 128) == 0) {
++			struct mwifiex_ds_auto_ds auto_ds = {
++				.auto_ds = DEEP_SLEEP_OFF,
++			};
++
++			mwifiex_dbg(priv->adapter, MSG,
++				    "Bad HW revision detected, disabling deep sleep\n");
++
++			mwifiex_send_cmd(priv, HostCmd_CMD_802_11_PS_MODE_ENH,
++					 DIS_AUTO_PS, BITMAP_AUTO_DS, &auto_ds, false);
++		}
++
++		return 0;
++	}
++
+ 	if (version_ext) {
+ 		version_ext->version_str_sel = ver_ext->version_str_sel;
+ 		memcpy(version_ext->version_str, ver_ext->version_str,
+-- 
+2.31.1
+
+From 3efbf69305a003ee7ad0b09157be0ed6307b63e5 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Wed, 11 Nov 2020 15:17:07 +0100
+Subject: [PATCH] mwifiex: Don't log error on suspend if wake-on-wlan is
+ disabled
+
+It's not an error if someone chooses to put their computer to sleep, not
+wanting it to wake up because the person next door has just discovered
+what a magic packet is. So change the loglevel of this annoying message
+from ERROR to INFO.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cfg80211.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 8c472b2d982a..153025d1b2fa 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -3497,7 +3497,7 @@ static int mwifiex_cfg80211_suspend(struct wiphy *wiphy,
+ 	}
+ 
+ 	if (!wowlan) {
+-		mwifiex_dbg(adapter, ERROR,
++		mwifiex_dbg(adapter, INFO,
+ 			    "None of the WOWLAN triggers enabled\n");
+ 		ret = 0;
+ 		goto done;
+-- 
+2.31.1
+
+From 24eace3d82040069a68e13d6a80053ae1d597306 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Sun, 28 Mar 2021 21:42:54 +0200
+Subject: [PATCH] mwifiex: Log an error on command failure during key-material
+ upload
+
+Sometimes the KEY_MATERIAL command can fail with the 88W8897 firmware
+(when this happens exactly seems pretty random). This appears to prevent
+the access point from starting, so it seems like a good idea to log an
+error in that case.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/cfg80211.c | 10 ++++++++--
+ 1 file changed, 8 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+index 153025d1b2fa..ef6ce3f63aec 100644
+--- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c
++++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c
+@@ -519,8 +519,14 @@ mwifiex_cfg80211_set_default_mgmt_key(struct wiphy *wiphy,
+ 	encrypt_key.is_igtk_def_key = true;
+ 	eth_broadcast_addr(encrypt_key.mac_addr);
+ 
+-	return mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL,
+-				HostCmd_ACT_GEN_SET, true, &encrypt_key, true);
++	if (mwifiex_send_cmd(priv, HostCmd_CMD_802_11_KEY_MATERIAL,
++			     HostCmd_ACT_GEN_SET, true, &encrypt_key, true)) {
++		mwifiex_dbg(priv->adapter, ERROR,
++			    "Sending KEY_MATERIAL command failed\n");
++		return -1;
++	}
++
++	return 0;
+ }
+ 
+ /*
+-- 
+2.31.1
+
+From 0d3a1e277248feaf3da3cd21f0ce68bfeec46f65 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 13 Apr 2021 12:44:03 +0200
+Subject: [PATCH] mwifiex: Fix an incorrect comment
+
+We're sending DELBA requests here, not ADDBA requests.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/11n.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c
+index 6696bce56178..b0695432b26a 100644
+--- a/drivers/net/wireless/marvell/mwifiex/11n.c
++++ b/drivers/net/wireless/marvell/mwifiex/11n.c
+@@ -125,7 +125,7 @@ int mwifiex_ret_11n_delba(struct mwifiex_private *priv,
+ 					   tx_ba_tbl->ra);
+ 	} else { /*
+ 		  * In case of failure, recreate the deleted stream in case
+-		  * we initiated the ADDBA
++		  * we initiated the DELBA
+ 		  */
+ 		if (!INITIATOR_BIT(del_ba_param_set))
+ 			return 0;
+-- 
+2.31.1
+
+From 06335e34442a195630c19a55abba6f7b4dc3b7ba Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 13 Apr 2021 12:45:59 +0200
+Subject: [PATCH] mwifiex: Send DELBA requests according to spec
+
+We're currently failing to set the initiator bit for DELBA requests:
+While we set the bit on our del_ba_param_set bitmask, we forget to
+actually copy that bitmask over to the command struct, which means we
+never actually set the initiator bit.
+
+Fix that and copy the bitmask over to the host_cmd_ds_11n_delba command
+struct.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/11n.c | 5 +++--
+ 1 file changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/11n.c b/drivers/net/wireless/marvell/mwifiex/11n.c
+index b0695432b26a..9ff2058bcd7e 100644
+--- a/drivers/net/wireless/marvell/mwifiex/11n.c
++++ b/drivers/net/wireless/marvell/mwifiex/11n.c
+@@ -657,14 +657,15 @@ int mwifiex_send_delba(struct mwifiex_private *priv, int tid, u8 *peer_mac,
+ 	uint16_t del_ba_param_set;
+ 
+ 	memset(&delba, 0, sizeof(delba));
+-	delba.del_ba_param_set = cpu_to_le16(tid << DELBA_TID_POS);
+ 
+-	del_ba_param_set = le16_to_cpu(delba.del_ba_param_set);
++	del_ba_param_set = tid << DELBA_TID_POS;
++
+ 	if (initiator)
+ 		del_ba_param_set |= IEEE80211_DELBA_PARAM_INITIATOR_MASK;
+ 	else
+ 		del_ba_param_set &= ~IEEE80211_DELBA_PARAM_INITIATOR_MASK;
+ 
++	delba.del_ba_param_set = cpu_to_le16(del_ba_param_set);
+ 	memcpy(&delba.peer_mac_addr, peer_mac, ETH_ALEN);
+ 
+ 	/* We don't wait for the response of this command */
+-- 
+2.31.1
+
+From c8196c059e85bacc463ebb7055359735bd20ad0a Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Tue, 13 Apr 2021 12:57:41 +0200
+Subject: [PATCH] mwifiex: Ignore BTCOEX events from the firmware
+
+The firmware of the pcie 88W8897 chip sends those events very
+unreliably, which means we sometimes end up actually capping the window
+size while bluetooth is disabled, artifically limiting wifi speeds even
+though it's not needed.
+
+Since we can't fix the firmware, let's just ignore those events, it
+seems that the Windows driver also doesn't change the rx/tx block ack
+buffer sizes when bluetooth gets enabled or disabled, so this is
+consistent with the Windows driver.
+
+Patchset: mwifiex
+---
+ drivers/net/wireless/marvell/mwifiex/sta_event.c | 4 +---
+ 1 file changed, 1 insertion(+), 3 deletions(-)
+
+diff --git a/drivers/net/wireless/marvell/mwifiex/sta_event.c b/drivers/net/wireless/marvell/mwifiex/sta_event.c
+index 68c63268e2e6..933111a3511c 100644
+--- a/drivers/net/wireless/marvell/mwifiex/sta_event.c
++++ b/drivers/net/wireless/marvell/mwifiex/sta_event.c
+@@ -1057,9 +1057,7 @@ int mwifiex_process_sta_event(struct mwifiex_private *priv)
+ 							adapter->event_skb);
+ 		break;
+ 	case EVENT_BT_COEX_WLAN_PARA_CHANGE:
+-		dev_dbg(adapter->dev, "EVENT: BT coex wlan param update\n");
+-		mwifiex_bt_coex_wlan_param_update_event(priv,
+-							adapter->event_skb);
++		dev_dbg(adapter->dev, "EVENT: ignoring BT coex wlan param update\n");
+ 		break;
+ 	case EVENT_RXBA_SYNC:
+ 		dev_dbg(adapter->dev, "EVENT: RXBA_SYNC\n");
+-- 
+2.31.1
+

+ 121 - 0
patches/5.12/0003-ath10k.patch

@@ -0,0 +1,121 @@
+From 19d746dc9980d9109b93c9424138d3966c8a67cf Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Sat, 27 Feb 2021 00:45:52 +0100
+Subject: [PATCH] ath10k: Add module parameters to override board files
+
+Some Surface devices, specifically the Surface Go and AMD version of the
+Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better
+with a different board file, as it seems that the firmeware included
+upstream is buggy.
+
+As it is generally not a good idea to randomly overwrite files, let
+alone doing so via packages, we add module parameters to override those
+file names in the driver. This allows us to package/deploy the override
+via a modprobe.d config.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Patchset: ath10k
+---
+ drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++
+ 1 file changed, 58 insertions(+)
+
+diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c
+index 2f9be182fbfb..84ae17af3f98 100644
+--- a/drivers/net/wireless/ath/ath10k/core.c
++++ b/drivers/net/wireless/ath/ath10k/core.c
+@@ -35,6 +35,9 @@ static bool skip_otp;
+ static bool rawmode;
+ static bool fw_diag_log;
+ 
++static char *override_board = "";
++static char *override_board2 = "";
++
+ unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) |
+ 				     BIT(ATH10K_FW_CRASH_DUMP_CE_DATA);
+ 
+@@ -47,6 +50,9 @@ module_param(rawmode, bool, 0644);
+ module_param(fw_diag_log, bool, 0644);
+ module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444);
+ 
++module_param(override_board, charp, 0644);
++module_param(override_board2, charp, 0644);
++
+ MODULE_PARM_DESC(debug_mask, "Debugging mask");
+ MODULE_PARM_DESC(uart_print, "Uart target debugging");
+ MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode");
+@@ -55,6 +61,9 @@ MODULE_PARM_DESC(rawmode, "Use raw 802.11 frame datapath");
+ MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file");
+ MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging");
+ 
++MODULE_PARM_DESC(override_board, "Override for board.bin file");
++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file");
++
+ static const struct ath10k_hw_params ath10k_hw_params_list[] = {
+ 	{
+ 		.id = QCA988X_HW_2_0_VERSION,
+@@ -826,6 +835,42 @@ static int ath10k_init_configure_target(struct ath10k *ar)
+ 	return 0;
+ }
+ 
++static const char *ath10k_override_board_fw_file(struct ath10k *ar,
++						 const char *file)
++{
++	if (strcmp(file, "board.bin") == 0) {
++		if (strcmp(override_board, "") == 0)
++			return file;
++
++		if (strcmp(override_board, "none") == 0) {
++			dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n");
++			return NULL;
++		}
++
++		dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n",
++			 override_board);
++
++		return override_board;
++	}
++
++	if (strcmp(file, "board-2.bin") == 0) {
++		if (strcmp(override_board2, "") == 0)
++			return file;
++
++		if (strcmp(override_board2, "none") == 0) {
++			dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n");
++			return NULL;
++		}
++
++		dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n",
++			 override_board2);
++
++		return override_board2;
++	}
++
++	return file;
++}
++
+ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar,
+ 						   const char *dir,
+ 						   const char *file)
+@@ -840,6 +885,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar,
+ 	if (dir == NULL)
+ 		dir = ".";
+ 
++	/* HACK: Override board.bin and board-2.bin files if specified.
++	 *
++	 * Some Surface devices perform better with a different board
++	 * configuration. To this end, one would need to replace the board.bin
++	 * file with the modified config and remove the board-2.bin file.
++	 * Unfortunately, that's not a solution that we can easily package. So
++	 * we add module options to perform these overrides here.
++	 */
++
++	file = ath10k_override_board_fw_file(ar, file);
++	if (!file)
++		return ERR_PTR(-ENOENT);
++
+ 	snprintf(filename, sizeof(filename), "%s/%s", dir, file);
+ 	ret = firmware_request_nowarn(&fw, filename, ar->dev);
+ 	ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n",
+-- 
+2.31.1
+

+ 1503 - 0
patches/5.12/0004-ipts.patch

@@ -0,0 +1,1503 @@
+From 744ada53b8de5b3aab134a0593f09d2845b10a24 Mon Sep 17 00:00:00 2001
+From: Dorian Stoll <dorian.stoll@tmsp.io>
+Date: Thu, 30 Jul 2020 13:21:53 +0200
+Subject: [PATCH] misc: mei: Add missing IPTS device IDs
+
+Patchset: ipts
+---
+ drivers/misc/mei/hw-me-regs.h | 1 +
+ drivers/misc/mei/pci-me.c     | 1 +
+ 2 files changed, 2 insertions(+)
+
+diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h
+index cb34925e10f1..2b3f8073a3ec 100644
+--- a/drivers/misc/mei/hw-me-regs.h
++++ b/drivers/misc/mei/hw-me-regs.h
+@@ -92,6 +92,7 @@
+ #define MEI_DEV_ID_CDF        0x18D3  /* Cedar Fork */
+ 
+ #define MEI_DEV_ID_ICP_LP     0x34E0  /* Ice Lake Point LP */
++#define MEI_DEV_ID_ICP_LP_3   0x34E4  /* Ice Lake Point LP 3 (iTouch) */
+ 
+ #define MEI_DEV_ID_JSP_N      0x4DE0  /* Jasper Lake Point N */
+ 
+diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c
+index c3393b383e59..0098f98426c1 100644
+--- a/drivers/misc/mei/pci-me.c
++++ b/drivers/misc/mei/pci-me.c
+@@ -96,6 +96,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = {
+ 	{MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)},
+ 
+ 	{MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)},
++	{MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)},
+ 
+ 	{MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)},
+ 	{MEI_PCI_DEVICE(MEI_DEV_ID_TGP_H, MEI_ME_PCH15_SPS_CFG)},
+-- 
+2.31.1
+
+From 714d09089cdf91c6f2f54f04d08be663884777eb Mon Sep 17 00:00:00 2001
+From: Dorian Stoll <dorian.stoll@tmsp.io>
+Date: Thu, 6 Aug 2020 11:20:41 +0200
+Subject: [PATCH] misc: Add support for Intel Precise Touch & Stylus
+
+Based on linux-surface/intel-precise-touch@3f362c
+
+Signed-off-by: Dorian Stoll <dorian.stoll@tmsp.io>
+Patchset: ipts
+---
+ drivers/misc/Kconfig          |   1 +
+ drivers/misc/Makefile         |   1 +
+ drivers/misc/ipts/Kconfig     |  17 ++
+ drivers/misc/ipts/Makefile    |  12 ++
+ drivers/misc/ipts/context.h   |  47 +++++
+ drivers/misc/ipts/control.c   | 113 +++++++++++
+ drivers/misc/ipts/control.h   |  24 +++
+ drivers/misc/ipts/mei.c       | 125 ++++++++++++
+ drivers/misc/ipts/protocol.h  | 347 ++++++++++++++++++++++++++++++++++
+ drivers/misc/ipts/receiver.c  | 224 ++++++++++++++++++++++
+ drivers/misc/ipts/receiver.h  |  16 ++
+ drivers/misc/ipts/resources.c | 128 +++++++++++++
+ drivers/misc/ipts/resources.h |  17 ++
+ drivers/misc/ipts/uapi.c      | 208 ++++++++++++++++++++
+ drivers/misc/ipts/uapi.h      |  47 +++++
+ 15 files changed, 1327 insertions(+)
+ create mode 100644 drivers/misc/ipts/Kconfig
+ create mode 100644 drivers/misc/ipts/Makefile
+ create mode 100644 drivers/misc/ipts/context.h
+ create mode 100644 drivers/misc/ipts/control.c
+ create mode 100644 drivers/misc/ipts/control.h
+ create mode 100644 drivers/misc/ipts/mei.c
+ create mode 100644 drivers/misc/ipts/protocol.h
+ create mode 100644 drivers/misc/ipts/receiver.c
+ create mode 100644 drivers/misc/ipts/receiver.h
+ create mode 100644 drivers/misc/ipts/resources.c
+ create mode 100644 drivers/misc/ipts/resources.h
+ create mode 100644 drivers/misc/ipts/uapi.c
+ create mode 100644 drivers/misc/ipts/uapi.h
+
+diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
+index f532c59bb59b..cc973f55f5ca 100644
+--- a/drivers/misc/Kconfig
++++ b/drivers/misc/Kconfig
+@@ -461,4 +461,5 @@ source "drivers/misc/bcm-vk/Kconfig"
+ source "drivers/misc/cardreader/Kconfig"
+ source "drivers/misc/habanalabs/Kconfig"
+ source "drivers/misc/uacce/Kconfig"
++source "drivers/misc/ipts/Kconfig"
+ endmenu
+diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
+index 99b6f15a3c70..9470a93d7fa4 100644
+--- a/drivers/misc/Makefile
++++ b/drivers/misc/Makefile
+@@ -56,3 +56,4 @@ obj-$(CONFIG_HABANA_AI)		+= habanalabs/
+ obj-$(CONFIG_UACCE)		+= uacce/
+ obj-$(CONFIG_XILINX_SDFEC)	+= xilinx_sdfec.o
+ obj-$(CONFIG_HISI_HIKEY_USB)	+= hisi_hikey_usb.o
++obj-$(CONFIG_MISC_IPTS)		+= ipts/
+diff --git a/drivers/misc/ipts/Kconfig b/drivers/misc/ipts/Kconfig
+new file mode 100644
+index 000000000000..83e2a930c396
+--- /dev/null
++++ b/drivers/misc/ipts/Kconfig
+@@ -0,0 +1,17 @@
++# SPDX-License-Identifier: GPL-2.0-or-later
++
++config MISC_IPTS
++	tristate "Intel Precise Touch & Stylus"
++	depends on INTEL_MEI
++	help
++	  Say Y here if your system has a touchscreen using Intels
++	  Precise Touch & Stylus (IPTS) technology.
++
++	  If unsure say N.
++
++	  To compile this driver as a module, choose M here: the
++	  module will be called ipts.
++
++	  Building this driver alone will not give you a working touchscreen.
++	  It only exposed a userspace API that can be used by a daemon to
++	  receive and process data from the touchscreen hardware.
+diff --git a/drivers/misc/ipts/Makefile b/drivers/misc/ipts/Makefile
+new file mode 100644
+index 000000000000..8f58b9adbc94
+--- /dev/null
++++ b/drivers/misc/ipts/Makefile
+@@ -0,0 +1,12 @@
++# SPDX-License-Identifier: GPL-2.0-or-later
++#
++# Makefile for the IPTS touchscreen driver
++#
++
++obj-$(CONFIG_MISC_IPTS) += ipts.o
++ipts-objs := control.o
++ipts-objs += mei.o
++ipts-objs += receiver.o
++ipts-objs += resources.o
++ipts-objs += uapi.o
++
+diff --git a/drivers/misc/ipts/context.h b/drivers/misc/ipts/context.h
+new file mode 100644
+index 000000000000..f4b06a2d3f72
+--- /dev/null
++++ b/drivers/misc/ipts/context.h
+@@ -0,0 +1,47 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_CONTEXT_H_
++#define _IPTS_CONTEXT_H_
++
++#include <linux/cdev.h>
++#include <linux/device.h>
++#include <linux/mei_cl_bus.h>
++#include <linux/types.h>
++
++#include "protocol.h"
++
++enum ipts_host_status {
++	IPTS_HOST_STATUS_STARTING,
++	IPTS_HOST_STATUS_STARTED,
++	IPTS_HOST_STATUS_STOPPING,
++	IPTS_HOST_STATUS_STOPPED,
++};
++
++struct ipts_buffer_info {
++	u8 *address;
++	dma_addr_t dma_address;
++};
++
++struct ipts_context {
++	struct mei_cl_device *cldev;
++	struct device *dev;
++
++	bool restart;
++	enum ipts_host_status status;
++	struct ipts_get_device_info_rsp device_info;
++
++	struct ipts_buffer_info data[IPTS_BUFFERS];
++	struct ipts_buffer_info doorbell;
++
++	struct ipts_buffer_info feedback[IPTS_BUFFERS];
++	struct ipts_buffer_info workqueue;
++	struct ipts_buffer_info host2me;
++};
++
++#endif /* _IPTS_CONTEXT_H_ */
+diff --git a/drivers/misc/ipts/control.c b/drivers/misc/ipts/control.c
+new file mode 100644
+index 000000000000..a1d1f97a13d7
+--- /dev/null
++++ b/drivers/misc/ipts/control.c
+@@ -0,0 +1,113 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#include <linux/mei_cl_bus.h>
++
++#include "context.h"
++#include "protocol.h"
++#include "resources.h"
++#include "uapi.h"
++
++int ipts_control_send(struct ipts_context *ipts, u32 code, void *payload,
++		      size_t size)
++{
++	int ret;
++	struct ipts_command cmd;
++
++	memset(&cmd, 0, sizeof(struct ipts_command));
++	cmd.code = code;
++
++	if (payload && size > 0)
++		memcpy(&cmd.payload, payload, size);
++
++	ret = mei_cldev_send(ipts->cldev, (u8 *)&cmd, sizeof(cmd.code) + size);
++	if (ret >= 0)
++		return 0;
++
++	/*
++	 * During shutdown the device might get pulled away from below our feet.
++	 * Dont log an error in this case, because it will confuse people.
++	 */
++	if (ret != -ENODEV || ipts->status != IPTS_HOST_STATUS_STOPPING)
++		dev_err(ipts->dev, "Error while sending: 0x%X:%d\n", code, ret);
++
++	return ret;
++}
++
++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer)
++{
++	struct ipts_feedback_cmd cmd;
++
++	memset(&cmd, 0, sizeof(struct ipts_feedback_cmd));
++	cmd.buffer = buffer;
++
++	return ipts_control_send(ipts, IPTS_CMD_FEEDBACK, &cmd,
++				 sizeof(struct ipts_feedback_cmd));
++}
++
++int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value)
++{
++	struct ipts_feedback_buffer *feedback;
++
++	memset(ipts->host2me.address, 0, ipts->device_info.feedback_size);
++	feedback = (struct ipts_feedback_buffer *)ipts->host2me.address;
++
++	feedback->cmd_type = IPTS_FEEDBACK_CMD_TYPE_NONE;
++	feedback->data_type = IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES;
++	feedback->buffer = IPTS_HOST2ME_BUFFER;
++	feedback->size = 2;
++	feedback->payload[0] = report;
++	feedback->payload[1] = value;
++
++	return ipts_control_send_feedback(ipts, IPTS_HOST2ME_BUFFER);
++}
++
++int ipts_control_start(struct ipts_context *ipts)
++{
++	if (ipts->status != IPTS_HOST_STATUS_STOPPED)
++		return -EBUSY;
++
++	dev_info(ipts->dev, "Starting IPTS\n");
++	ipts->status = IPTS_HOST_STATUS_STARTING;
++	ipts->restart = false;
++
++	ipts_uapi_link(ipts);
++	return ipts_control_send(ipts, IPTS_CMD_GET_DEVICE_INFO, NULL, 0);
++}
++
++int ipts_control_stop(struct ipts_context *ipts)
++{
++	int ret;
++
++	if (ipts->status == IPTS_HOST_STATUS_STOPPING)
++		return -EBUSY;
++
++	if (ipts->status == IPTS_HOST_STATUS_STOPPED)
++		return -EBUSY;
++
++	dev_info(ipts->dev, "Stopping IPTS\n");
++	ipts->status = IPTS_HOST_STATUS_STOPPING;
++
++	ipts_uapi_unlink();
++	ipts_resources_free(ipts);
++
++	ret = ipts_control_send_feedback(ipts, 0);
++	if (ret == -ENODEV)
++		ipts->status = IPTS_HOST_STATUS_STOPPED;
++
++	return ret;
++}
++
++int ipts_control_restart(struct ipts_context *ipts)
++{
++	if (ipts->restart)
++		return -EBUSY;
++
++	ipts->restart = true;
++	return ipts_control_stop(ipts);
++}
+diff --git a/drivers/misc/ipts/control.h b/drivers/misc/ipts/control.h
+new file mode 100644
+index 000000000000..2c44e9e0e99f
+--- /dev/null
++++ b/drivers/misc/ipts/control.h
+@@ -0,0 +1,24 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_CONTROL_H_
++#define _IPTS_CONTROL_H_
++
++#include <linux/types.h>
++
++#include "context.h"
++
++int ipts_control_send(struct ipts_context *ipts, u32 cmd, void *payload,
++		      size_t size);
++int ipts_control_send_feedback(struct ipts_context *ipts, u32 buffer);
++int ipts_control_set_feature(struct ipts_context *ipts, u8 report, u8 value);
++int ipts_control_start(struct ipts_context *ipts);
++int ipts_control_restart(struct ipts_context *ipts);
++int ipts_control_stop(struct ipts_context *ipts);
++
++#endif /* _IPTS_CONTROL_H_ */
+diff --git a/drivers/misc/ipts/mei.c b/drivers/misc/ipts/mei.c
+new file mode 100644
+index 000000000000..59ecf13e00d2
+--- /dev/null
++++ b/drivers/misc/ipts/mei.c
+@@ -0,0 +1,125 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#include <linux/delay.h>
++#include <linux/dma-mapping.h>
++#include <linux/mei_cl_bus.h>
++#include <linux/mod_devicetable.h>
++#include <linux/module.h>
++#include <linux/slab.h>
++
++#include "context.h"
++#include "control.h"
++#include "protocol.h"
++#include "receiver.h"
++#include "uapi.h"
++
++static int ipts_mei_set_dma_mask(struct mei_cl_device *cldev)
++{
++	int ret;
++
++	ret = dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(64));
++	if (!ret)
++		return 0;
++
++	return dma_coerce_mask_and_coherent(&cldev->dev, DMA_BIT_MASK(32));
++}
++
++static int ipts_mei_probe(struct mei_cl_device *cldev,
++			  const struct mei_cl_device_id *id)
++{
++	int ret;
++	struct ipts_context *ipts;
++
++	if (ipts_mei_set_dma_mask(cldev)) {
++		dev_err(&cldev->dev, "Failed to set DMA mask for IPTS\n");
++		return -EFAULT;
++	}
++
++	ret = mei_cldev_enable(cldev);
++	if (ret) {
++		dev_err(&cldev->dev, "Failed to enable MEI device: %d\n", ret);
++		return ret;
++	}
++
++	ipts = kzalloc(sizeof(*ipts), GFP_KERNEL);
++	if (!ipts) {
++		mei_cldev_disable(cldev);
++		return -ENOMEM;
++	}
++
++	ipts->cldev = cldev;
++	ipts->dev = &cldev->dev;
++	ipts->status = IPTS_HOST_STATUS_STOPPED;
++
++	mei_cldev_set_drvdata(cldev, ipts);
++	mei_cldev_register_rx_cb(cldev, ipts_receiver_callback);
++
++	return ipts_control_start(ipts);
++}
++
++static void ipts_mei_remove(struct mei_cl_device *cldev)
++{
++	int i;
++	struct ipts_context *ipts = mei_cldev_get_drvdata(cldev);
++
++	ipts_control_stop(ipts);
++
++	for (i = 0; i < 20; i++) {
++		if (ipts->status == IPTS_HOST_STATUS_STOPPED)
++			break;
++
++		msleep(25);
++	}
++
++	mei_cldev_disable(cldev);
++	kfree(ipts);
++}
++
++static struct mei_cl_device_id ipts_mei_device_id_table[] = {
++	{ "", IPTS_MEI_UUID, MEI_CL_VERSION_ANY },
++	{},
++};
++MODULE_DEVICE_TABLE(mei, ipts_mei_device_id_table);
++
++static struct mei_cl_driver ipts_mei_driver = {
++	.id_table = ipts_mei_device_id_table,
++	.name = "ipts",
++	.probe = ipts_mei_probe,
++	.remove = ipts_mei_remove,
++};
++
++static int __init ipts_mei_init(void)
++{
++	int ret;
++
++	ret = ipts_uapi_init();
++	if (ret)
++		return ret;
++
++	ret = mei_cldev_driver_register(&ipts_mei_driver);
++	if (ret) {
++		ipts_uapi_free();
++		return ret;
++	}
++
++	return 0;
++}
++
++static void __exit ipts_mei_exit(void)
++{
++	mei_cldev_driver_unregister(&ipts_mei_driver);
++	ipts_uapi_free();
++}
++
++MODULE_DESCRIPTION("IPTS touchscreen driver");
++MODULE_AUTHOR("Dorian Stoll <dorian.stoll@tmsp.io>");
++MODULE_LICENSE("GPL");
++
++module_init(ipts_mei_init);
++module_exit(ipts_mei_exit);
+diff --git a/drivers/misc/ipts/protocol.h b/drivers/misc/ipts/protocol.h
+new file mode 100644
+index 000000000000..c3458904a94d
+--- /dev/null
++++ b/drivers/misc/ipts/protocol.h
+@@ -0,0 +1,347 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_PROTOCOL_H_
++#define _IPTS_PROTOCOL_H_
++
++#include <linux/types.h>
++
++/*
++ * The MEI client ID for IPTS functionality.
++ */
++#define IPTS_MEI_UUID                                                          \
++	UUID_LE(0x3e8d0870, 0x271a, 0x4208, 0x8e, 0xb5, 0x9a, 0xcb, 0x94,      \
++		0x02, 0xae, 0x04)
++
++/*
++ * Queries the device for vendor specific information.
++ *
++ * The command must not contain any payload.
++ * The response will contain struct ipts_get_device_info_rsp as payload.
++ */
++#define IPTS_CMD_GET_DEVICE_INFO 0x00000001
++#define IPTS_RSP_GET_DEVICE_INFO 0x80000001
++
++/*
++ * Sets the mode that IPTS will operate in.
++ *
++ * The command must contain struct ipts_set_mode_cmd as payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_SET_MODE 0x00000002
++#define IPTS_RSP_SET_MODE 0x80000002
++
++/*
++ * Configures the memory buffers that the ME will use
++ * for passing data to the host.
++ *
++ * The command must contain struct ipts_set_mem_window_cmd as payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_SET_MEM_WINDOW 0x00000003
++#define IPTS_RSP_SET_MEM_WINDOW 0x80000003
++
++/*
++ * Signals that the host is ready to receive data to the ME.
++ *
++ * The command must not contain any payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_READY_FOR_DATA 0x00000005
++#define IPTS_RSP_READY_FOR_DATA 0x80000005
++
++/*
++ * Signals that a buffer can be refilled to the ME.
++ *
++ * The command must contain struct ipts_feedback_cmd as payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_FEEDBACK 0x00000006
++#define IPTS_RSP_FEEDBACK 0x80000006
++
++/*
++ * Resets the data flow from the ME to the hosts and
++ * clears the buffers that were set with SET_MEM_WINDOW.
++ *
++ * The command must not contain any payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_CLEAR_MEM_WINDOW 0x00000007
++#define IPTS_RSP_CLEAR_MEM_WINDOW 0x80000007
++
++/*
++ * Instructs the ME to reset the touch sensor.
++ *
++ * The command must contain struct ipts_reset_sensor_cmd as payload.
++ * The response will not contain any payload.
++ */
++#define IPTS_CMD_RESET_SENSOR 0x0000000B
++#define IPTS_RSP_RESET_SENSOR 0x8000000B
++
++/**
++ * enum ipts_status - Possible status codes returned by IPTS commands.
++ * @IPTS_STATUS_SUCCESS:                 Operation completed successfully.
++ * @IPTS_STATUS_INVALID_PARAMS:          Command contained a payload with invalid parameters.
++ * @IPTS_STATUS_ACCESS_DENIED:           ME could not validate buffer addresses supplied by host.
++ * @IPTS_STATUS_CMD_SIZE_ERROR:          Command contains an invalid payload.
++ * @IPTS_STATUS_NOT_READY:               Buffer addresses have not been set.
++ * @IPTS_STATUS_REQUEST_OUTSTANDING:     There is an outstanding command of the same type.
++ *                                       The host must wait for a response before sending another
++ *                                       command of the same type.
++ * @IPTS_STATUS_NO_SENSOR_FOUND:         No sensor could be found. Either no sensor is connected, it
++ *                                       has not been initialized yet, or the system is improperly
++ *                                       configured.
++ * @IPTS_STATUS_OUT_OF_MEMORY:           Not enough free memory for requested operation.
++ * @IPTS_STATUS_INTERNAL_ERROR:          An unexpected error occurred.
++ * @IPTS_STATUS_SENSOR_DISABLED:         The sensor has been disabled and must be reinitialized.
++ * @IPTS_STATUS_COMPAT_CHECK_FAIL:       Compatibility revision check between sensor and ME failed.
++ *                                       The host can ignore this error and attempt to continue.
++ * @IPTS_STATUS_SENSOR_EXPECTED_RESET:   The sensor went through a reset initiated by ME or host.
++ * @IPTS_STATUS_SENSOR_UNEXPECTED_RESET: The sensor went through an unexpected reset.
++ * @IPTS_STATUS_RESET_FAILED:            Requested sensor reset failed to complete.
++ * @IPTS_STATUS_TIMEOUT:                 The operation timed out.
++ * @IPTS_STATUS_TEST_MODE_FAIL:          Test mode pattern did not match expected values.
++ * @IPTS_STATUS_SENSOR_FAIL_FATAL:       The sensor reported a fatal error during reset sequence.
++ *                                       Further progress is not possible.
++ * @IPTS_STATUS_SENSOR_FAIL_NONFATAL:    The sensor reported a fatal error during reset sequence.
++ *                                       The host can attempt to continue.
++ * @IPTS_STATUS_INVALID_DEVICE_CAPS:     The device reported invalid capabilities.
++ * @IPTS_STATUS_QUIESCE_IO_IN_PROGRESS:  Command cannot be completed until Quiesce IO is done.
++ */
++enum ipts_status {
++	IPTS_STATUS_SUCCESS = 0,
++	IPTS_STATUS_INVALID_PARAMS = 1,
++	IPTS_STATUS_ACCESS_DENIED = 2,
++	IPTS_STATUS_CMD_SIZE_ERROR = 3,
++	IPTS_STATUS_NOT_READY = 4,
++	IPTS_STATUS_REQUEST_OUTSTANDING = 5,
++	IPTS_STATUS_NO_SENSOR_FOUND = 6,
++	IPTS_STATUS_OUT_OF_MEMORY = 7,
++	IPTS_STATUS_INTERNAL_ERROR = 8,
++	IPTS_STATUS_SENSOR_DISABLED = 9,
++	IPTS_STATUS_COMPAT_CHECK_FAIL = 10,
++	IPTS_STATUS_SENSOR_EXPECTED_RESET = 11,
++	IPTS_STATUS_SENSOR_UNEXPECTED_RESET = 12,
++	IPTS_STATUS_RESET_FAILED = 13,
++	IPTS_STATUS_TIMEOUT = 14,
++	IPTS_STATUS_TEST_MODE_FAIL = 15,
++	IPTS_STATUS_SENSOR_FAIL_FATAL = 16,
++	IPTS_STATUS_SENSOR_FAIL_NONFATAL = 17,
++	IPTS_STATUS_INVALID_DEVICE_CAPS = 18,
++	IPTS_STATUS_QUIESCE_IO_IN_PROGRESS = 19,
++};
++
++/*
++ * The amount of buffers that is used for IPTS
++ */
++#define IPTS_BUFFERS 16
++
++/*
++ * The special buffer ID that is used for direct host2me feedback.
++ */
++#define IPTS_HOST2ME_BUFFER IPTS_BUFFERS
++
++/**
++ * enum ipts_mode - Operation mode for IPTS hardware
++ * @IPTS_MODE_SINGLETOUCH: Fallback that supports only one finger and no stylus.
++ *                         The data is received as a HID report with ID 64.
++ * @IPTS_MODE_MULTITOUCH:  The "proper" operation mode for IPTS. It will return
++ *                         stylus data as well as capacitive heatmap touch data.
++ *                         This data needs to be processed in userspace.
++ */
++enum ipts_mode {
++	IPTS_MODE_SINGLETOUCH = 0,
++	IPTS_MODE_MULTITOUCH = 1,
++};
++
++/**
++ * struct ipts_set_mode_cmd - Payload for the SET_MODE command.
++ * @mode: The mode that IPTS should operate in.
++ */
++struct ipts_set_mode_cmd {
++	enum ipts_mode mode;
++	u8 reserved[12];
++} __packed;
++
++#define IPTS_WORKQUEUE_SIZE	 8192
++#define IPTS_WORKQUEUE_ITEM_SIZE 16
++
++/**
++ * struct ipts_set_mem_window_cmd - Payload for the SET_MEM_WINDOW command.
++ * @data_buffer_addr_lower:     Lower 32 bits of the data buffer addresses.
++ * @data_buffer_addr_upper:     Upper 32 bits of the data buffer addresses.
++ * @workqueue_addr_lower:       Lower 32 bits of the workqueue buffer address.
++ * @workqueue_addr_upper:       Upper 32 bits of the workqueue buffer address.
++ * @doorbell_addr_lower:        Lower 32 bits of the doorbell buffer address.
++ * @doorbell_addr_upper:        Upper 32 bits of the doorbell buffer address.
++ * @feedback_buffer_addr_lower: Lower 32 bits of the feedback buffer addresses.
++ * @feedback_buffer_addr_upper: Upper 32 bits of the feedback buffer addresses.
++ * @host2me_addr_lower:         Lower 32 bits of the host2me buffer address.
++ * @host2me_addr_upper:         Upper 32 bits of the host2me buffer address.
++ * @workqueue_item_size:        Magic value. (IPTS_WORKQUEUE_ITEM_SIZE)
++ * @workqueue_size:             Magic value. (IPTS_WORKQUEUE_SIZE)
++ *
++ * The data buffers are buffers that get filled with touch data by the ME.
++ * The doorbell buffer is a u32 that gets incremented by the ME once a data
++ * buffer has been filled with new data.
++ *
++ * The other buffers are required for using GuC submission with binary
++ * firmware. Since support for GuC submission has been dropped from i915,
++ * they are not used anymore, but they need to be allocated and passed,
++ * otherwise the hardware will refuse to start.
++ */
++struct ipts_set_mem_window_cmd {
++	u32 data_buffer_addr_lower[IPTS_BUFFERS];
++	u32 data_buffer_addr_upper[IPTS_BUFFERS];
++	u32 workqueue_addr_lower;
++	u32 workqueue_addr_upper;
++	u32 doorbell_addr_lower;
++	u32 doorbell_addr_upper;
++	u32 feedback_buffer_addr_lower[IPTS_BUFFERS];
++	u32 feedback_buffer_addr_upper[IPTS_BUFFERS];
++	u32 host2me_addr_lower;
++	u32 host2me_addr_upper;
++	u32 host2me_size;
++	u8 reserved1;
++	u8 workqueue_item_size;
++	u16 workqueue_size;
++	u8 reserved[32];
++} __packed;
++
++/**
++ * struct ipts_feedback_cmd - Payload for the FEEDBACK command.
++ * @buffer: The buffer that the ME should refill.
++ */
++struct ipts_feedback_cmd {
++	u32 buffer;
++	u8 reserved[12];
++} __packed;
++
++/**
++ * enum ipts_feedback_cmd_type - Commands that can be executed on the sensor through feedback.
++ */
++enum ipts_feedback_cmd_type {
++	IPTS_FEEDBACK_CMD_TYPE_NONE = 0,
++	IPTS_FEEDBACK_CMD_TYPE_SOFT_RESET = 1,
++	IPTS_FEEDBACK_CMD_TYPE_GOTO_ARMED = 2,
++	IPTS_FEEDBACK_CMD_TYPE_GOTO_SENSING = 3,
++	IPTS_FEEDBACK_CMD_TYPE_GOTO_SLEEP = 4,
++	IPTS_FEEDBACK_CMD_TYPE_GOTO_DOZE = 5,
++	IPTS_FEEDBACK_CMD_TYPE_HARD_RESET = 6,
++};
++
++/**
++ * enum ipts_feedback_data_type - Describes the data that a feedback buffer contains.
++ * @IPTS_FEEDBACK_DATA_TYPE_VENDOR:        The buffer contains vendor specific feedback.
++ * @IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES:  The buffer contains a HID set features command.
++ * @IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES:  The buffer contains a HID get features command.
++ * @IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT: The buffer contains a HID output report.
++ * @IPTS_FEEDBACK_DATA_TYPE_STORE_DATA:    The buffer contains calibration data for the sensor.
++ */
++enum ipts_feedback_data_type {
++	IPTS_FEEDBACK_DATA_TYPE_VENDOR = 0,
++	IPTS_FEEDBACK_DATA_TYPE_SET_FEATURES = 1,
++	IPTS_FEEDBACK_DATA_TYPE_GET_FEATURES = 2,
++	IPTS_FEEDBACK_DATA_TYPE_OUTPUT_REPORT = 3,
++	IPTS_FEEDBACK_DATA_TYPE_STORE_DATA = 4,
++};
++
++/**
++ * struct ipts_feedback_buffer - The contents of an IPTS feedback buffer.
++ * @cmd_type: A command that should be executed on the sensor.
++ * @size: The size of the payload to be written.
++ * @buffer: The ID of the buffer that contains this feedback data.
++ * @protocol: The protocol version of the EDS.
++ * @data_type: The type of payload that the buffer contains.
++ * @spi_offset: The offset at which to write the payload data.
++ * @payload: Payload for the feedback command, or 0 if no payload is sent.
++ */
++struct ipts_feedback_buffer {
++	enum ipts_feedback_cmd_type cmd_type;
++	u32 size;
++	u32 buffer;
++	u32 protocol;
++	enum ipts_feedback_data_type data_type;
++	u32 spi_offset;
++	u8 reserved[40];
++	u8 payload[];
++} __packed;
++
++/**
++ * enum ipts_reset_type - Possible ways of resetting the touch sensor
++ * @IPTS_RESET_TYPE_HARD: Perform hardware reset using GPIO pin.
++ * @IPTS_RESET_TYPE_SOFT: Perform software reset using SPI interface.
++ */
++enum ipts_reset_type {
++	IPTS_RESET_TYPE_HARD = 0,
++	IPTS_RESET_TYPE_SOFT = 1,
++};
++
++/**
++ * struct ipts_reset_sensor_cmd - Payload for the RESET_SENSOR command.
++ * @type: What type of reset should be performed.
++ */
++struct ipts_reset_sensor_cmd {
++	enum ipts_reset_type type;
++	u8 reserved[4];
++} __packed;
++
++/**
++ * struct ipts_command - A message sent from the host to the ME.
++ * @code:    The message code describing the command. (see IPTS_CMD_*)
++ * @payload: Payload for the command, or 0 if no payload is required.
++ */
++struct ipts_command {
++	u32 code;
++	u8 payload[320];
++} __packed;
++
++/**
++ * struct ipts_device_info - Payload for the GET_DEVICE_INFO response.
++ * @vendor_id:     Vendor ID of the touch sensor.
++ * @device_id:     Device ID of the touch sensor.
++ * @hw_rev:        Hardware revision of the touch sensor.
++ * @fw_rev:        Firmware revision of the touch sensor.
++ * @data_size:     Required size of one data buffer.
++ * @feedback_size: Required size of one feedback buffer.
++ * @mode:          Current operation mode of IPTS.
++ * @max_contacts:  The amount of concurrent touches supported by the sensor.
++ */
++struct ipts_get_device_info_rsp {
++	u16 vendor_id;
++	u16 device_id;
++	u32 hw_rev;
++	u32 fw_rev;
++	u32 data_size;
++	u32 feedback_size;
++	enum ipts_mode mode;
++	u8 max_contacts;
++	u8 reserved[19];
++} __packed;
++
++/**
++ * struct ipts_feedback_rsp - Payload for the FEEDBACK response.
++ * @buffer: The buffer that has received feedback.
++ */
++struct ipts_feedback_rsp {
++	u32 buffer;
++} __packed;
++
++/**
++ * struct ipts_response - A message sent from the ME to the host.
++ * @code:    The message code describing the response. (see IPTS_RSP_*)
++ * @status:  The status code returned by the command.
++ * @payload: Payload returned by the command.
++ */
++struct ipts_response {
++	u32 code;
++	enum ipts_status status;
++	u8 payload[80];
++} __packed;
++
++#endif /* _IPTS_PROTOCOL_H_ */
+diff --git a/drivers/misc/ipts/receiver.c b/drivers/misc/ipts/receiver.c
+new file mode 100644
+index 000000000000..23dca13c2139
+--- /dev/null
++++ b/drivers/misc/ipts/receiver.c
+@@ -0,0 +1,224 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#include <linux/mei_cl_bus.h>
++#include <linux/moduleparam.h>
++#include <linux/types.h>
++
++#include "context.h"
++#include "control.h"
++#include "protocol.h"
++#include "resources.h"
++
++/*
++ * Temporary parameter to guard gen7 multitouch mode.
++ * Remove once gen7 has stable iptsd support.
++ */
++static bool gen7mt;
++module_param(gen7mt, bool, 0644);
++
++static int ipts_receiver_handle_get_device_info(struct ipts_context *ipts,
++						struct ipts_response *rsp)
++{
++	struct ipts_set_mode_cmd cmd;
++
++	memcpy(&ipts->device_info, rsp->payload,
++	       sizeof(struct ipts_get_device_info_rsp));
++
++	memset(&cmd, 0, sizeof(struct ipts_set_mode_cmd));
++	cmd.mode = IPTS_MODE_MULTITOUCH;
++
++	return ipts_control_send(ipts, IPTS_CMD_SET_MODE, &cmd,
++				 sizeof(struct ipts_set_mode_cmd));
++}
++
++static int ipts_receiver_handle_set_mode(struct ipts_context *ipts)
++{
++	int i, ret;
++	struct ipts_set_mem_window_cmd cmd;
++
++	ret = ipts_resources_alloc(ipts);
++	if (ret) {
++		dev_err(ipts->dev, "Failed to allocate resources\n");
++		return ret;
++	}
++
++	memset(&cmd, 0, sizeof(struct ipts_set_mem_window_cmd));
++
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		cmd.data_buffer_addr_lower[i] =
++			lower_32_bits(ipts->data[i].dma_address);
++
++		cmd.data_buffer_addr_upper[i] =
++			upper_32_bits(ipts->data[i].dma_address);
++
++		cmd.feedback_buffer_addr_lower[i] =
++			lower_32_bits(ipts->feedback[i].dma_address);
++
++		cmd.feedback_buffer_addr_upper[i] =
++			upper_32_bits(ipts->feedback[i].dma_address);
++	}
++
++	cmd.workqueue_addr_lower = lower_32_bits(ipts->workqueue.dma_address);
++	cmd.workqueue_addr_upper = upper_32_bits(ipts->workqueue.dma_address);
++
++	cmd.doorbell_addr_lower = lower_32_bits(ipts->doorbell.dma_address);
++	cmd.doorbell_addr_upper = upper_32_bits(ipts->doorbell.dma_address);
++
++	cmd.host2me_addr_lower = lower_32_bits(ipts->host2me.dma_address);
++	cmd.host2me_addr_upper = upper_32_bits(ipts->host2me.dma_address);
++
++	cmd.workqueue_size = IPTS_WORKQUEUE_SIZE;
++	cmd.workqueue_item_size = IPTS_WORKQUEUE_ITEM_SIZE;
++
++	return ipts_control_send(ipts, IPTS_CMD_SET_MEM_WINDOW, &cmd,
++				 sizeof(struct ipts_set_mem_window_cmd));
++}
++
++static int ipts_receiver_handle_set_mem_window(struct ipts_context *ipts)
++{
++	int ret;
++
++	dev_info(ipts->dev, "Device %04hX:%04hX ready\n",
++		 ipts->device_info.vendor_id, ipts->device_info.device_id);
++	ipts->status = IPTS_HOST_STATUS_STARTED;
++
++	ret = ipts_control_send(ipts, IPTS_CMD_READY_FOR_DATA, NULL, 0);
++	if (ret)
++		return ret;
++
++	if (!gen7mt)
++		return 0;
++
++	return ipts_control_set_feature(ipts, 0x5, 0x1);
++}
++
++static int ipts_receiver_handle_feedback(struct ipts_context *ipts,
++					 struct ipts_response *rsp)
++{
++	struct ipts_feedback_rsp feedback;
++
++	if (ipts->status != IPTS_HOST_STATUS_STOPPING)
++		return 0;
++
++	memcpy(&feedback, rsp->payload, sizeof(feedback));
++
++	if (feedback.buffer < IPTS_BUFFERS - 1)
++		return ipts_control_send_feedback(ipts, feedback.buffer + 1);
++
++	return ipts_control_send(ipts, IPTS_CMD_CLEAR_MEM_WINDOW, NULL, 0);
++}
++
++static int ipts_receiver_handle_clear_mem_window(struct ipts_context *ipts)
++{
++	ipts->status = IPTS_HOST_STATUS_STOPPED;
++
++	if (ipts->restart)
++		return ipts_control_start(ipts);
++
++	return 0;
++}
++
++static bool ipts_receiver_sensor_was_reset(u32 status)
++{
++	return status == IPTS_STATUS_SENSOR_EXPECTED_RESET ||
++	       status == IPTS_STATUS_SENSOR_UNEXPECTED_RESET;
++}
++
++static bool ipts_receiver_handle_error(struct ipts_context *ipts,
++				       struct ipts_response *rsp)
++{
++	bool error;
++
++	switch (rsp->status) {
++	case IPTS_STATUS_SUCCESS:
++	case IPTS_STATUS_COMPAT_CHECK_FAIL:
++		error = false;
++		break;
++	case IPTS_STATUS_INVALID_PARAMS:
++		error = rsp->code != IPTS_RSP_FEEDBACK;
++		break;
++	case IPTS_STATUS_SENSOR_DISABLED:
++		error = ipts->status != IPTS_HOST_STATUS_STOPPING;
++		break;
++	default:
++		error = true;
++		break;
++	}
++
++	if (!error)
++		return false;
++
++	dev_err(ipts->dev, "Command 0x%08x failed: %d\n", rsp->code,
++		rsp->status);
++
++	if (ipts_receiver_sensor_was_reset(rsp->status)) {
++		dev_err(ipts->dev, "Sensor was reset\n");
++
++		if (ipts_control_restart(ipts))
++			dev_err(ipts->dev, "Failed to restart IPTS\n");
++	}
++
++	return true;
++}
++
++static void ipts_receiver_handle_response(struct ipts_context *ipts,
++					  struct ipts_response *rsp)
++{
++	int ret;
++
++	if (ipts_receiver_handle_error(ipts, rsp))
++		return;
++
++	switch (rsp->code) {
++	case IPTS_RSP_GET_DEVICE_INFO:
++		ret = ipts_receiver_handle_get_device_info(ipts, rsp);
++		break;
++	case IPTS_RSP_SET_MODE:
++		ret = ipts_receiver_handle_set_mode(ipts);
++		break;
++	case IPTS_RSP_SET_MEM_WINDOW:
++		ret = ipts_receiver_handle_set_mem_window(ipts);
++		break;
++	case IPTS_RSP_FEEDBACK:
++		ret = ipts_receiver_handle_feedback(ipts, rsp);
++		break;
++	case IPTS_RSP_CLEAR_MEM_WINDOW:
++		ret = ipts_receiver_handle_clear_mem_window(ipts);
++		break;
++	default:
++		ret = 0;
++		break;
++	}
++
++	if (!ret)
++		return;
++
++	dev_err(ipts->dev, "Error while handling response 0x%08x: %d\n",
++		rsp->code, ret);
++
++	if (ipts_control_stop(ipts))
++		dev_err(ipts->dev, "Failed to stop IPTS\n");
++}
++
++void ipts_receiver_callback(struct mei_cl_device *cldev)
++{
++	int ret;
++	struct ipts_response rsp;
++	struct ipts_context *ipts;
++
++	ipts = mei_cldev_get_drvdata(cldev);
++
++	ret = mei_cldev_recv(cldev, (u8 *)&rsp, sizeof(struct ipts_response));
++	if (ret <= 0) {
++		dev_err(ipts->dev, "Error while reading response: %d\n", ret);
++		return;
++	}
++
++	ipts_receiver_handle_response(ipts, &rsp);
++}
+diff --git a/drivers/misc/ipts/receiver.h b/drivers/misc/ipts/receiver.h
+new file mode 100644
+index 000000000000..7f075afa7ef8
+--- /dev/null
++++ b/drivers/misc/ipts/receiver.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_RECEIVER_H_
++#define _IPTS_RECEIVER_H_
++
++#include <linux/mei_cl_bus.h>
++
++void ipts_receiver_callback(struct mei_cl_device *cldev);
++
++#endif /* _IPTS_RECEIVER_H_ */
+diff --git a/drivers/misc/ipts/resources.c b/drivers/misc/ipts/resources.c
+new file mode 100644
+index 000000000000..8e3a2409e438
+--- /dev/null
++++ b/drivers/misc/ipts/resources.c
+@@ -0,0 +1,128 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#include <linux/dma-mapping.h>
++
++#include "context.h"
++
++void ipts_resources_free(struct ipts_context *ipts)
++{
++	int i;
++	struct ipts_buffer_info *buffers;
++
++	u32 data_buffer_size = ipts->device_info.data_size;
++	u32 feedback_buffer_size = ipts->device_info.feedback_size;
++
++	buffers = ipts->data;
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		if (!buffers[i].address)
++			continue;
++
++		dma_free_coherent(ipts->dev, data_buffer_size,
++				  buffers[i].address, buffers[i].dma_address);
++
++		buffers[i].address = NULL;
++		buffers[i].dma_address = 0;
++	}
++
++	buffers = ipts->feedback;
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		if (!buffers[i].address)
++			continue;
++
++		dma_free_coherent(ipts->dev, feedback_buffer_size,
++				  buffers[i].address, buffers[i].dma_address);
++
++		buffers[i].address = NULL;
++		buffers[i].dma_address = 0;
++	}
++
++	if (ipts->doorbell.address) {
++		dma_free_coherent(ipts->dev, sizeof(u32),
++				  ipts->doorbell.address,
++				  ipts->doorbell.dma_address);
++
++		ipts->doorbell.address = NULL;
++		ipts->doorbell.dma_address = 0;
++	}
++
++	if (ipts->workqueue.address) {
++		dma_free_coherent(ipts->dev, sizeof(u32),
++				  ipts->workqueue.address,
++				  ipts->workqueue.dma_address);
++
++		ipts->workqueue.address = NULL;
++		ipts->workqueue.dma_address = 0;
++	}
++
++	if (ipts->host2me.address) {
++		dma_free_coherent(ipts->dev, feedback_buffer_size,
++				  ipts->host2me.address,
++				  ipts->host2me.dma_address);
++
++		ipts->host2me.address = NULL;
++		ipts->host2me.dma_address = 0;
++	}
++}
++
++int ipts_resources_alloc(struct ipts_context *ipts)
++{
++	int i;
++	struct ipts_buffer_info *buffers;
++
++	u32 data_buffer_size = ipts->device_info.data_size;
++	u32 feedback_buffer_size = ipts->device_info.feedback_size;
++
++	buffers = ipts->data;
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		buffers[i].address =
++			dma_alloc_coherent(ipts->dev, data_buffer_size,
++					   &buffers[i].dma_address, GFP_KERNEL);
++
++		if (!buffers[i].address)
++			goto release_resources;
++	}
++
++	buffers = ipts->feedback;
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		buffers[i].address =
++			dma_alloc_coherent(ipts->dev, feedback_buffer_size,
++					   &buffers[i].dma_address, GFP_KERNEL);
++
++		if (!buffers[i].address)
++			goto release_resources;
++	}
++
++	ipts->doorbell.address =
++		dma_alloc_coherent(ipts->dev, sizeof(u32),
++				   &ipts->doorbell.dma_address, GFP_KERNEL);
++
++	if (!ipts->doorbell.address)
++		goto release_resources;
++
++	ipts->workqueue.address =
++		dma_alloc_coherent(ipts->dev, sizeof(u32),
++				   &ipts->workqueue.dma_address, GFP_KERNEL);
++
++	if (!ipts->workqueue.address)
++		goto release_resources;
++
++	ipts->host2me.address =
++		dma_alloc_coherent(ipts->dev, feedback_buffer_size,
++				   &ipts->host2me.dma_address, GFP_KERNEL);
++
++	if (!ipts->workqueue.address)
++		goto release_resources;
++
++	return 0;
++
++release_resources:
++
++	ipts_resources_free(ipts);
++	return -ENOMEM;
++}
+diff --git a/drivers/misc/ipts/resources.h b/drivers/misc/ipts/resources.h
+new file mode 100644
+index 000000000000..fdac0eee9156
+--- /dev/null
++++ b/drivers/misc/ipts/resources.h
+@@ -0,0 +1,17 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_RESOURCES_H_
++#define _IPTS_RESOURCES_H_
++
++#include "context.h"
++
++int ipts_resources_alloc(struct ipts_context *ipts);
++void ipts_resources_free(struct ipts_context *ipts);
++
++#endif /* _IPTS_RESOURCES_H_ */
+diff --git a/drivers/misc/ipts/uapi.c b/drivers/misc/ipts/uapi.c
+new file mode 100644
+index 000000000000..598f0710ad64
+--- /dev/null
++++ b/drivers/misc/ipts/uapi.c
+@@ -0,0 +1,208 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#include <linux/cdev.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/fs.h>
++#include <linux/types.h>
++#include <linux/uaccess.h>
++
++#include "context.h"
++#include "control.h"
++#include "protocol.h"
++#include "uapi.h"
++
++struct ipts_uapi uapi;
++
++static ssize_t ipts_uapi_read(struct file *file, char __user *buf, size_t count,
++			      loff_t *offset)
++{
++	int buffer;
++	int maxbytes;
++	struct ipts_context *ipts = uapi.ipts;
++
++	buffer = MINOR(file->f_path.dentry->d_inode->i_rdev);
++
++	if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
++		return -ENODEV;
++
++	maxbytes = ipts->device_info.data_size - *offset;
++	if (maxbytes <= 0 || count > maxbytes)
++		return -EINVAL;
++
++	if (copy_to_user(buf, ipts->data[buffer].address + *offset, count))
++		return -EFAULT;
++
++	return count;
++}
++
++static long ipts_uapi_ioctl_get_device_ready(struct ipts_context *ipts,
++					     unsigned long arg)
++{
++	void __user *buffer = (void __user *)arg;
++	u8 ready = 0;
++
++	if (ipts)
++		ready = ipts->status == IPTS_HOST_STATUS_STARTED;
++
++	if (copy_to_user(buffer, &ready, sizeof(u8)))
++		return -EFAULT;
++
++	return 0;
++}
++
++static long ipts_uapi_ioctl_get_device_info(struct ipts_context *ipts,
++					    unsigned long arg)
++{
++	struct ipts_device_info info;
++	void __user *buffer = (void __user *)arg;
++
++	if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
++		return -ENODEV;
++
++	info.vendor = ipts->device_info.vendor_id;
++	info.product = ipts->device_info.device_id;
++	info.version = ipts->device_info.fw_rev;
++	info.buffer_size = ipts->device_info.data_size;
++	info.max_contacts = ipts->device_info.max_contacts;
++
++	if (copy_to_user(buffer, &info, sizeof(struct ipts_device_info)))
++		return -EFAULT;
++
++	return 0;
++}
++
++static long ipts_uapi_ioctl_get_doorbell(struct ipts_context *ipts,
++					 unsigned long arg)
++{
++	void __user *buffer = (void __user *)arg;
++
++	if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
++		return -ENODEV;
++
++	if (copy_to_user(buffer, ipts->doorbell.address, sizeof(u32)))
++		return -EFAULT;
++
++	return 0;
++}
++
++static long ipts_uapi_ioctl_send_feedback(struct ipts_context *ipts,
++					  struct file *file)
++{
++	int ret;
++	u32 buffer;
++
++	if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
++		return -ENODEV;
++
++	buffer = MINOR(file->f_path.dentry->d_inode->i_rdev);
++
++	ret = ipts_control_send_feedback(ipts, buffer);
++	if (ret)
++		return -EFAULT;
++
++	return 0;
++}
++
++static long ipts_uapi_ioctl_send_reset(struct ipts_context *ipts)
++{
++	int ret;
++	struct ipts_reset_sensor_cmd cmd;
++
++	if (!ipts || ipts->status != IPTS_HOST_STATUS_STARTED)
++		return -ENODEV;
++
++	memset(&cmd, 0, sizeof(struct ipts_reset_sensor_cmd));
++	cmd.type = IPTS_RESET_TYPE_SOFT;
++
++	ret = ipts_control_send(ipts, IPTS_CMD_RESET_SENSOR, &cmd,
++				sizeof(struct ipts_reset_sensor_cmd));
++
++	if (ret)
++		return -EFAULT;
++
++	return 0;
++}
++
++static long ipts_uapi_ioctl(struct file *file, unsigned int cmd,
++			    unsigned long arg)
++{
++	struct ipts_context *ipts = uapi.ipts;
++
++	switch (cmd) {
++	case IPTS_IOCTL_GET_DEVICE_READY:
++		return ipts_uapi_ioctl_get_device_ready(ipts, arg);
++	case IPTS_IOCTL_GET_DEVICE_INFO:
++		return ipts_uapi_ioctl_get_device_info(ipts, arg);
++	case IPTS_IOCTL_GET_DOORBELL:
++		return ipts_uapi_ioctl_get_doorbell(ipts, arg);
++	case IPTS_IOCTL_SEND_FEEDBACK:
++		return ipts_uapi_ioctl_send_feedback(ipts, file);
++	case IPTS_IOCTL_SEND_RESET:
++		return ipts_uapi_ioctl_send_reset(ipts);
++	default:
++		return -ENOTTY;
++	}
++}
++
++static const struct file_operations ipts_uapi_fops = {
++	.owner = THIS_MODULE,
++	.read = ipts_uapi_read,
++	.unlocked_ioctl = ipts_uapi_ioctl,
++#ifdef CONFIG_COMPAT
++	.compat_ioctl = ipts_uapi_ioctl,
++#endif
++};
++
++void ipts_uapi_link(struct ipts_context *ipts)
++{
++	uapi.ipts = ipts;
++}
++
++void ipts_uapi_unlink(void)
++{
++	uapi.ipts = NULL;
++}
++
++int ipts_uapi_init(void)
++{
++	int i, major;
++
++	alloc_chrdev_region(&uapi.dev, 0, IPTS_BUFFERS, "ipts");
++	uapi.class = class_create(THIS_MODULE, "ipts");
++
++	major = MAJOR(uapi.dev);
++
++	cdev_init(&uapi.cdev, &ipts_uapi_fops);
++	uapi.cdev.owner = THIS_MODULE;
++	cdev_add(&uapi.cdev, MKDEV(major, 0), IPTS_BUFFERS);
++
++	for (i = 0; i < IPTS_BUFFERS; i++) {
++		device_create(uapi.class, NULL, MKDEV(major, i), NULL,
++			      "ipts/%d", i);
++	}
++
++	return 0;
++}
++
++void ipts_uapi_free(void)
++{
++	int i;
++	int major;
++
++	major = MAJOR(uapi.dev);
++
++	for (i = 0; i < IPTS_BUFFERS; i++)
++		device_destroy(uapi.class, MKDEV(major, i));
++
++	cdev_del(&uapi.cdev);
++
++	unregister_chrdev_region(MKDEV(major, 0), MINORMASK);
++	class_destroy(uapi.class);
++}
+diff --git a/drivers/misc/ipts/uapi.h b/drivers/misc/ipts/uapi.h
+new file mode 100644
+index 000000000000..53fb86a88f97
+--- /dev/null
++++ b/drivers/misc/ipts/uapi.h
+@@ -0,0 +1,47 @@
++/* SPDX-License-Identifier: GPL-2.0-or-later */
++/*
++ * Copyright (c) 2016 Intel Corporation
++ * Copyright (c) 2020 Dorian Stoll
++ *
++ * Linux driver for Intel Precise Touch & Stylus
++ */
++
++#ifndef _IPTS_UAPI_H_
++#define _IPTS_UAPI_H_
++
++#include <linux/types.h>
++
++#include "context.h"
++
++struct ipts_uapi {
++	dev_t dev;
++	struct class *class;
++	struct cdev cdev;
++
++	struct ipts_context *ipts;
++};
++
++struct ipts_device_info {
++	__u16 vendor;
++	__u16 product;
++	__u32 version;
++	__u32 buffer_size;
++	__u8 max_contacts;
++
++	/* For future expansion */
++	__u8 reserved[19];
++};
++
++#define IPTS_IOCTL_GET_DEVICE_READY _IOR(0x86, 0x01, __u8)
++#define IPTS_IOCTL_GET_DEVICE_INFO  _IOR(0x86, 0x02, struct ipts_device_info)
++#define IPTS_IOCTL_GET_DOORBELL	    _IOR(0x86, 0x03, __u32)
++#define IPTS_IOCTL_SEND_FEEDBACK    _IO(0x86, 0x04)
++#define IPTS_IOCTL_SEND_RESET	    _IO(0x86, 0x05)
++
++void ipts_uapi_link(struct ipts_context *ipts);
++void ipts_uapi_unlink(void);
++
++int ipts_uapi_init(void);
++void ipts_uapi_free(void);
++
++#endif /* _IPTS_UAPI_H_ */
+-- 
+2.31.1
+

+ 335 - 0
patches/5.12/0005-surface-sam-over-hid.patch

@@ -0,0 +1,335 @@
+From e72c7d0b6c7b5ee1f99a749ff56379bb67033ec3 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Sat, 25 Jul 2020 17:19:53 +0200
+Subject: [PATCH] i2c: acpi: Implement RawBytes read access
+
+Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C
+device via a generic serial bus operation region and RawBytes read
+access. On the Surface Book 1, this access is required to turn on (and
+off) the discrete GPU.
+
+Multiple things are to note here:
+
+a) The RawBytes access is device/driver dependent. The ACPI
+   specification states:
+
+   > Raw accesses assume that the writer has knowledge of the bus that
+   > the access is made over and the device that is being accessed. The
+   > protocol may only ensure that the buffer is transmitted to the
+   > appropriate driver, but the driver must be able to interpret the
+   > buffer to communicate to a register.
+
+   Thus this implementation may likely not work on other devices
+   accessing I2C via the RawBytes accessor type.
+
+b) The MSHW0030 I2C device is an HID-over-I2C device which seems to
+   serve multiple functions:
+
+   1. It is the main access point for the legacy-type Surface Aggregator
+      Module (also referred to as SAM-over-HID, as opposed to the newer
+      SAM-over-SSH/UART). It has currently not been determined on how
+      support for the legacy SAM should be implemented. Likely via a
+      custom HID driver.
+
+   2. It seems to serve as the HID device for the Integrated Sensor Hub.
+      This might complicate matters with regards to implementing a
+      SAM-over-HID driver required by legacy SAM.
+
+In light of this, the simplest approach has been chosen for now.
+However, it may make more sense regarding breakage and compatibility to
+either provide functionality for replacing or enhancing the default
+operation region handler via some additional API functions, or even to
+completely blacklist MSHW0030 from the I2C core and provide a custom
+driver for it.
+
+Replacing/enhancing the default operation region handler would, however,
+either require some sort of secondary driver and access point for it,
+from which the new API functions would be called and the new handler
+(part) would be installed, or hard-coding them via some sort of
+quirk-like interface into the I2C core.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Patchset: surface-sam-over-hid
+---
+ drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++
+ 1 file changed, 35 insertions(+)
+
+diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
+index 8ceaa88dd78f..deceed0d76c6 100644
+--- a/drivers/i2c/i2c-core-acpi.c
++++ b/drivers/i2c/i2c-core-acpi.c
+@@ -570,6 +570,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client,
+ 	return (ret == 1) ? 0 : -EIO;
+ }
+ 
++static int acpi_gsb_i2c_write_raw_bytes(struct i2c_client *client,
++		u8 *data, u8 data_len)
++{
++	struct i2c_msg msgs[1];
++	int ret = AE_OK;
++
++	msgs[0].addr = client->addr;
++	msgs[0].flags = client->flags;
++	msgs[0].len = data_len + 1;
++	msgs[0].buf = data;
++
++	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
++
++	if (ret < 0) {
++		dev_err(&client->adapter->dev, "i2c write failed: %d\n", ret);
++		return ret;
++	}
++
++	/* 1 transfer must have completed successfully */
++	return (ret == 1) ? 0 : -EIO;
++}
++
+ static acpi_status
+ i2c_acpi_space_handler(u32 function, acpi_physical_address command,
+ 			u32 bits, u64 *value64,
+@@ -671,6 +693,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command,
+ 		}
+ 		break;
+ 
++	case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES:
++		if (action == ACPI_READ) {
++			dev_warn(&adapter->dev,
++				 "protocol 0x%02x not supported for client 0x%02x\n",
++				 accessor_type, client->addr);
++			ret = AE_BAD_PARAMETER;
++			goto err;
++		} else {
++			status = acpi_gsb_i2c_write_raw_bytes(client,
++					gsb->data, info->access_length);
++		}
++		break;
++
+ 	default:
+ 		dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n",
+ 			 accessor_type, client->addr);
+-- 
+2.31.1
+
+From 597647585a2c0f369f47a3f5c1e58a1251b8fa97 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Sat, 13 Feb 2021 16:41:18 +0100
+Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch
+
+Add driver exposing the discrete GPU power-switch of the  Microsoft
+Surface Book 1 to user-space.
+
+On the Surface Book 1, the dGPU power is controlled via the Surface
+System Aggregator Module (SAM). The specific SAM-over-HID command for
+this is exposed via ACPI. This module provides a simple driver exposing
+the ACPI call via a sysfs parameter to user-space, so that users can
+easily power-on/-off the dGPU.
+
+Patchset: surface-sam-over-hid
+---
+ drivers/platform/surface/Kconfig              |   7 +
+ drivers/platform/surface/Makefile             |   1 +
+ .../surface/surfacebook1_dgpu_switch.c        | 162 ++++++++++++++++++
+ 3 files changed, 170 insertions(+)
+ create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c
+
+diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
+index 0847b2dc97bf..fd45940ab6ce 100644
+--- a/drivers/platform/surface/Kconfig
++++ b/drivers/platform/surface/Kconfig
+@@ -77,6 +77,13 @@ config SURFACE_AGGREGATOR_CDEV
+ 	  The provided interface is intended for debugging and development only,
+ 	  and should not be used otherwise.
+ 
++config SURFACE_BOOK1_DGPU_SWITCH
++	tristate "Surface Book 1 dGPU Switch Driver"
++	depends on SYSFS
++	help
++	  This driver provides a sysfs switch to set the power-state of the
++	  discrete GPU found on the Microsoft Surface Book 1.
++
+ config SURFACE_GPE
+ 	tristate "Surface GPE/Lid Support Driver"
+ 	depends on DMI
+diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
+index 990424c5f0c9..6b69175598ab 100644
+--- a/drivers/platform/surface/Makefile
++++ b/drivers/platform/surface/Makefile
+@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION)	+= surface3_power.o
+ obj-$(CONFIG_SURFACE_ACPI_NOTIFY)	+= surface_acpi_notify.o
+ obj-$(CONFIG_SURFACE_AGGREGATOR)	+= aggregator/
+ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV)	+= surface_aggregator_cdev.o
++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
+ obj-$(CONFIG_SURFACE_GPE)		+= surface_gpe.o
+ obj-$(CONFIG_SURFACE_HOTPLUG)		+= surface_hotplug.o
+ obj-$(CONFIG_SURFACE_PRO3_BUTTON)	+= surfacepro3_button.o
+diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c
+new file mode 100644
+index 000000000000..8b816ed8f35c
+--- /dev/null
++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c
+@@ -0,0 +1,162 @@
++// SPDX-License-Identifier: GPL-2.0-or-later
++
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/acpi.h>
++#include <linux/platform_device.h>
++
++
++#ifdef pr_fmt
++#undef pr_fmt
++#endif
++#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__
++
++
++static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4,
++	0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35);
++
++#define DGPUSW_ACPI_PATH_DSM	"\\_SB_.PCI0.LPCB.EC0_.VGBI"
++#define DGPUSW_ACPI_PATH_HGON	"\\_SB_.PCI0.RP05.HGON"
++#define DGPUSW_ACPI_PATH_HGOF	"\\_SB_.PCI0.RP05.HGOF"
++
++
++static int sb1_dgpu_sw_dsmcall(void)
++{
++	union acpi_object *ret;
++	acpi_handle handle;
++	acpi_status status;
++
++	status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle);
++	if (status)
++		return -EINVAL;
++
++	ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER);
++	if (!ret)
++		return -EINVAL;
++
++	ACPI_FREE(ret);
++	return 0;
++}
++
++static int sb1_dgpu_sw_hgon(void)
++{
++	struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
++	acpi_status status;
++
++	status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf);
++	if (status) {
++		pr_err("failed to run HGON: %d\n", status);
++		return -EINVAL;
++	}
++
++	if (buf.pointer)
++		ACPI_FREE(buf.pointer);
++
++	pr_info("turned-on dGPU via HGON\n");
++	return 0;
++}
++
++static int sb1_dgpu_sw_hgof(void)
++{
++	struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL};
++	acpi_status status;
++
++	status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf);
++	if (status) {
++		pr_err("failed to run HGOF: %d\n", status);
++		return -EINVAL;
++	}
++
++	if (buf.pointer)
++		ACPI_FREE(buf.pointer);
++
++	pr_info("turned-off dGPU via HGOF\n");
++	return 0;
++}
++
++
++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr,
++				  const char *buf, size_t len)
++{
++	int status, value;
++
++	status = kstrtoint(buf, 0, &value);
++	if (status < 0)
++		return status;
++
++	if (value != 1)
++		return -EINVAL;
++
++	status = sb1_dgpu_sw_dsmcall();
++
++	return status < 0 ? status : len;
++}
++
++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr,
++				const char *buf, size_t len)
++{
++	bool power;
++	int status;
++
++	status = kstrtobool(buf, &power);
++	if (status < 0)
++		return status;
++
++	if (power)
++		status = sb1_dgpu_sw_hgon();
++	else
++		status = sb1_dgpu_sw_hgof();
++
++	return status < 0 ? status : len;
++}
++
++static DEVICE_ATTR_WO(dgpu_dsmcall);
++static DEVICE_ATTR_WO(dgpu_power);
++
++static struct attribute *sb1_dgpu_sw_attrs[] = {
++	&dev_attr_dgpu_dsmcall.attr,
++	&dev_attr_dgpu_power.attr,
++	NULL,
++};
++
++static const struct attribute_group sb1_dgpu_sw_attr_group = {
++	.attrs = sb1_dgpu_sw_attrs,
++};
++
++
++static int sb1_dgpu_sw_probe(struct platform_device *pdev)
++{
++	return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group);
++}
++
++static int sb1_dgpu_sw_remove(struct platform_device *pdev)
++{
++	sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group);
++	return 0;
++}
++
++/*
++ * The dGPU power seems to be actually handled by MSHW0040. However, that is
++ * also the power-/volume-button device with a mainline driver. So let's use
++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device.
++ */
++static const struct acpi_device_id sb1_dgpu_sw_match[] = {
++	{ "MSHW0041", },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match);
++
++static struct platform_driver sb1_dgpu_sw = {
++	.probe = sb1_dgpu_sw_probe,
++	.remove = sb1_dgpu_sw_remove,
++	.driver = {
++		.name = "surfacebook1_dgpu_switch",
++		.acpi_match_table = sb1_dgpu_sw_match,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_platform_driver(sb1_dgpu_sw);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+

+ 7402 - 0
patches/5.12/0006-surface-sam.patch

@@ -0,0 +1,7402 @@
+From d85a26f2eb7a2ed68e4c56030c030bd151f90eeb Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:34 +0100
+Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry
+
+The Surface System Aggregator Module (SSAM) subsystem provides various
+functionalities, which are separated by spreading them across multiple
+devices and corresponding drivers. Parts of that functionality / some of
+those devices, however, can (as far as we currently know) not be
+auto-detected by conventional means. While older (specifically 5th- and
+6th-)generation models do advertise most of their functionality via
+standard platform devices in ACPI, newer generations do not.
+
+As we are currently also not aware of any feasible way to query said
+functionalities dynamically, this poses a problem. There is, however, a
+device in ACPI that seems to be used by Windows for identifying
+different Surface models: The Windows Surface Integration Device (WSID).
+This device seems to have a HID corresponding to the overall set of
+functionalities SSAM provides for the associated model.
+
+This commit introduces a registry providing non-detectable device
+information via software nodes. In addition, a SSAM platform hub driver
+is introduced, which takes care of creating and managing the SSAM
+devices specified in this registry. This approach allows for a
+hierarchical setup akin to ACPI and is easily extendable, e.g. via
+firmware node properties.
+
+Note that this commit only provides the basis for the platform hub and
+registry, and does not add any content to it. The registry will be
+expanded in subsequent commits.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ MAINTAINERS                                   |   1 +
+ drivers/platform/surface/Kconfig              |  27 ++
+ drivers/platform/surface/Makefile             |   1 +
+ .../surface/surface_aggregator_registry.c     | 284 ++++++++++++++++++
+ 4 files changed, 313 insertions(+)
+ create mode 100644 drivers/platform/surface/surface_aggregator_registry.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 9450e052f1b1..f6c524630575 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11904,6 +11904,7 @@ F:	Documentation/driver-api/surface_aggregator/
+ F:	drivers/platform/surface/aggregator/
+ F:	drivers/platform/surface/surface_acpi_notify.c
+ F:	drivers/platform/surface/surface_aggregator_cdev.c
++F:	drivers/platform/surface/surface_aggregator_registry.c
+ F:	include/linux/surface_acpi_notify.h
+ F:	include/linux/surface_aggregator/
+ F:	include/uapi/linux/surface_aggregator/
+diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
+index fd45940ab6ce..c51c55204b5f 100644
+--- a/drivers/platform/surface/Kconfig
++++ b/drivers/platform/surface/Kconfig
+@@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV
+ 	  The provided interface is intended for debugging and development only,
+ 	  and should not be used otherwise.
+ 
++config SURFACE_AGGREGATOR_REGISTRY
++	tristate "Surface System Aggregator Module Device Registry"
++	depends on SURFACE_AGGREGATOR
++	depends on SURFACE_AGGREGATOR_BUS
++	help
++	  Device-registry and device-hubs for Surface System Aggregator Module
++	  (SSAM) devices.
++
++	  Provides a module and driver which act as a device-registry for SSAM
++	  client devices that cannot be detected automatically, e.g. via ACPI.
++	  Such devices are instead provided via this registry and attached via
++	  device hubs, also provided in this module.
++
++	  Devices provided via this registry are:
++	  - Platform profile (performance-/cooling-mode) device (5th- and later
++	    generations).
++	  - Battery/AC devices (7th-generation).
++	  - HID input devices (7th-generation).
++
++	  Select M (recommended) or Y here if you want support for the above
++	  mentioned devices on the corresponding Surface models. Without this
++	  module, the respective devices will not be instantiated and thus any
++	  functionality provided by them will be missing, even when drivers for
++	  these devices are present. In other words, this module only provides
++	  the respective client devices. Drivers for these devices still need to
++	  be selected via the other options.
++
+ config SURFACE_BOOK1_DGPU_SWITCH
+ 	tristate "Surface Book 1 dGPU Switch Driver"
+ 	depends on SYSFS
+diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
+index 6b69175598ab..ed12676f06e6 100644
+--- a/drivers/platform/surface/Makefile
++++ b/drivers/platform/surface/Makefile
+@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION)	+= surface3_power.o
+ obj-$(CONFIG_SURFACE_ACPI_NOTIFY)	+= surface_acpi_notify.o
+ obj-$(CONFIG_SURFACE_AGGREGATOR)	+= aggregator/
+ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV)	+= surface_aggregator_cdev.o
++obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
+ obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
+ obj-$(CONFIG_SURFACE_GPE)		+= surface_gpe.o
+ obj-$(CONFIG_SURFACE_HOTPLUG)		+= surface_hotplug.o
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+new file mode 100644
+index 000000000000..a051d941ad96
+--- /dev/null
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -0,0 +1,284 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Surface System Aggregator Module (SSAM) client device registry.
++ *
++ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
++ * cannot be auto-detected. Provides device-hubs and performs instantiation
++ * for these devices.
++ *
++ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <linux/acpi.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/property.h>
++
++#include <linux/surface_aggregator/controller.h>
++#include <linux/surface_aggregator/device.h>
++
++
++/* -- Device registry. ------------------------------------------------------ */
++
++/*
++ * SSAM device names follow the SSAM module alias, meaning they are prefixed
++ * with 'ssam:', followed by domain, category, target ID, instance ID, and
++ * function, each encoded as two-digit hexadecimal, separated by ':'. In other
++ * words, it follows the scheme
++ *
++ *      ssam:dd:cc:tt:ii:ff
++ *
++ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
++ * values mentioned above, respectively.
++ */
++
++/* Root node. */
++static const struct software_node ssam_node_root = {
++	.name = "ssam_platform_hub",
++};
++
++/* Devices for Surface Book 2. */
++static const struct software_node *ssam_node_group_sb2[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Book 3. */
++static const struct software_node *ssam_node_group_sb3[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Laptop 1. */
++static const struct software_node *ssam_node_group_sl1[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Laptop 2. */
++static const struct software_node *ssam_node_group_sl2[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Laptop 3. */
++static const struct software_node *ssam_node_group_sl3[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Laptop Go. */
++static const struct software_node *ssam_node_group_slg1[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Pro 5. */
++static const struct software_node *ssam_node_group_sp5[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Pro 6. */
++static const struct software_node *ssam_node_group_sp6[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++/* Devices for Surface Pro 7. */
++static const struct software_node *ssam_node_group_sp7[] = {
++	&ssam_node_root,
++	NULL,
++};
++
++
++/* -- Device registry helper functions. ------------------------------------- */
++
++static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
++{
++	u8 d, tc, tid, iid, fn;
++	int n;
++
++	n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
++	if (n != 5)
++		return -EINVAL;
++
++	uid->domain = d;
++	uid->category = tc;
++	uid->target = tid;
++	uid->instance = iid;
++	uid->function = fn;
++
++	return 0;
++}
++
++static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
++{
++	if (!is_ssam_device(dev))
++		return 0;
++
++	ssam_device_remove(to_ssam_device(dev));
++	return 0;
++}
++
++static void ssam_hub_remove_devices(struct device *parent)
++{
++	device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
++}
++
++static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
++			       struct fwnode_handle *node)
++{
++	struct ssam_device_uid uid;
++	struct ssam_device *sdev;
++	int status;
++
++	status = ssam_uid_from_string(fwnode_get_name(node), &uid);
++	if (status)
++		return status;
++
++	sdev = ssam_device_alloc(ctrl, uid);
++	if (!sdev)
++		return -ENOMEM;
++
++	sdev->dev.parent = parent;
++	sdev->dev.fwnode = node;
++
++	status = ssam_device_add(sdev);
++	if (status)
++		ssam_device_put(sdev);
++
++	return status;
++}
++
++static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
++				struct fwnode_handle *node)
++{
++	struct fwnode_handle *child;
++	int status;
++
++	fwnode_for_each_child_node(node, child) {
++		/*
++		 * Try to add the device specified in the firmware node. If
++		 * this fails with -EINVAL, the node does not specify any SSAM
++		 * device, so ignore it and continue with the next one.
++		 */
++
++		status = ssam_hub_add_device(parent, ctrl, child);
++		if (status && status != -EINVAL)
++			goto err;
++	}
++
++	return 0;
++err:
++	ssam_hub_remove_devices(parent);
++	return status;
++}
++
++
++/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
++
++static const struct acpi_device_id ssam_platform_hub_match[] = {
++	/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
++	{ "MSHW0081", (unsigned long)ssam_node_group_sp5 },
++
++	/* Surface Pro 6 (OMBR >= 0x10) */
++	{ "MSHW0111", (unsigned long)ssam_node_group_sp6 },
++
++	/* Surface Pro 7 */
++	{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
++
++	/* Surface Book 2 */
++	{ "MSHW0107", (unsigned long)ssam_node_group_sb2 },
++
++	/* Surface Book 3 */
++	{ "MSHW0117", (unsigned long)ssam_node_group_sb3 },
++
++	/* Surface Laptop 1 */
++	{ "MSHW0086", (unsigned long)ssam_node_group_sl1 },
++
++	/* Surface Laptop 2 */
++	{ "MSHW0112", (unsigned long)ssam_node_group_sl2 },
++
++	/* Surface Laptop 3 (13", Intel) */
++	{ "MSHW0114", (unsigned long)ssam_node_group_sl3 },
++
++	/* Surface Laptop 3 (15", AMD) */
++	{ "MSHW0110", (unsigned long)ssam_node_group_sl3 },
++
++	/* Surface Laptop Go 1 */
++	{ "MSHW0118", (unsigned long)ssam_node_group_slg1 },
++
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
++
++static int ssam_platform_hub_probe(struct platform_device *pdev)
++{
++	const struct software_node **nodes;
++	struct ssam_controller *ctrl;
++	struct fwnode_handle *root;
++	int status;
++
++	nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
++	if (!nodes)
++		return -ENODEV;
++
++	/*
++	 * As we're adding the SSAM client devices as children under this device
++	 * and not the SSAM controller, we need to add a device link to the
++	 * controller to ensure that we remove all of our devices before the
++	 * controller is removed. This also guarantees proper ordering for
++	 * suspend/resume of the devices on this hub.
++	 */
++	ctrl = ssam_client_bind(&pdev->dev);
++	if (IS_ERR(ctrl))
++		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
++
++	status = software_node_register_node_group(nodes);
++	if (status)
++		return status;
++
++	root = software_node_fwnode(&ssam_node_root);
++	if (!root) {
++		software_node_unregister_node_group(nodes);
++		return -ENOENT;
++	}
++
++	set_secondary_fwnode(&pdev->dev, root);
++
++	status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
++	if (status) {
++		set_secondary_fwnode(&pdev->dev, NULL);
++		software_node_unregister_node_group(nodes);
++	}
++
++	platform_set_drvdata(pdev, nodes);
++	return status;
++}
++
++static int ssam_platform_hub_remove(struct platform_device *pdev)
++{
++	const struct software_node **nodes = platform_get_drvdata(pdev);
++
++	ssam_hub_remove_devices(&pdev->dev);
++	set_secondary_fwnode(&pdev->dev, NULL);
++	software_node_unregister_node_group(nodes);
++	return 0;
++}
++
++static struct platform_driver ssam_platform_hub_driver = {
++	.probe = ssam_platform_hub_probe,
++	.remove = ssam_platform_hub_remove,
++	.driver = {
++		.name = "surface_aggregator_platform_hub",
++		.acpi_match_table = ssam_platform_hub_match,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_platform_driver(ssam_platform_hub_driver);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From fc0cef48bcf0326c3cecd52fc22bdec318b7d95c Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:35 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub
+
+The Surface Book 3 has a detachable base part. While the top part
+(so-called clipboard) contains the CPU, touchscreen, and primary
+battery, the base contains, among other things, a keyboard, touchpad,
+and secondary battery.
+
+Those devices do not react well to being accessed when the base part is
+detached and should thus be removed and added in sync with the base. To
+facilitate this, we introduce a virtual base device hub, which
+automatically removes or adds the devices registered under it.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface/surface_aggregator_registry.c     | 261 +++++++++++++++++-
+ 1 file changed, 260 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index a051d941ad96..6c23d75a044c 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -11,9 +11,12 @@
+ 
+ #include <linux/acpi.h>
+ #include <linux/kernel.h>
++#include <linux/limits.h>
+ #include <linux/module.h>
++#include <linux/mutex.h>
+ #include <linux/platform_device.h>
+ #include <linux/property.h>
++#include <linux/types.h>
+ 
+ #include <linux/surface_aggregator/controller.h>
+ #include <linux/surface_aggregator/device.h>
+@@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = {
+ 	.name = "ssam_platform_hub",
+ };
+ 
++/* Base device hub (devices attached to Surface Book 3 base). */
++static const struct software_node ssam_node_hub_base = {
++	.name = "ssam:00:00:02:00:00",
++	.parent = &ssam_node_root,
++};
++
+ /* Devices for Surface Book 2. */
+ static const struct software_node *ssam_node_group_sb2[] = {
+ 	&ssam_node_root,
+@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = {
+ /* Devices for Surface Book 3. */
+ static const struct software_node *ssam_node_group_sb3[] = {
+ 	&ssam_node_root,
++	&ssam_node_hub_base,
+ 	NULL,
+ };
+ 
+@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
+ }
+ 
+ 
++/* -- SSAM base-hub driver. ------------------------------------------------- */
++
++enum ssam_base_hub_state {
++	SSAM_BASE_HUB_UNINITIALIZED,
++	SSAM_BASE_HUB_CONNECTED,
++	SSAM_BASE_HUB_DISCONNECTED,
++};
++
++struct ssam_base_hub {
++	struct ssam_device *sdev;
++
++	struct mutex lock;  /* Guards state update checks and transitions. */
++	enum ssam_base_hub_state state;
++
++	struct ssam_event_notifier notif;
++};
++
++static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x0d,
++	.instance_id     = 0x00,
++});
++
++#define SSAM_BAS_OPMODE_TABLET		0x00
++#define SSAM_EVENT_BAS_CID_CONNECTION	0x0c
++
++static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
++{
++	u8 opmode;
++	int status;
++
++	status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
++	if (status < 0) {
++		dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
++		return status;
++	}
++
++	if (opmode != SSAM_BAS_OPMODE_TABLET)
++		*state = SSAM_BASE_HUB_CONNECTED;
++	else
++		*state = SSAM_BASE_HUB_DISCONNECTED;
++
++	return 0;
++}
++
++static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
++					char *buf)
++{
++	struct ssam_base_hub *hub = dev_get_drvdata(dev);
++	bool connected;
++
++	mutex_lock(&hub->lock);
++	connected = hub->state == SSAM_BASE_HUB_CONNECTED;
++	mutex_unlock(&hub->lock);
++
++	return sysfs_emit(buf, "%d\n", connected);
++}
++
++static struct device_attribute ssam_base_hub_attr_state =
++	__ATTR(state, 0444, ssam_base_hub_state_show, NULL);
++
++static struct attribute *ssam_base_hub_attrs[] = {
++	&ssam_base_hub_attr_state.attr,
++	NULL,
++};
++
++const struct attribute_group ssam_base_hub_group = {
++	.attrs = ssam_base_hub_attrs,
++};
++
++static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
++{
++	struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
++	int status = 0;
++
++	lockdep_assert_held(&hub->lock);
++
++	if (hub->state == new)
++		return 0;
++	hub->state = new;
++
++	if (hub->state == SSAM_BASE_HUB_CONNECTED)
++		status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
++	else
++		ssam_hub_remove_devices(&hub->sdev->dev);
++
++	if (status)
++		dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
++
++	return status;
++}
++
++static int ssam_base_hub_update(struct ssam_base_hub *hub)
++{
++	enum ssam_base_hub_state state;
++	int status;
++
++	mutex_lock(&hub->lock);
++
++	status = ssam_base_hub_query_state(hub, &state);
++	if (!status)
++		status = __ssam_base_hub_update(hub, state);
++
++	mutex_unlock(&hub->lock);
++	return status;
++}
++
++static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
++{
++	struct ssam_base_hub *hub;
++	struct ssam_device *sdev;
++	enum ssam_base_hub_state new;
++
++	hub = container_of(nf, struct ssam_base_hub, notif);
++	sdev = hub->sdev;
++
++	if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
++		return 0;
++
++	if (event->length < 1) {
++		dev_err(&sdev->dev, "unexpected payload size: %u\n",
++			event->length);
++		return 0;
++	}
++
++	if (event->data[0])
++		new = SSAM_BASE_HUB_CONNECTED;
++	else
++		new = SSAM_BASE_HUB_DISCONNECTED;
++
++	mutex_lock(&hub->lock);
++	__ssam_base_hub_update(hub, new);
++	mutex_unlock(&hub->lock);
++
++	/*
++	 * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
++	 * consumed by the detachment system driver. We're just a (more or less)
++	 * silent observer.
++	 */
++	return 0;
++}
++
++static int __maybe_unused ssam_base_hub_resume(struct device *dev)
++{
++	return ssam_base_hub_update(dev_get_drvdata(dev));
++}
++static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
++
++static int ssam_base_hub_probe(struct ssam_device *sdev)
++{
++	struct ssam_base_hub *hub;
++	int status;
++
++	hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
++	if (!hub)
++		return -ENOMEM;
++
++	mutex_init(&hub->lock);
++
++	hub->sdev = sdev;
++	hub->state = SSAM_BASE_HUB_UNINITIALIZED;
++
++	hub->notif.base.priority = INT_MAX;  /* This notifier should run first. */
++	hub->notif.base.fn = ssam_base_hub_notif;
++	hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
++	hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
++	hub->notif.event.id.instance = 0,
++	hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
++	hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
++
++	ssam_device_set_drvdata(sdev, hub);
++
++	status = ssam_notifier_register(sdev->ctrl, &hub->notif);
++	if (status)
++		goto err_register;
++
++	status = ssam_base_hub_update(hub);
++	if (status)
++		goto err_update;
++
++	status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
++	if (status)
++		goto err_update;
++
++	return 0;
++
++err_update:
++	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
++	ssam_hub_remove_devices(&sdev->dev);
++err_register:
++	mutex_destroy(&hub->lock);
++	return status;
++}
++
++static void ssam_base_hub_remove(struct ssam_device *sdev)
++{
++	struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
++
++	sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
++
++	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
++	ssam_hub_remove_devices(&sdev->dev);
++
++	mutex_destroy(&hub->lock);
++}
++
++static const struct ssam_device_id ssam_base_hub_match[] = {
++	{ SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
++	{ },
++};
++
++static struct ssam_device_driver ssam_base_hub_driver = {
++	.probe = ssam_base_hub_probe,
++	.remove = ssam_base_hub_remove,
++	.match_table = ssam_base_hub_match,
++	.driver = {
++		.name = "surface_aggregator_base_hub",
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++		.pm = &ssam_base_hub_pm_ops,
++	},
++};
++
++
+ /* -- SSAM platform/meta-hub driver. ---------------------------------------- */
+ 
+ static const struct acpi_device_id ssam_platform_hub_match[] = {
+@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = {
+ 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ 	},
+ };
+-module_platform_driver(ssam_platform_hub_driver);
++
++
++/* -- Module initialization. ------------------------------------------------ */
++
++static int __init ssam_device_hub_init(void)
++{
++	int status;
++
++	status = platform_driver_register(&ssam_platform_hub_driver);
++	if (status)
++		return status;
++
++	status = ssam_device_driver_register(&ssam_base_hub_driver);
++	if (status)
++		platform_driver_unregister(&ssam_platform_hub_driver);
++
++	return status;
++}
++module_init(ssam_device_hub_init);
++
++static void __exit ssam_device_hub_exit(void)
++{
++	ssam_device_driver_unregister(&ssam_base_hub_driver);
++	platform_driver_unregister(&ssam_platform_hub_driver);
++}
++module_exit(ssam_device_hub_exit);
+ 
+ MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+ MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
+-- 
+2.31.1
+
+From 9d99facf87190ec57305a908829037887d2a324d Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:36 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem
+ devices
+
+Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM
+device registry. These devices need to be registered for 7th-generation
+Surface models. On 5th- and 6th-generation models, these devices are
+handled via the standard ACPI battery/AC interface, which in turn
+accesses the same SSAM interface via the Surface ACPI Notify (SAN)
+driver.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface/surface_aggregator_registry.c     | 27 +++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index 6c23d75a044c..cde279692842 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = {
+ 	.parent = &ssam_node_root,
+ };
+ 
++/* AC adapter. */
++static const struct software_node ssam_node_bat_ac = {
++	.name = "ssam:01:02:01:01:01",
++	.parent = &ssam_node_root,
++};
++
++/* Primary battery. */
++static const struct software_node ssam_node_bat_main = {
++	.name = "ssam:01:02:01:01:00",
++	.parent = &ssam_node_root,
++};
++
++/* Secondary battery (Surface Book 3). */
++static const struct software_node ssam_node_bat_sb3base = {
++	.name = "ssam:01:02:02:01:00",
++	.parent = &ssam_node_hub_base,
++};
++
+ /* Devices for Surface Book 2. */
+ static const struct software_node *ssam_node_group_sb2[] = {
+ 	&ssam_node_root,
+@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = {
+ static const struct software_node *ssam_node_group_sb3[] = {
+ 	&ssam_node_root,
+ 	&ssam_node_hub_base,
++	&ssam_node_bat_ac,
++	&ssam_node_bat_main,
++	&ssam_node_bat_sb3base,
+ 	NULL,
+ };
+ 
+@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = {
+ /* Devices for Surface Laptop 3. */
+ static const struct software_node *ssam_node_group_sl3[] = {
+ 	&ssam_node_root,
++	&ssam_node_bat_ac,
++	&ssam_node_bat_main,
+ 	NULL,
+ };
+ 
+ /* Devices for Surface Laptop Go. */
+ static const struct software_node *ssam_node_group_slg1[] = {
+ 	&ssam_node_root,
++	&ssam_node_bat_ac,
++	&ssam_node_bat_main,
+ 	NULL,
+ };
+ 
+@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = {
+ /* Devices for Surface Pro 7. */
+ static const struct software_node *ssam_node_group_sp7[] = {
+ 	&ssam_node_root,
++	&ssam_node_bat_ac,
++	&ssam_node_bat_main,
+ 	NULL,
+ };
+ 
+-- 
+2.31.1
+
+From 225f6f6b8848bb6c4752510251791ce7cd7e8ed6 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:37 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile
+ device
+
+Add the SSAM platform profile device to the SSAM device registry. This
+device is accessible under the thermal subsystem (TC=0x03) and needs to
+be registered for all Surface models.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface/surface_aggregator_registry.c         | 15 +++++++++++++++
+ 1 file changed, 15 insertions(+)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index cde279692842..33904613dd4b 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = {
+ 	.parent = &ssam_node_hub_base,
+ };
+ 
++/* Platform profile / performance-mode device. */
++static const struct software_node ssam_node_tmp_pprof = {
++	.name = "ssam:01:03:01:00:01",
++	.parent = &ssam_node_root,
++};
++
+ /* Devices for Surface Book 2. */
+ static const struct software_node *ssam_node_group_sb2[] = {
+ 	&ssam_node_root,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = {
+ 	&ssam_node_bat_ac,
+ 	&ssam_node_bat_main,
+ 	&ssam_node_bat_sb3base,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+ /* Devices for Surface Laptop 1. */
+ static const struct software_node *ssam_node_group_sl1[] = {
+ 	&ssam_node_root,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+ /* Devices for Surface Laptop 2. */
+ static const struct software_node *ssam_node_group_sl2[] = {
+ 	&ssam_node_root,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
+ 	&ssam_node_root,
+ 	&ssam_node_bat_ac,
+ 	&ssam_node_bat_main,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+@@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = {
+ 	&ssam_node_root,
+ 	&ssam_node_bat_ac,
+ 	&ssam_node_bat_main,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+ /* Devices for Surface Pro 5. */
+ static const struct software_node *ssam_node_group_sp5[] = {
+ 	&ssam_node_root,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+ /* Devices for Surface Pro 6. */
+ static const struct software_node *ssam_node_group_sp6[] = {
+ 	&ssam_node_root,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+@@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
+ 	&ssam_node_root,
+ 	&ssam_node_bat_ac,
+ 	&ssam_node_bat_main,
++	&ssam_node_tmp_pprof,
+ 	NULL,
+ };
+ 
+-- 
+2.31.1
+
+From 982bd482ea8323218ee40a4ff42b8d7f50f30859 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:38 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device
+
+Add the detachment system (DTX) SSAM device for the Surface Book 3. This
+device is accessible under the base (TC=0x11) subsystem.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index 33904613dd4b..dc044d06828b 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = {
+ 	.parent = &ssam_node_root,
+ };
+ 
++/* DTX / detachment-system device (Surface Book 3). */
++static const struct software_node ssam_node_bas_dtx = {
++	.name = "ssam:01:11:01:00:00",
++	.parent = &ssam_node_root,
++};
++
+ /* Devices for Surface Book 2. */
+ static const struct software_node *ssam_node_group_sb2[] = {
+ 	&ssam_node_root,
+@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
+ 	&ssam_node_bat_main,
+ 	&ssam_node_bat_sb3base,
+ 	&ssam_node_tmp_pprof,
++	&ssam_node_bas_dtx,
+ 	NULL,
+ };
+ 
+-- 
+2.31.1
+
+From 78b63edb2081fd123019b0f61a6d09db47ad4643 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 12 Feb 2021 12:54:39 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem
+ devices
+
+Add HID subsystem (TC=0x15) devices. These devices need to be registered
+for 7th-generation Surface models. On previous generations, these
+devices are either provided as platform devices via ACPI (Surface Laptop
+1 and 2) or implemented as standard USB device.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface/surface_aggregator_registry.c     | 49 +++++++++++++++++++
+ 1 file changed, 49 insertions(+)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index dc044d06828b..caee90d135c5 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = {
+ 	.parent = &ssam_node_root,
+ };
+ 
++/* HID keyboard. */
++static const struct software_node ssam_node_hid_main_keyboard = {
++	.name = "ssam:01:15:02:01:00",
++	.parent = &ssam_node_root,
++};
++
++/* HID touchpad. */
++static const struct software_node ssam_node_hid_main_touchpad = {
++	.name = "ssam:01:15:02:03:00",
++	.parent = &ssam_node_root,
++};
++
++/* HID device instance 5 (unknown HID device). */
++static const struct software_node ssam_node_hid_main_iid5 = {
++	.name = "ssam:01:15:02:05:00",
++	.parent = &ssam_node_root,
++};
++
++/* HID keyboard (base hub). */
++static const struct software_node ssam_node_hid_base_keyboard = {
++	.name = "ssam:01:15:02:01:00",
++	.parent = &ssam_node_hub_base,
++};
++
++/* HID touchpad (base hub). */
++static const struct software_node ssam_node_hid_base_touchpad = {
++	.name = "ssam:01:15:02:03:00",
++	.parent = &ssam_node_hub_base,
++};
++
++/* HID device instance 5 (unknown HID device, base hub). */
++static const struct software_node ssam_node_hid_base_iid5 = {
++	.name = "ssam:01:15:02:05:00",
++	.parent = &ssam_node_hub_base,
++};
++
++/* HID device instance 6 (unknown HID device, base hub). */
++static const struct software_node ssam_node_hid_base_iid6 = {
++	.name = "ssam:01:15:02:06:00",
++	.parent = &ssam_node_hub_base,
++};
++
+ /* Devices for Surface Book 2. */
+ static const struct software_node *ssam_node_group_sb2[] = {
+ 	&ssam_node_root,
+@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = {
+ 	&ssam_node_bat_sb3base,
+ 	&ssam_node_tmp_pprof,
+ 	&ssam_node_bas_dtx,
++	&ssam_node_hid_base_keyboard,
++	&ssam_node_hid_base_touchpad,
++	&ssam_node_hid_base_iid5,
++	&ssam_node_hid_base_iid6,
+ 	NULL,
+ };
+ 
+@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = {
+ 	&ssam_node_bat_ac,
+ 	&ssam_node_bat_main,
+ 	&ssam_node_tmp_pprof,
++	&ssam_node_hid_main_keyboard,
++	&ssam_node_hid_main_touchpad,
++	&ssam_node_hid_main_iid5,
+ 	NULL,
+ };
+ 
+-- 
+2.31.1
+
+From 5b1f25eec0165e7a745cc87e4a45e3dbafb36016 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Thu, 11 Feb 2021 21:17:03 +0100
+Subject: [PATCH] platform/surface: Add platform profile driver
+
+Add a driver to provide platform profile support on 5th- and later
+generation Microsoft Surface devices with a Surface System Aggregator
+Module. On those devices, the platform profile can be used to influence
+cooling behavior and power consumption.
+
+For example, the default 'quiet' profile limits fan noise and in turn
+sacrifices performance of the discrete GPU found on Surface Books. Its
+full performance can only be unlocked on the 'performance' profile.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Reviewed-by: Hans de Goede <hdegoede@redhat.com>
+Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ MAINTAINERS                                   |   6 +
+ drivers/platform/surface/Kconfig              |  22 ++
+ drivers/platform/surface/Makefile             |   1 +
+ .../surface/surface_platform_profile.c        | 190 ++++++++++++++++++
+ 4 files changed, 219 insertions(+)
+ create mode 100644 drivers/platform/surface/surface_platform_profile.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index f6c524630575..fce5cdcefc0b 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11889,6 +11889,12 @@ L:	platform-driver-x86@vger.kernel.org
+ S:	Maintained
+ F:	drivers/platform/surface/surface_hotplug.c
+ 
++MICROSOFT SURFACE PLATFORM PROFILE DRIVER
++M:	Maximilian Luz <luzmaximilian@gmail.com>
++L:	platform-driver-x86@vger.kernel.org
++S:	Maintained
++F:	drivers/platform/surface/surface_platform_profile.c
++
+ MICROSOFT SURFACE PRO 3 BUTTON DRIVER
+ M:	Chen Yu <yu.c.chen@intel.com>
+ L:	platform-driver-x86@vger.kernel.org
+diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
+index c51c55204b5f..6fb304da845f 100644
+--- a/drivers/platform/surface/Kconfig
++++ b/drivers/platform/surface/Kconfig
+@@ -139,6 +139,28 @@ config SURFACE_HOTPLUG
+ 	  Select M or Y here, if you want to (fully) support hot-plugging of
+ 	  dGPU devices on the Surface Book 2 and/or 3 during D3cold.
+ 
++config SURFACE_PLATFORM_PROFILE
++	tristate "Surface Platform Profile Driver"
++	depends on SURFACE_AGGREGATOR_REGISTRY
++	select ACPI_PLATFORM_PROFILE
++	help
++	  Provides support for the ACPI platform profile on 5th- and later
++	  generation Microsoft Surface devices.
++
++	  More specifically, this driver provides ACPI platform profile support
++	  on Microsoft Surface devices with a Surface System Aggregator Module
++	  (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In
++	  other words, this driver provides platform profile support on the
++	  Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and
++	  later. On those devices, the platform profile can significantly
++	  influence cooling behavior, e.g. setting it to 'quiet' (default) or
++	  'low-power' can significantly limit performance of the discrete GPU on
++	  Surface Books, while in turn leading to lower power consumption and/or
++	  less fan noise.
++
++	  Select M or Y here, if you want to include ACPI platform profile
++	  support on the above mentioned devices.
++
+ config SURFACE_PRO3_BUTTON
+ 	tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
+ 	depends on INPUT
+diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
+index ed12676f06e6..f7187bae1729 100644
+--- a/drivers/platform/surface/Makefile
++++ b/drivers/platform/surface/Makefile
+@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
+ obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
+ obj-$(CONFIG_SURFACE_GPE)		+= surface_gpe.o
+ obj-$(CONFIG_SURFACE_HOTPLUG)		+= surface_hotplug.o
++obj-$(CONFIG_SURFACE_PLATFORM_PROFILE)	+= surface_platform_profile.o
+ obj-$(CONFIG_SURFACE_PRO3_BUTTON)	+= surfacepro3_button.o
+diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
+new file mode 100644
+index 000000000000..0081b01a5b0f
+--- /dev/null
++++ b/drivers/platform/surface/surface_platform_profile.c
+@@ -0,0 +1,190 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Surface Platform Profile / Performance Mode driver for Surface System
++ * Aggregator Module (thermal subsystem).
++ *
++ * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/platform_profile.h>
++#include <linux/types.h>
++
++#include <linux/surface_aggregator/device.h>
++
++enum ssam_tmp_profile {
++	SSAM_TMP_PROFILE_NORMAL             = 1,
++	SSAM_TMP_PROFILE_BATTERY_SAVER      = 2,
++	SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
++	SSAM_TMP_PROFILE_BEST_PERFORMANCE   = 4,
++};
++
++struct ssam_tmp_profile_info {
++	__le32 profile;
++	__le16 unknown1;
++	__le16 unknown2;
++} __packed;
++
++struct ssam_tmp_profile_device {
++	struct ssam_device *sdev;
++	struct platform_profile_handler handler;
++};
++
++static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
++	.target_category = SSAM_SSH_TC_TMP,
++	.command_id      = 0x02,
++});
++
++static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
++	.target_category = SSAM_SSH_TC_TMP,
++	.command_id      = 0x03,
++});
++
++static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
++{
++	struct ssam_tmp_profile_info info;
++	int status;
++
++	status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
++	if (status < 0)
++		return status;
++
++	*p = le32_to_cpu(info.profile);
++	return 0;
++}
++
++static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
++{
++	__le32 profile_le = cpu_to_le32(p);
++
++	return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
++}
++
++static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
++{
++	switch (p) {
++	case SSAM_TMP_PROFILE_NORMAL:
++		return PLATFORM_PROFILE_BALANCED;
++
++	case SSAM_TMP_PROFILE_BATTERY_SAVER:
++		return PLATFORM_PROFILE_LOW_POWER;
++
++	case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
++		return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
++
++	case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
++		return PLATFORM_PROFILE_PERFORMANCE;
++
++	default:
++		dev_err(&sdev->dev, "invalid performance profile: %d", p);
++		return -EINVAL;
++	}
++}
++
++static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
++{
++	switch (p) {
++	case PLATFORM_PROFILE_LOW_POWER:
++		return SSAM_TMP_PROFILE_BATTERY_SAVER;
++
++	case PLATFORM_PROFILE_BALANCED:
++		return SSAM_TMP_PROFILE_NORMAL;
++
++	case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
++		return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
++
++	case PLATFORM_PROFILE_PERFORMANCE:
++		return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
++
++	default:
++		/* This should have already been caught by platform_profile_store(). */
++		WARN(true, "unsupported platform profile");
++		return -EOPNOTSUPP;
++	}
++}
++
++static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
++				     enum platform_profile_option *profile)
++{
++	struct ssam_tmp_profile_device *tpd;
++	enum ssam_tmp_profile tp;
++	int status;
++
++	tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
++
++	status = ssam_tmp_profile_get(tpd->sdev, &tp);
++	if (status)
++		return status;
++
++	status = convert_ssam_to_profile(tpd->sdev, tp);
++	if (status < 0)
++		return status;
++
++	*profile = status;
++	return 0;
++}
++
++static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
++				     enum platform_profile_option profile)
++{
++	struct ssam_tmp_profile_device *tpd;
++	int tp;
++
++	tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
++
++	tp = convert_profile_to_ssam(tpd->sdev, profile);
++	if (tp < 0)
++		return tp;
++
++	return ssam_tmp_profile_set(tpd->sdev, tp);
++}
++
++static int surface_platform_profile_probe(struct ssam_device *sdev)
++{
++	struct ssam_tmp_profile_device *tpd;
++
++	tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
++	if (!tpd)
++		return -ENOMEM;
++
++	tpd->sdev = sdev;
++
++	tpd->handler.profile_get = ssam_platform_profile_get;
++	tpd->handler.profile_set = ssam_platform_profile_set;
++
++	set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
++	set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
++	set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
++	set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
++
++	platform_profile_register(&tpd->handler);
++	return 0;
++}
++
++static void surface_platform_profile_remove(struct ssam_device *sdev)
++{
++	platform_profile_remove();
++}
++
++static const struct ssam_device_id ssam_platform_profile_match[] = {
++	{ SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
++	{ },
++};
++MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
++
++static struct ssam_device_driver surface_platform_profile = {
++	.probe = surface_platform_profile_probe,
++	.remove = surface_platform_profile_remove,
++	.match_table = ssam_platform_profile_match,
++	.driver = {
++		.name = "surface_platform_profile",
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_ssam_device_driver(surface_platform_profile);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From 3587540017845f99f6d311bb59c6c26824bf9237 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Thu, 4 Mar 2021 20:05:24 +0100
+Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x
+ define static functions
+
+The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce
+boiler-plate code for SSAM request definitions by defining a wrapper
+function for the specified request. The client device variants of those
+macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the
+multi-device (MD) variants, e.g.:
+
+    #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...)   \
+        SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec)  \
+        int name(struct ssam_device *sdev, rtype *ret)            \
+        {                                                         \
+            return __raw_##name(sdev->ctrl, sdev->uid.target,     \
+                                sdev->uid.instance, ret);         \
+        }
+
+This now creates the problem that it is not possible to declare the
+generated functions static via
+
+    static SSAM_DEFINE_SYNC_REQUEST_CL_R(...)
+
+as this will only apply to the function defined by the multi-device
+macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with
+`-Wmissing-prototypes' rightfully complains that there is a 'static'
+keyword missing.
+
+To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define
+static functions. Non-client-device macros are also changed for
+consistency. In general, we expect those functions to be only used
+locally in the respective drivers for the corresponding interfaces, so
+having to define a wrapper function to be able to export this should be
+the odd case out.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Fixes: b78b4982d763 ("platform/surface: Add platform profile driver")
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../driver-api/surface_aggregator/client.rst  |  4 +-
+ .../platform/surface/aggregator/controller.c  | 10 +--
+ .../surface/surface_aggregator_registry.c     |  2 +-
+ .../surface/surface_platform_profile.c        |  4 +-
+ include/linux/surface_aggregator/controller.h | 74 +++++++++----------
+ include/linux/surface_aggregator/device.h     | 31 ++++----
+ 6 files changed, 63 insertions(+), 62 deletions(-)
+
+diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst
+index 26d13085a117..e519d374c378 100644
+--- a/Documentation/driver-api/surface_aggregator/client.rst
++++ b/Documentation/driver-api/surface_aggregator/client.rst
+@@ -248,7 +248,7 @@ This example defines a function
+ 
+ .. code-block:: c
+ 
+-   int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
++   static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
+ 
+ executing the specified request, with the controller passed in when calling
+ said function. In this example, the argument is provided via the ``arg``
+@@ -296,7 +296,7 @@ This invocation of the macro defines a function
+ 
+ .. code-block:: c
+ 
+-   int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
++   static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
+ 
+ executing the specified request, using the device IDs and controller given
+ in the client device. The full list of such macros for client devices is:
+diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
+index 5bcb59ed579d..aa6f37b4f46e 100644
+--- a/drivers/platform/surface/aggregator/controller.c
++++ b/drivers/platform/surface/aggregator/controller.c
+@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);
+ 
+ /* -- Internal SAM requests. ------------------------------------------------ */
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
+ 	.target_category = SSAM_SSH_TC_SAM,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x13,
+ 	.instance_id     = 0x00,
+ });
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
+ 	.target_category = SSAM_SSH_TC_SAM,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x15,
+ 	.instance_id     = 0x00,
+ });
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
+ 	.target_category = SSAM_SSH_TC_SAM,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x16,
+ 	.instance_id     = 0x00,
+ });
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
+ 	.target_category = SSAM_SSH_TC_SAM,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x33,
+ 	.instance_id     = 0x00,
+ });
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
+ 	.target_category = SSAM_SSH_TC_SAM,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x34,
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index caee90d135c5..cdb4a95af3e8 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -302,7 +302,7 @@ struct ssam_base_hub {
+ 	struct ssam_event_notifier notif;
+ };
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
+ 	.target_category = SSAM_SSH_TC_BAS,
+ 	.target_id       = 0x01,
+ 	.command_id      = 0x0d,
+diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
+index 0081b01a5b0f..6373d3b5eb7f 100644
+--- a/drivers/platform/surface/surface_platform_profile.c
++++ b/drivers/platform/surface/surface_platform_profile.c
+@@ -32,12 +32,12 @@ struct ssam_tmp_profile_device {
+ 	struct platform_profile_handler handler;
+ };
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
++SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
+ 	.target_category = SSAM_SSH_TC_TMP,
+ 	.command_id      = 0x02,
+ });
+ 
+-static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
++SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
+ 	.target_category = SSAM_SSH_TC_TMP,
+ 	.command_id      = 0x03,
+ });
+diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
+index f4b1ba887384..0806796eabcb 100644
+--- a/include/linux/surface_aggregator/controller.h
++++ b/include/linux/surface_aggregator/controller.h
+@@ -344,16 +344,16 @@ struct ssam_request_spec_md {
+  * request has been fully completed. The required transport buffer will be
+  * allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl)``, returning the status of the request, which is zero on success and
+- * negative on failure. The ``ctrl`` parameter is the controller via which the
+- * request is being sent.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl)``, returning the status of the request, which is
++ * zero on success and negative on failure. The ``ctrl`` parameter is the
++ * controller via which the request is being sent.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...)				\
+-	int name(struct ssam_controller *ctrl)					\
++	static int name(struct ssam_controller *ctrl)				\
+ 	{									\
+ 		struct ssam_request_spec s = (struct ssam_request_spec)spec;	\
+ 		struct ssam_request rqst;					\
+@@ -383,17 +383,17 @@ struct ssam_request_spec_md {
+  * returning once the request has been fully completed. The required transport
+  * buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl, const atype *arg)``, returning the status of the request, which is
+- * zero on success and negative on failure. The ``ctrl`` parameter is the
+- * controller via which the request is sent. The request argument is specified
+- * via the ``arg`` pointer.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl, const atype *arg)``, returning the status of the
++ * request, which is zero on success and negative on failure. The ``ctrl``
++ * parameter is the controller via which the request is sent. The request
++ * argument is specified via the ``arg`` pointer.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...)			\
+-	int name(struct ssam_controller *ctrl, const atype *arg)		\
++	static int name(struct ssam_controller *ctrl, const atype *arg)		\
+ 	{									\
+ 		struct ssam_request_spec s = (struct ssam_request_spec)spec;	\
+ 		struct ssam_request rqst;					\
+@@ -424,17 +424,17 @@ struct ssam_request_spec_md {
+  * request itself, returning once the request has been fully completed. The
+  * required transport buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on
+- * success and negative on failure. The ``ctrl`` parameter is the controller
+- * via which the request is sent. The request's return value is written to the
+- * memory pointed to by the ``ret`` parameter.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request,
++ * which is zero on success and negative on failure. The ``ctrl`` parameter is
++ * the controller via which the request is sent. The request's return value is
++ * written to the memory pointed to by the ``ret`` parameter.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...)			\
+-	int name(struct ssam_controller *ctrl, rtype *ret)			\
++	static int name(struct ssam_controller *ctrl, rtype *ret)		\
+ 	{									\
+ 		struct ssam_request_spec s = (struct ssam_request_spec)spec;	\
+ 		struct ssam_request rqst;					\
+@@ -483,17 +483,17 @@ struct ssam_request_spec_md {
+  * returning once the request has been fully completed. The required transport
+  * buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is
+- * zero on success and negative on failure. The ``ctrl`` parameter is the
+- * controller via which the request is sent, ``tid`` the target ID for the
+- * request, and ``iid`` the instance ID.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the
++ * request, which is zero on success and negative on failure. The ``ctrl``
++ * parameter is the controller via which the request is sent, ``tid`` the
++ * target ID for the request, and ``iid`` the instance ID.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...)				\
+-	int name(struct ssam_controller *ctrl, u8 tid, u8 iid)			\
++	static int name(struct ssam_controller *ctrl, u8 tid, u8 iid)		\
+ 	{									\
+ 		struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
+ 		struct ssam_request rqst;					\
+@@ -524,18 +524,18 @@ struct ssam_request_spec_md {
+  * the request itself, returning once the request has been fully completed.
+  * The required transport buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the
+- * request, which is zero on success and negative on failure. The ``ctrl``
+- * parameter is the controller via which the request is sent, ``tid`` the
+- * target ID for the request, and ``iid`` the instance ID. The request argument
+- * is specified via the ``arg`` pointer.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the
++ * status of the request, which is zero on success and negative on failure.
++ * The ``ctrl`` parameter is the controller via which the request is sent,
++ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The
++ * request argument is specified via the ``arg`` pointer.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...)			\
+-	int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\
++	static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \
+ 	{									\
+ 		struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
+ 		struct ssam_request rqst;					\
+@@ -567,18 +567,18 @@ struct ssam_request_spec_md {
+  * execution of the request itself, returning once the request has been fully
+  * completed. The required transport buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_controller
+- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request,
+- * which is zero on success and negative on failure. The ``ctrl`` parameter is
+- * the controller via which the request is sent, ``tid`` the target ID for the
+- * request, and ``iid`` the instance ID. The request's return value is written
+- * to the memory pointed to by the ``ret`` parameter.
++ * The generated function is defined as ``static int name(struct
++ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status
++ * of the request, which is zero on success and negative on failure. The
++ * ``ctrl`` parameter is the controller via which the request is sent, ``tid``
++ * the target ID for the request, and ``iid`` the instance ID. The request's
++ * return value is written to the memory pointed to by the ``ret`` parameter.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...)			\
+-	int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)	\
++	static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
+ 	{									\
+ 		struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
+ 		struct ssam_request rqst;					\
+diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
+index 02f3e06c0a60..4441ad667c3f 100644
+--- a/include/linux/surface_aggregator/device.h
++++ b/include/linux/surface_aggregator/device.h
+@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
+  * request has been fully completed. The required transport buffer will be
+  * allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_device *sdev)``,
+- * returning the status of the request, which is zero on success and negative
+- * on failure. The ``sdev`` parameter specifies both the target device of the
+- * request and by association the controller via which the request is sent.
++ * The generated function is defined as ``static int name(struct ssam_device
++ * *sdev)``, returning the status of the request, which is zero on success and
++ * negative on failure. The ``sdev`` parameter specifies both the target
++ * device of the request and by association the controller via which the
++ * request is sent.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...)			\
+ 	SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec)		\
+-	int name(struct ssam_device *sdev)				\
++	static int name(struct ssam_device *sdev)			\
+ 	{								\
+ 		return __raw_##name(sdev->ctrl, sdev->uid.target,	\
+ 				    sdev->uid.instance);		\
+@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
+  * itself, returning once the request has been fully completed. The required
+  * transport buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_device *sdev,
+- * const atype *arg)``, returning the status of the request, which is zero on
+- * success and negative on failure. The ``sdev`` parameter specifies both the
+- * target device of the request and by association the controller via which
+- * the request is sent. The request's argument is specified via the ``arg``
+- * pointer.
++ * The generated function is defined as ``static int name(struct ssam_device
++ * *sdev, const atype *arg)``, returning the status of the request, which is
++ * zero on success and negative on failure. The ``sdev`` parameter specifies
++ * both the target device of the request and by association the controller via
++ * which the request is sent. The request's argument is specified via the
++ * ``arg`` pointer.
+  *
+  * Refer to ssam_request_sync_onstack() for more details on the behavior of
+  * the generated function.
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...)		\
+ 	SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec)	\
+-	int name(struct ssam_device *sdev, const atype *arg)		\
++	static int name(struct ssam_device *sdev, const atype *arg)	\
+ 	{								\
+ 		return __raw_##name(sdev->ctrl, sdev->uid.target,	\
+ 				    sdev->uid.instance, arg);		\
+@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
+  * itself, returning once the request has been fully completed. The required
+  * transport buffer will be allocated on the stack.
+  *
+- * The generated function is defined as ``int name(struct ssam_device *sdev,
+- * rtype *ret)``, returning the status of the request, which is zero on
++ * The generated function is defined as ``static int name(struct ssam_device
++ * *sdev, rtype *ret)``, returning the status of the request, which is zero on
+  * success and negative on failure. The ``sdev`` parameter specifies both the
+  * target device of the request and by association the controller via which
+  * the request is sent. The request's return value is written to the memory
+@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
+  */
+ #define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...)		\
+ 	SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec)	\
+-	int name(struct ssam_device *sdev, rtype *ret)			\
++	static int name(struct ssam_device *sdev, rtype *ret)		\
+ 	{								\
+ 		return __raw_##name(sdev->ctrl, sdev->uid.target,	\
+ 				    sdev->uid.instance, ret);		\
+-- 
+2.31.1
+
+From 3e6957d1939ac4fd7a32df8a3805dcd32243875d Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Mon, 8 Mar 2021 19:48:17 +0100
+Subject: [PATCH] platform/surface: Add DTX driver
+
+The Microsoft Surface Book series devices consist of a so-called
+clipboard part (containing the CPU, touchscreen, and primary battery)
+and a base part (containing keyboard, secondary battery, and optional
+discrete GPU). These parts can be separated, i.e. the clipboard can be
+detached and used as tablet.
+
+This detachment process is initiated by pressing a button. On the
+Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator
+Module (i.e. the embedded controller on those devices) attempts to send
+a notification to any listening client driver and waits for further
+instructions (i.e. whether the detachment process should continue or be
+aborted). If it does not receive a response in a certain time-frame, the
+detachment process (by default) continues and the clipboard can be
+physically separated. In other words, (by default and) without a driver,
+the detachment process takes about 10 seconds to complete.
+
+This commit introduces a driver for this detachment system (called DTX).
+This driver allows a user-space daemon to control and influence the
+detachment behavior. Specifically, it forwards any detachment requests
+to user-space, allows user-space to make such requests itself, and
+allows handling of those requests. Requests can be handled by either
+aborting, continuing/allowing, or delaying (i.e. resetting the timeout
+via a heartbeat commend). The user-space API is implemented via the
+/dev/surface/dtx miscdevice.
+
+In addition, user-space can change the default behavior on timeout from
+allowing detachment to disallowing it, which is useful if the (optional)
+discrete GPU is in use.
+
+Furthermore, this driver allows user-space to receive notifications
+about the state of the base, specifically when it is physically removed
+(as opposed to detachment requested), in what manner it is connected
+(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base
+is connected. Based on this information, the driver also provides a
+simple tablet-mode switch (aliasing all modes without keyboard access,
+i.e. tablet-mode and studio-mode to its reported tablet-mode).
+
+An implementation of such a user-space daemon, allowing configuration of
+detachment behavior via scripts (e.g. safely unmounting USB devices
+connected to the base before continuing) can be found at [1].
+
+[1]: https://github.com/linux-surface/surface-dtx-daemon
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../userspace-api/ioctl/ioctl-number.rst      |    2 +
+ MAINTAINERS                                   |    7 +
+ drivers/platform/surface/Kconfig              |   16 +
+ drivers/platform/surface/Makefile             |    1 +
+ drivers/platform/surface/surface_dtx.c        | 1201 +++++++++++++++++
+ include/uapi/linux/surface_aggregator/dtx.h   |  146 ++
+ 6 files changed, 1373 insertions(+)
+ create mode 100644 drivers/platform/surface/surface_dtx.c
+ create mode 100644 include/uapi/linux/surface_aggregator/dtx.h
+
+diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
+index 599bd4493944..1c28b8ef6677 100644
+--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
+@@ -327,6 +327,8 @@ Code  Seq#    Include File                                           Comments
+ 0xA4  00-1F  uapi/asm/sgx.h                                          <mailto:linux-sgx@vger.kernel.org>
+ 0xA5  01     linux/surface_aggregator/cdev.h                         Microsoft Surface Platform System Aggregator
+                                                                      <mailto:luzmaximilian@gmail.com>
++0xA5  20-2F  linux/surface_aggregator/dtx.h                          Microsoft Surface DTX driver
++                                                                     <mailto:luzmaximilian@gmail.com>
+ 0xAA  00-3F  linux/uapi/linux/userfaultfd.h
+ 0xAB  00-1F  linux/nbd.h
+ 0xAC  00-1F  linux/raw.h
+diff --git a/MAINTAINERS b/MAINTAINERS
+index fce5cdcefc0b..3917e7363520 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11868,6 +11868,13 @@ F:	drivers/scsi/smartpqi/smartpqi*.[ch]
+ F:	include/linux/cciss*.h
+ F:	include/uapi/linux/cciss*.h
+ 
++MICROSOFT SURFACE DTX DRIVER
++M:	Maximilian Luz <luzmaximilian@gmail.com>
++L:	platform-driver-x86@vger.kernel.org
++S:	Maintained
++F:	drivers/platform/surface/surface_dtx.c
++F:	include/uapi/linux/surface_aggregator/dtx.h
++
+ MICROSOFT SURFACE GPE LID SUPPORT DRIVER
+ M:	Maximilian Luz <luzmaximilian@gmail.com>
+ L:	platform-driver-x86@vger.kernel.org
+diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
+index 6fb304da845f..41d67bb250fd 100644
+--- a/drivers/platform/surface/Kconfig
++++ b/drivers/platform/surface/Kconfig
+@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH
+ 	  This driver provides a sysfs switch to set the power-state of the
+ 	  discrete GPU found on the Microsoft Surface Book 1.
+ 
++config SURFACE_DTX
++	tristate "Surface DTX (Detachment System) Driver"
++	depends on SURFACE_AGGREGATOR
++	depends on INPUT
++	help
++	  Driver for the Surface Book clipboard detachment system (DTX).
++
++	  On the Surface Book series devices, the display part containing the
++	  CPU (called the clipboard) can be detached from the base (containing a
++	  battery, the keyboard, and, optionally, a discrete GPU) by (if
++	  necessary) unlocking and opening the latch connecting both parts.
++
++	  This driver provides a user-space interface that can influence the
++	  behavior of this process, which includes the option to abort it in
++	  case the base is still in use or speed it up in case it is not.
++
+ config SURFACE_GPE
+ 	tristate "Surface GPE/Lid Support Driver"
+ 	depends on DMI
+diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
+index f7187bae1729..0cc63440328d 100644
+--- a/drivers/platform/surface/Makefile
++++ b/drivers/platform/surface/Makefile
+@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR)	+= aggregator/
+ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV)	+= surface_aggregator_cdev.o
+ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
+ obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
++obj-$(CONFIG_SURFACE_DTX)		+= surface_dtx.o
+ obj-$(CONFIG_SURFACE_GPE)		+= surface_gpe.o
+ obj-$(CONFIG_SURFACE_HOTPLUG)		+= surface_hotplug.o
+ obj-$(CONFIG_SURFACE_PLATFORM_PROFILE)	+= surface_platform_profile.o
+diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
+new file mode 100644
+index 000000000000..1301fab0ea14
+--- /dev/null
++++ b/drivers/platform/surface/surface_dtx.c
+@@ -0,0 +1,1201 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Surface Book (gen. 2 and later) detachment system (DTX) driver.
++ *
++ * Provides a user-space interface to properly handle clipboard/tablet
++ * (containing screen and processor) detachment from the base of the device
++ * (containing the keyboard and optionally a discrete GPU). Allows to
++ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
++ * use), or request detachment via user-space.
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <linux/fs.h>
++#include <linux/input.h>
++#include <linux/ioctl.h>
++#include <linux/kernel.h>
++#include <linux/kfifo.h>
++#include <linux/kref.h>
++#include <linux/miscdevice.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/platform_device.h>
++#include <linux/poll.h>
++#include <linux/rwsem.h>
++#include <linux/slab.h>
++#include <linux/workqueue.h>
++
++#include <linux/surface_aggregator/controller.h>
++#include <linux/surface_aggregator/dtx.h>
++
++
++/* -- SSAM interface. ------------------------------------------------------- */
++
++enum sam_event_cid_bas {
++	SAM_EVENT_CID_DTX_CONNECTION			= 0x0c,
++	SAM_EVENT_CID_DTX_REQUEST			= 0x0e,
++	SAM_EVENT_CID_DTX_CANCEL			= 0x0f,
++	SAM_EVENT_CID_DTX_LATCH_STATUS			= 0x11,
++};
++
++enum ssam_bas_base_state {
++	SSAM_BAS_BASE_STATE_DETACH_SUCCESS		= 0x00,
++	SSAM_BAS_BASE_STATE_ATTACHED			= 0x01,
++	SSAM_BAS_BASE_STATE_NOT_FEASIBLE		= 0x02,
++};
++
++enum ssam_bas_latch_status {
++	SSAM_BAS_LATCH_STATUS_CLOSED			= 0x00,
++	SSAM_BAS_LATCH_STATUS_OPENED			= 0x01,
++	SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN		= 0x02,
++	SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN	= 0x03,
++	SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE		= 0x04,
++};
++
++enum ssam_bas_cancel_reason {
++	SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE		= 0x00,  /* Low battery. */
++	SSAM_BAS_CANCEL_REASON_TIMEOUT			= 0x02,
++	SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN		= 0x03,
++	SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN	= 0x04,
++	SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE		= 0x05,
++};
++
++struct ssam_bas_base_info {
++	u8 state;
++	u8 base_id;
++} __packed;
++
++static_assert(sizeof(struct ssam_bas_base_info) == 2);
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x06,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x07,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x08,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x09,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x0a,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x0b,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x0c,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x0d,
++	.instance_id     = 0x00,
++});
++
++SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, {
++	.target_category = SSAM_SSH_TC_BAS,
++	.target_id       = 0x01,
++	.command_id      = 0x11,
++	.instance_id     = 0x00,
++});
++
++
++/* -- Main structures. ------------------------------------------------------ */
++
++enum sdtx_device_state {
++	SDTX_DEVICE_SHUTDOWN_BIT    = BIT(0),
++	SDTX_DEVICE_DIRTY_BASE_BIT  = BIT(1),
++	SDTX_DEVICE_DIRTY_MODE_BIT  = BIT(2),
++	SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),
++};
++
++struct sdtx_device {
++	struct kref kref;
++	struct rw_semaphore lock;         /* Guards device and controller reference. */
++
++	struct device *dev;
++	struct ssam_controller *ctrl;
++	unsigned long flags;
++
++	struct miscdevice mdev;
++	wait_queue_head_t waitq;
++	struct mutex write_lock;          /* Guards order of events/notifications. */
++	struct rw_semaphore client_lock;  /* Guards client list.                   */
++	struct list_head client_list;
++
++	struct delayed_work state_work;
++	struct {
++		struct ssam_bas_base_info base;
++		u8 device_mode;
++		u8 latch_status;
++	} state;
++
++	struct delayed_work mode_work;
++	struct input_dev *mode_switch;
++
++	struct ssam_event_notifier notif;
++};
++
++enum sdtx_client_state {
++	SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),
++};
++
++struct sdtx_client {
++	struct sdtx_device *ddev;
++	struct list_head node;
++	unsigned long flags;
++
++	struct fasync_struct *fasync;
++
++	struct mutex read_lock;           /* Guards FIFO buffer read access. */
++	DECLARE_KFIFO(buffer, u8, 512);
++};
++
++static void __sdtx_device_release(struct kref *kref)
++{
++	struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
++
++	mutex_destroy(&ddev->write_lock);
++	kfree(ddev);
++}
++
++static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev)
++{
++	if (ddev)
++		kref_get(&ddev->kref);
++
++	return ddev;
++}
++
++static void sdtx_device_put(struct sdtx_device *ddev)
++{
++	if (ddev)
++		kref_put(&ddev->kref, __sdtx_device_release);
++}
++
++
++/* -- Firmware value translations. ------------------------------------------ */
++
++static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state)
++{
++	switch (state) {
++	case SSAM_BAS_BASE_STATE_ATTACHED:
++		return SDTX_BASE_ATTACHED;
++
++	case SSAM_BAS_BASE_STATE_DETACH_SUCCESS:
++		return SDTX_BASE_DETACHED;
++
++	case SSAM_BAS_BASE_STATE_NOT_FEASIBLE:
++		return SDTX_DETACH_NOT_FEASIBLE;
++
++	default:
++		dev_err(ddev->dev, "unknown base state: %#04x\n", state);
++		return SDTX_UNKNOWN(state);
++	}
++}
++
++static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status)
++{
++	switch (status) {
++	case SSAM_BAS_LATCH_STATUS_CLOSED:
++		return SDTX_LATCH_CLOSED;
++
++	case SSAM_BAS_LATCH_STATUS_OPENED:
++		return SDTX_LATCH_OPENED;
++
++	case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN:
++		return SDTX_ERR_FAILED_TO_OPEN;
++
++	case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN:
++		return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
++
++	case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE:
++		return SDTX_ERR_FAILED_TO_CLOSE;
++
++	default:
++		dev_err(ddev->dev, "unknown latch status: %#04x\n", status);
++		return SDTX_UNKNOWN(status);
++	}
++}
++
++static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason)
++{
++	switch (reason) {
++	case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE:
++		return SDTX_DETACH_NOT_FEASIBLE;
++
++	case SSAM_BAS_CANCEL_REASON_TIMEOUT:
++		return SDTX_DETACH_TIMEDOUT;
++
++	case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN:
++		return SDTX_ERR_FAILED_TO_OPEN;
++
++	case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN:
++		return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
++
++	case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE:
++		return SDTX_ERR_FAILED_TO_CLOSE;
++
++	default:
++		dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason);
++		return SDTX_UNKNOWN(reason);
++	}
++}
++
++
++/* -- IOCTLs. --------------------------------------------------------------- */
++
++static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev,
++				    struct sdtx_base_info __user *buf)
++{
++	struct ssam_bas_base_info raw;
++	struct sdtx_base_info info;
++	int status;
++
++	lockdep_assert_held_read(&ddev->lock);
++
++	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw);
++	if (status < 0)
++		return status;
++
++	info.state = sdtx_translate_base_state(ddev, raw.state);
++	info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
++
++	if (copy_to_user(buf, &info, sizeof(info)))
++		return -EFAULT;
++
++	return 0;
++}
++
++static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf)
++{
++	u8 mode;
++	int status;
++
++	lockdep_assert_held_read(&ddev->lock);
++
++	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
++	if (status < 0)
++		return status;
++
++	return put_user(mode, buf);
++}
++
++static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf)
++{
++	u8 latch;
++	int status;
++
++	lockdep_assert_held_read(&ddev->lock);
++
++	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
++	if (status < 0)
++		return status;
++
++	return put_user(sdtx_translate_latch_status(ddev, latch), buf);
++}
++
++static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg)
++{
++	struct sdtx_device *ddev = client->ddev;
++
++	lockdep_assert_held_read(&ddev->lock);
++
++	switch (cmd) {
++	case SDTX_IOCTL_EVENTS_ENABLE:
++		set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
++		return 0;
++
++	case SDTX_IOCTL_EVENTS_DISABLE:
++		clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
++		return 0;
++
++	case SDTX_IOCTL_LATCH_LOCK:
++		return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
++
++	case SDTX_IOCTL_LATCH_UNLOCK:
++		return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
++
++	case SDTX_IOCTL_LATCH_REQUEST:
++		return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
++
++	case SDTX_IOCTL_LATCH_CONFIRM:
++		return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
++
++	case SDTX_IOCTL_LATCH_HEARTBEAT:
++		return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
++
++	case SDTX_IOCTL_LATCH_CANCEL:
++		return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
++
++	case SDTX_IOCTL_GET_BASE_INFO:
++		return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
++
++	case SDTX_IOCTL_GET_DEVICE_MODE:
++		return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
++
++	case SDTX_IOCTL_GET_LATCH_STATUS:
++		return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
++
++	default:
++		return -EINVAL;
++	}
++}
++
++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
++{
++	struct sdtx_client *client = file->private_data;
++	long status;
++
++	if (down_read_killable(&client->ddev->lock))
++		return -ERESTARTSYS;
++
++	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
++		up_read(&client->ddev->lock);
++		return -ENODEV;
++	}
++
++	status = __surface_dtx_ioctl(client, cmd, arg);
++
++	up_read(&client->ddev->lock);
++	return status;
++}
++
++
++/* -- File operations. ------------------------------------------------------ */
++
++static int surface_dtx_open(struct inode *inode, struct file *file)
++{
++	struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev);
++	struct sdtx_client *client;
++
++	/* Initialize client. */
++	client = kzalloc(sizeof(*client), GFP_KERNEL);
++	if (!client)
++		return -ENOMEM;
++
++	client->ddev = sdtx_device_get(ddev);
++
++	INIT_LIST_HEAD(&client->node);
++
++	mutex_init(&client->read_lock);
++	INIT_KFIFO(client->buffer);
++
++	file->private_data = client;
++
++	/* Attach client. */
++	down_write(&ddev->client_lock);
++
++	/*
++	 * Do not add a new client if the device has been shut down. Note that
++	 * it's enough to hold the client_lock here as, during shutdown, we
++	 * only acquire that lock and remove clients after marking the device
++	 * as shut down.
++	 */
++	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
++		up_write(&ddev->client_lock);
++		sdtx_device_put(client->ddev);
++		kfree(client);
++		return -ENODEV;
++	}
++
++	list_add_tail(&client->node, &ddev->client_list);
++	up_write(&ddev->client_lock);
++
++	stream_open(inode, file);
++	return 0;
++}
++
++static int surface_dtx_release(struct inode *inode, struct file *file)
++{
++	struct sdtx_client *client = file->private_data;
++
++	/* Detach client. */
++	down_write(&client->ddev->client_lock);
++	list_del(&client->node);
++	up_write(&client->ddev->client_lock);
++
++	/* Free client. */
++	sdtx_device_put(client->ddev);
++	mutex_destroy(&client->read_lock);
++	kfree(client);
++
++	return 0;
++}
++
++static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
++{
++	struct sdtx_client *client = file->private_data;
++	struct sdtx_device *ddev = client->ddev;
++	unsigned int copied;
++	int status = 0;
++
++	if (down_read_killable(&ddev->lock))
++		return -ERESTARTSYS;
++
++	/* Make sure we're not shut down. */
++	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
++		up_read(&ddev->lock);
++		return -ENODEV;
++	}
++
++	do {
++		/* Check availability, wait if necessary. */
++		if (kfifo_is_empty(&client->buffer)) {
++			up_read(&ddev->lock);
++
++			if (file->f_flags & O_NONBLOCK)
++				return -EAGAIN;
++
++			status = wait_event_interruptible(ddev->waitq,
++							  !kfifo_is_empty(&client->buffer) ||
++							  test_bit(SDTX_DEVICE_SHUTDOWN_BIT,
++								   &ddev->flags));
++			if (status < 0)
++				return status;
++
++			if (down_read_killable(&client->ddev->lock))
++				return -ERESTARTSYS;
++
++			/* Need to check that we're not shut down again. */
++			if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
++				up_read(&ddev->lock);
++				return -ENODEV;
++			}
++		}
++
++		/* Try to read from FIFO. */
++		if (mutex_lock_interruptible(&client->read_lock)) {
++			up_read(&ddev->lock);
++			return -ERESTARTSYS;
++		}
++
++		status = kfifo_to_user(&client->buffer, buf, count, &copied);
++		mutex_unlock(&client->read_lock);
++
++		if (status < 0) {
++			up_read(&ddev->lock);
++			return status;
++		}
++
++		/* We might not have gotten anything, check this here. */
++		if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
++			up_read(&ddev->lock);
++			return -EAGAIN;
++		}
++	} while (copied == 0);
++
++	up_read(&ddev->lock);
++	return copied;
++}
++
++static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
++{
++	struct sdtx_client *client = file->private_data;
++	__poll_t events = 0;
++
++	if (down_read_killable(&client->ddev->lock))
++		return -ERESTARTSYS;
++
++	if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
++		up_read(&client->ddev->lock);
++		return EPOLLHUP | EPOLLERR;
++	}
++
++	poll_wait(file, &client->ddev->waitq, pt);
++
++	if (!kfifo_is_empty(&client->buffer))
++		events |= EPOLLIN | EPOLLRDNORM;
++
++	up_read(&client->ddev->lock);
++	return events;
++}
++
++static int surface_dtx_fasync(int fd, struct file *file, int on)
++{
++	struct sdtx_client *client = file->private_data;
++
++	return fasync_helper(fd, file, on, &client->fasync);
++}
++
++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,
++	.compat_ioctl   = surface_dtx_ioctl,
++	.llseek         = no_llseek,
++};
++
++
++/* -- Event handling/forwarding. -------------------------------------------- */
++
++/*
++ * The device operation mode is not immediately updated on the EC when the
++ * base has been connected, i.e. querying the device mode inside the
++ * connection event callback yields an outdated value. Thus, we can only
++ * determine the new tablet-mode switch and device mode values after some
++ * time.
++ *
++ * These delays have been chosen by experimenting. We first delay on connect
++ * events, then check and validate the device mode against the base state and
++ * if invalid delay again by the "recheck" delay.
++ */
++#define SDTX_DEVICE_MODE_DELAY_CONNECT	msecs_to_jiffies(100)
++#define SDTX_DEVICE_MODE_DELAY_RECHECK	msecs_to_jiffies(100)
++
++struct sdtx_status_event {
++	struct sdtx_event e;
++	__u16 v;
++} __packed;
++
++struct sdtx_base_info_event {
++	struct sdtx_event e;
++	struct sdtx_base_info v;
++} __packed;
++
++union sdtx_generic_event {
++	struct sdtx_event common;
++	struct sdtx_status_event status;
++	struct sdtx_base_info_event base;
++};
++
++static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
++
++/* Must be executed with ddev->write_lock held. */
++static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt)
++{
++	const size_t len = sizeof(struct sdtx_event) + evt->length;
++	struct sdtx_client *client;
++
++	lockdep_assert_held(&ddev->write_lock);
++
++	down_read(&ddev->client_lock);
++	list_for_each_entry(client, &ddev->client_list, node) {
++		if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags))
++			continue;
++
++		if (likely(kfifo_avail(&client->buffer) >= len))
++			kfifo_in(&client->buffer, (const u8 *)evt, len);
++		else
++			dev_warn(ddev->dev, "event buffer overrun\n");
++
++		kill_fasync(&client->fasync, SIGIO, POLL_IN);
++	}
++	up_read(&ddev->client_lock);
++
++	wake_up_interruptible(&ddev->waitq);
++}
++
++static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
++{
++	struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif);
++	union sdtx_generic_event event;
++	size_t len;
++
++	/* Validate event payload length. */
++	switch (in->command_id) {
++	case SAM_EVENT_CID_DTX_CONNECTION:
++		len = 2 * sizeof(u8);
++		break;
++
++	case SAM_EVENT_CID_DTX_REQUEST:
++		len = 0;
++		break;
++
++	case SAM_EVENT_CID_DTX_CANCEL:
++		len = sizeof(u8);
++		break;
++
++	case SAM_EVENT_CID_DTX_LATCH_STATUS:
++		len = sizeof(u8);
++		break;
++
++	default:
++		return 0;
++	};
++
++	if (in->length != len) {
++		dev_err(ddev->dev,
++			"unexpected payload size for event %#04x: got %u, expected %zu\n",
++			in->command_id, in->length, len);
++		return 0;
++	}
++
++	mutex_lock(&ddev->write_lock);
++
++	/* Translate event. */
++	switch (in->command_id) {
++	case SAM_EVENT_CID_DTX_CONNECTION:
++		clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
++
++		/* If state has not changed: do not send new event. */
++		if (ddev->state.base.state == in->data[0] &&
++		    ddev->state.base.base_id == in->data[1])
++			goto out;
++
++		ddev->state.base.state = in->data[0];
++		ddev->state.base.base_id = in->data[1];
++
++		event.base.e.length = sizeof(struct sdtx_base_info);
++		event.base.e.code = SDTX_EVENT_BASE_CONNECTION;
++		event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]);
++		event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]);
++		break;
++
++	case SAM_EVENT_CID_DTX_REQUEST:
++		event.common.code = SDTX_EVENT_REQUEST;
++		event.common.length = 0;
++		break;
++
++	case SAM_EVENT_CID_DTX_CANCEL:
++		event.status.e.length = sizeof(u16);
++		event.status.e.code = SDTX_EVENT_CANCEL;
++		event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]);
++		break;
++
++	case SAM_EVENT_CID_DTX_LATCH_STATUS:
++		clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
++
++		/* If state has not changed: do not send new event. */
++		if (ddev->state.latch_status == in->data[0])
++			goto out;
++
++		ddev->state.latch_status = in->data[0];
++
++		event.status.e.length = sizeof(u16);
++		event.status.e.code = SDTX_EVENT_LATCH_STATUS;
++		event.status.v = sdtx_translate_latch_status(ddev, in->data[0]);
++		break;
++	}
++
++	sdtx_push_event(ddev, &event.common);
++
++	/* Update device mode on base connection change. */
++	if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) {
++		unsigned long delay;
++
++		delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0;
++		sdtx_update_device_mode(ddev, delay);
++	}
++
++out:
++	mutex_unlock(&ddev->write_lock);
++	return SSAM_NOTIF_HANDLED;
++}
++
++
++/* -- State update functions. ----------------------------------------------- */
++
++static bool sdtx_device_mode_invalid(u8 mode, u8 base_state)
++{
++	return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) &&
++		(mode == SDTX_DEVICE_MODE_TABLET)) ||
++	       ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) &&
++		(mode != SDTX_DEVICE_MODE_TABLET));
++}
++
++static void sdtx_device_mode_workfn(struct work_struct *work)
++{
++	struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work);
++	struct sdtx_status_event event;
++	struct ssam_bas_base_info base;
++	int status, tablet;
++	u8 mode;
++
++	/* Get operation mode. */
++	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
++	if (status) {
++		dev_err(ddev->dev, "failed to get device mode: %d\n", status);
++		return;
++	}
++
++	/* Get base info. */
++	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
++	if (status) {
++		dev_err(ddev->dev, "failed to get base info: %d\n", status);
++		return;
++	}
++
++	/*
++	 * In some cases (specifically when attaching the base), the device
++	 * mode isn't updated right away. Thus we check if the device mode
++	 * makes sense for the given base state and try again later if it
++	 * doesn't.
++	 */
++	if (sdtx_device_mode_invalid(mode, base.state)) {
++		dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
++		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
++		return;
++	}
++
++	mutex_lock(&ddev->write_lock);
++	clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
++
++	/* Avoid sending duplicate device-mode events. */
++	if (ddev->state.device_mode == mode) {
++		mutex_unlock(&ddev->write_lock);
++		return;
++	}
++
++	ddev->state.device_mode = mode;
++
++	event.e.length = sizeof(u16);
++	event.e.code = SDTX_EVENT_DEVICE_MODE;
++	event.v = mode;
++
++	sdtx_push_event(ddev, &event.e);
++
++	/* Send SW_TABLET_MODE event. */
++	tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
++	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
++	input_sync(ddev->mode_switch);
++
++	mutex_unlock(&ddev->write_lock);
++}
++
++static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay)
++{
++	schedule_delayed_work(&ddev->mode_work, delay);
++}
++
++/* Must be executed with ddev->write_lock held. */
++static void __sdtx_device_state_update_base(struct sdtx_device *ddev,
++					    struct ssam_bas_base_info info)
++{
++	struct sdtx_base_info_event event;
++
++	lockdep_assert_held(&ddev->write_lock);
++
++	/* Prevent duplicate events. */
++	if (ddev->state.base.state == info.state &&
++	    ddev->state.base.base_id == info.base_id)
++		return;
++
++	ddev->state.base = info;
++
++	event.e.length = sizeof(struct sdtx_base_info);
++	event.e.code = SDTX_EVENT_BASE_CONNECTION;
++	event.v.state = sdtx_translate_base_state(ddev, info.state);
++	event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
++
++	sdtx_push_event(ddev, &event.e);
++}
++
++/* Must be executed with ddev->write_lock held. */
++static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode)
++{
++	struct sdtx_status_event event;
++	int tablet;
++
++	/*
++	 * Note: This function must be called after updating the base state
++	 * via __sdtx_device_state_update_base(), as we rely on the updated
++	 * base state value in the validity check below.
++	 */
++
++	lockdep_assert_held(&ddev->write_lock);
++
++	if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) {
++		dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
++		sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
++		return;
++	}
++
++	/* Prevent duplicate events. */
++	if (ddev->state.device_mode == mode)
++		return;
++
++	ddev->state.device_mode = mode;
++
++	/* Send event. */
++	event.e.length = sizeof(u16);
++	event.e.code = SDTX_EVENT_DEVICE_MODE;
++	event.v = mode;
++
++	sdtx_push_event(ddev, &event.e);
++
++	/* Send SW_TABLET_MODE event. */
++	tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
++	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
++	input_sync(ddev->mode_switch);
++}
++
++/* Must be executed with ddev->write_lock held. */
++static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status)
++{
++	struct sdtx_status_event event;
++
++	lockdep_assert_held(&ddev->write_lock);
++
++	/* Prevent duplicate events. */
++	if (ddev->state.latch_status == status)
++		return;
++
++	ddev->state.latch_status = status;
++
++	event.e.length = sizeof(struct sdtx_base_info);
++	event.e.code = SDTX_EVENT_BASE_CONNECTION;
++	event.v = sdtx_translate_latch_status(ddev, status);
++
++	sdtx_push_event(ddev, &event.e);
++}
++
++static void sdtx_device_state_workfn(struct work_struct *work)
++{
++	struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work);
++	struct ssam_bas_base_info base;
++	u8 mode, latch;
++	int status;
++
++	/* Mark everything as dirty. */
++	set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
++	set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
++	set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
++
++	/*
++	 * Ensure that the state gets marked as dirty before continuing to
++	 * query it. Necessary to ensure that clear_bit() calls in
++	 * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these
++	 * bits if an event is received while updating the state here.
++	 */
++	smp_mb__after_atomic();
++
++	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
++	if (status) {
++		dev_err(ddev->dev, "failed to get base state: %d\n", status);
++		return;
++	}
++
++	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
++	if (status) {
++		dev_err(ddev->dev, "failed to get device mode: %d\n", status);
++		return;
++	}
++
++	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
++	if (status) {
++		dev_err(ddev->dev, "failed to get latch status: %d\n", status);
++		return;
++	}
++
++	mutex_lock(&ddev->write_lock);
++
++	/*
++	 * If the respective dirty-bit has been cleared, an event has been
++	 * received, updating this state. The queried state may thus be out of
++	 * date. At this point, we can safely assume that the state provided
++	 * by the event is either up to date, or we're about to receive
++	 * another event updating it.
++	 */
++
++	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags))
++		__sdtx_device_state_update_base(ddev, base);
++
++	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags))
++		__sdtx_device_state_update_mode(ddev, mode);
++
++	if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags))
++		__sdtx_device_state_update_latch(ddev, latch);
++
++	mutex_unlock(&ddev->write_lock);
++}
++
++static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay)
++{
++	schedule_delayed_work(&ddev->state_work, delay);
++}
++
++
++/* -- Common device initialization. ----------------------------------------- */
++
++static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev,
++			    struct ssam_controller *ctrl)
++{
++	int status, tablet_mode;
++
++	/* Basic initialization. */
++	kref_init(&ddev->kref);
++	init_rwsem(&ddev->lock);
++	ddev->dev = dev;
++	ddev->ctrl = ctrl;
++
++	ddev->mdev.minor = MISC_DYNAMIC_MINOR;
++	ddev->mdev.name = "surface_dtx";
++	ddev->mdev.nodename = "surface/dtx";
++	ddev->mdev.fops = &surface_dtx_fops;
++
++	ddev->notif.base.priority = 1;
++	ddev->notif.base.fn = sdtx_notifier;
++	ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
++	ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS;
++	ddev->notif.event.id.instance = 0;
++	ddev->notif.event.mask = SSAM_EVENT_MASK_NONE;
++	ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
++
++	init_waitqueue_head(&ddev->waitq);
++	mutex_init(&ddev->write_lock);
++	init_rwsem(&ddev->client_lock);
++	INIT_LIST_HEAD(&ddev->client_list);
++
++	INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn);
++	INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
++
++	/*
++	 * Get current device state. We want to guarantee that events are only
++	 * sent when state actually changes. Thus we cannot use special
++	 * "uninitialized" values, as that would cause problems when manually
++	 * querying the state in surface_dtx_pm_complete(). I.e. we would not
++	 * be able to detect state changes there if no change event has been
++	 * received between driver initialization and first device suspension.
++	 *
++	 * Note that we also need to do this before registering the event
++	 * notifier, as that may access the state values.
++	 */
++	status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base);
++	if (status)
++		return status;
++
++	status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode);
++	if (status)
++		return status;
++
++	status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status);
++	if (status)
++		return status;
++
++	/* Set up tablet mode switch. */
++	ddev->mode_switch = input_allocate_device();
++	if (!ddev->mode_switch)
++		return -ENOMEM;
++
++	ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch";
++	ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0";
++	ddev->mode_switch->id.bustype = BUS_HOST;
++	ddev->mode_switch->dev.parent = ddev->dev;
++
++	tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP);
++	input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE);
++	input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
++
++	status = input_register_device(ddev->mode_switch);
++	if (status) {
++		input_free_device(ddev->mode_switch);
++		return status;
++	}
++
++	/* Set up event notifier. */
++	status = ssam_notifier_register(ddev->ctrl, &ddev->notif);
++	if (status)
++		goto err_notif;
++
++	/* Register miscdevice. */
++	status = misc_register(&ddev->mdev);
++	if (status)
++		goto err_mdev;
++
++	/*
++	 * Update device state in case it has changed between getting the
++	 * initial mode and registering the event notifier.
++	 */
++	sdtx_update_device_state(ddev, 0);
++	return 0;
++
++err_notif:
++	ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
++	cancel_delayed_work_sync(&ddev->mode_work);
++err_mdev:
++	input_unregister_device(ddev->mode_switch);
++	return status;
++}
++
++static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl)
++{
++	struct sdtx_device *ddev;
++	int status;
++
++	ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
++	if (!ddev)
++		return ERR_PTR(-ENOMEM);
++
++	status = sdtx_device_init(ddev, dev, ctrl);
++	if (status) {
++		sdtx_device_put(ddev);
++		return ERR_PTR(status);
++	}
++
++	return ddev;
++}
++
++static void sdtx_device_destroy(struct sdtx_device *ddev)
++{
++	struct sdtx_client *client;
++
++	/*
++	 * Mark device as shut-down. Prevent new clients from being added and
++	 * new operations from being executed.
++	 */
++	set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
++
++	/* Disable notifiers, prevent new events from arriving. */
++	ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
++
++	/* Stop mode_work, prevent access to mode_switch. */
++	cancel_delayed_work_sync(&ddev->mode_work);
++
++	/* Stop state_work. */
++	cancel_delayed_work_sync(&ddev->state_work);
++
++	/* With mode_work canceled, we can unregister the mode_switch. */
++	input_unregister_device(ddev->mode_switch);
++
++	/* Wake up async clients. */
++	down_write(&ddev->client_lock);
++	list_for_each_entry(client, &ddev->client_list, node) {
++		kill_fasync(&client->fasync, SIGIO, POLL_HUP);
++	}
++	up_write(&ddev->client_lock);
++
++	/* Wake up blocking clients. */
++	wake_up_interruptible(&ddev->waitq);
++
++	/*
++	 * Wait for clients to finish their current operation. After this, the
++	 * controller and device references are guaranteed to be no longer in
++	 * use.
++	 */
++	down_write(&ddev->lock);
++	ddev->dev = NULL;
++	ddev->ctrl = NULL;
++	up_write(&ddev->lock);
++
++	/* Finally remove the misc-device. */
++	misc_deregister(&ddev->mdev);
++
++	/*
++	 * We're now guaranteed that sdtx_device_open() won't be called any
++	 * more, so we can now drop out reference.
++	 */
++	sdtx_device_put(ddev);
++}
++
++
++/* -- PM ops. --------------------------------------------------------------- */
++
++#ifdef CONFIG_PM_SLEEP
++
++static void surface_dtx_pm_complete(struct device *dev)
++{
++	struct sdtx_device *ddev = dev_get_drvdata(dev);
++
++	/*
++	 * Normally, the EC will store events while suspended (i.e. in
++	 * display-off state) and release them when resumed (i.e. transitioned
++	 * to display-on state). During hibernation, however, the EC will be
++	 * shut down and does not store events. Furthermore, events might be
++	 * dropped during prolonged suspension (it is currently unknown how
++	 * big this event buffer is and how it behaves on overruns).
++	 *
++	 * To prevent any problems, we update the device state here. We do
++	 * this delayed to ensure that any events sent by the EC directly
++	 * after resuming will be handled first. The delay below has been
++	 * chosen (experimentally), so that there should be ample time for
++	 * these events to be handled, before we check and, if necessary,
++	 * update the state.
++	 */
++	sdtx_update_device_state(ddev, msecs_to_jiffies(1000));
++}
++
++static const struct dev_pm_ops surface_dtx_pm_ops = {
++	.complete = surface_dtx_pm_complete,
++};
++
++#else /* CONFIG_PM_SLEEP */
++
++static const struct dev_pm_ops surface_dtx_pm_ops = {};
++
++#endif /* CONFIG_PM_SLEEP */
++
++
++/* -- Platform driver. ------------------------------------------------------ */
++
++static int surface_dtx_platform_probe(struct platform_device *pdev)
++{
++	struct ssam_controller *ctrl;
++	struct sdtx_device *ddev;
++
++	/* Link to EC. */
++	ctrl = ssam_client_bind(&pdev->dev);
++	if (IS_ERR(ctrl))
++		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
++
++	ddev = sdtx_device_create(&pdev->dev, ctrl);
++	if (IS_ERR(ddev))
++		return PTR_ERR(ddev);
++
++	platform_set_drvdata(pdev, ddev);
++	return 0;
++}
++
++static int surface_dtx_platform_remove(struct platform_device *pdev)
++{
++	sdtx_device_destroy(platform_get_drvdata(pdev));
++	return 0;
++}
++
++static const struct acpi_device_id surface_dtx_acpi_match[] = {
++	{ "MSHW0133", 0 },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
++
++static struct platform_driver surface_dtx_platform_driver = {
++	.probe = surface_dtx_platform_probe,
++	.remove = surface_dtx_platform_remove,
++	.driver = {
++		.name = "surface_dtx_pltf",
++		.acpi_match_table = surface_dtx_acpi_match,
++		.pm = &surface_dtx_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_platform_driver(surface_dtx_platform_driver);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h
+new file mode 100644
+index 000000000000..0833aab0d819
+--- /dev/null
++++ b/include/uapi/linux/surface_aggregator/dtx.h
+@@ -0,0 +1,146 @@
++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
++/*
++ * Surface DTX (clipboard detachment system driver) user-space interface.
++ *
++ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This
++ * device allows user-space to control the clipboard detachment process on
++ * Surface Book series devices.
++ *
++ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
++#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
++
++#include <linux/ioctl.h>
++#include <linux/types.h>
++
++/* Status/error categories */
++#define SDTX_CATEGORY_STATUS		0x0000
++#define SDTX_CATEGORY_RUNTIME_ERROR	0x1000
++#define SDTX_CATEGORY_HARDWARE_ERROR	0x2000
++#define SDTX_CATEGORY_UNKNOWN		0xf000
++
++#define SDTX_CATEGORY_MASK		0xf000
++#define SDTX_CATEGORY(value)		((value) & SDTX_CATEGORY_MASK)
++
++#define SDTX_STATUS(code)		((code) | SDTX_CATEGORY_STATUS)
++#define SDTX_ERR_RT(code)		((code) | SDTX_CATEGORY_RUNTIME_ERROR)
++#define SDTX_ERR_HW(code)		((code) | SDTX_CATEGORY_HARDWARE_ERROR)
++#define SDTX_UNKNOWN(code)		((code) | SDTX_CATEGORY_UNKNOWN)
++
++#define SDTX_SUCCESS(value)		(SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS)
++
++/* Latch status values */
++#define SDTX_LATCH_CLOSED		SDTX_STATUS(0x00)
++#define SDTX_LATCH_OPENED		SDTX_STATUS(0x01)
++
++/* Base state values */
++#define SDTX_BASE_DETACHED		SDTX_STATUS(0x00)
++#define SDTX_BASE_ATTACHED		SDTX_STATUS(0x01)
++
++/* Runtime errors (non-critical) */
++#define SDTX_DETACH_NOT_FEASIBLE	SDTX_ERR_RT(0x01)
++#define SDTX_DETACH_TIMEDOUT		SDTX_ERR_RT(0x02)
++
++/* Hardware errors (critical) */
++#define SDTX_ERR_FAILED_TO_OPEN		SDTX_ERR_HW(0x01)
++#define SDTX_ERR_FAILED_TO_REMAIN_OPEN	SDTX_ERR_HW(0x02)
++#define SDTX_ERR_FAILED_TO_CLOSE	SDTX_ERR_HW(0x03)
++
++/* Base types */
++#define SDTX_DEVICE_TYPE_HID		0x0100
++#define SDTX_DEVICE_TYPE_SSH		0x0200
++
++#define SDTX_DEVICE_TYPE_MASK		0x0f00
++#define SDTX_DEVICE_TYPE(value)		((value) & SDTX_DEVICE_TYPE_MASK)
++
++#define SDTX_BASE_TYPE_HID(id)		((id) | SDTX_DEVICE_TYPE_HID)
++#define SDTX_BASE_TYPE_SSH(id)		((id) | SDTX_DEVICE_TYPE_SSH)
++
++/**
++ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is
++ * attached to the base of the device.
++ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the
++ *                           device operates as tablet.
++ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base
++ *                           and the device operates as laptop.
++ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse.
++ *                           The device operates as tablet with keyboard and
++ *                           touchpad deactivated, however, the base battery
++ *                           and, if present in the specific device model, dGPU
++ *                           are available to the system.
++ */
++enum sdtx_device_mode {
++	SDTX_DEVICE_MODE_TABLET		= 0x00,
++	SDTX_DEVICE_MODE_LAPTOP		= 0x01,
++	SDTX_DEVICE_MODE_STUDIO		= 0x02,
++};
++
++/**
++ * struct sdtx_event - Event provided by reading from the DTX device file.
++ * @length: Length of the event payload, in bytes.
++ * @code:   Event code, detailing what type of event this is.
++ * @data:   Payload of the event, containing @length bytes.
++ *
++ * See &enum sdtx_event_code for currently valid event codes.
++ */
++struct sdtx_event {
++	__u16 length;
++	__u16 code;
++	__u8 data[];
++} __attribute__((__packed__));
++
++/**
++ * enum sdtx_event_code - Code describing the type of an event.
++ * @SDTX_EVENT_REQUEST:         Detachment request event type.
++ * @SDTX_EVENT_CANCEL:          Cancel detachment process event type.
++ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type.
++ * @SDTX_EVENT_LATCH_STATUS:    Latch status change event type.
++ * @SDTX_EVENT_DEVICE_MODE:     Device mode change event type.
++ *
++ * Used in &struct sdtx_event to describe the type of the event. Further event
++ * codes are reserved for future use. Any event parser should be able to
++ * gracefully handle unknown events, i.e. by simply skipping them.
++ *
++ * Consult the DTX user-space interface documentation for details regarding
++ * the individual event types.
++ */
++enum sdtx_event_code {
++	SDTX_EVENT_REQUEST		= 1,
++	SDTX_EVENT_CANCEL		= 2,
++	SDTX_EVENT_BASE_CONNECTION	= 3,
++	SDTX_EVENT_LATCH_STATUS		= 4,
++	SDTX_EVENT_DEVICE_MODE		= 5,
++};
++
++/**
++ * struct sdtx_base_info - Describes if and what type of base is connected.
++ * @state:   The state of the connection. Valid values are %SDTX_BASE_DETACHED,
++ *           %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base
++ *           is attached but low clipboard battery prevents detachment). Other
++ *           values are currently reserved.
++ * @base_id: The type of base connected. Zero if no base is connected.
++ */
++struct sdtx_base_info {
++	__u16 state;
++	__u16 base_id;
++} __attribute__((__packed__));
++
++/* IOCTLs */
++#define SDTX_IOCTL_EVENTS_ENABLE	_IO(0xa5, 0x21)
++#define SDTX_IOCTL_EVENTS_DISABLE	_IO(0xa5, 0x22)
++
++#define SDTX_IOCTL_LATCH_LOCK		_IO(0xa5, 0x23)
++#define SDTX_IOCTL_LATCH_UNLOCK		_IO(0xa5, 0x24)
++
++#define SDTX_IOCTL_LATCH_REQUEST	_IO(0xa5, 0x25)
++#define SDTX_IOCTL_LATCH_CONFIRM	_IO(0xa5, 0x26)
++#define SDTX_IOCTL_LATCH_HEARTBEAT	_IO(0xa5, 0x27)
++#define SDTX_IOCTL_LATCH_CANCEL		_IO(0xa5, 0x28)
++
++#define SDTX_IOCTL_GET_BASE_INFO	_IOR(0xa5, 0x29, struct sdtx_base_info)
++#define SDTX_IOCTL_GET_DEVICE_MODE	_IOR(0xa5, 0x2a, __u16)
++#define SDTX_IOCTL_GET_LATCH_STATUS	_IOR(0xa5, 0x2b, __u16)
++
++#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */
+-- 
+2.31.1
+
+From 5f90d1d6214b7f4a0372dadbfe614ec12ae13425 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Mon, 8 Mar 2021 19:48:18 +0100
+Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices
+
+Add support for native SSAM devices to the DTX driver. This allows
+support for the Surface Book 3, on which the DTX device is not present
+in ACPI.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/Kconfig       |  4 ++
+ drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++-
+ 2 files changed, 93 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
+index 41d67bb250fd..53beaedefdd1 100644
+--- a/drivers/platform/surface/Kconfig
++++ b/drivers/platform/surface/Kconfig
+@@ -127,6 +127,10 @@ config SURFACE_DTX
+ 	  behavior of this process, which includes the option to abort it in
+ 	  case the base is still in use or speed it up in case it is not.
+ 
++	  Note that this module can be built without support for the Surface
++	  Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case,
++	  some devices, specifically the Surface Book 3, will not be supported.
++
+ config SURFACE_GPE
+ 	tristate "Surface GPE/Lid Support Driver"
+ 	depends on DMI
+diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
+index 1301fab0ea14..85451eb94d98 100644
+--- a/drivers/platform/surface/surface_dtx.c
++++ b/drivers/platform/surface/surface_dtx.c
+@@ -27,6 +27,7 @@
+ #include <linux/workqueue.h>
+ 
+ #include <linux/surface_aggregator/controller.h>
++#include <linux/surface_aggregator/device.h>
+ #include <linux/surface_aggregator/dtx.h>
+ 
+ 
+@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = {
+ 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ 	},
+ };
+-module_platform_driver(surface_dtx_platform_driver);
++
++
++/* -- SSAM device driver. --------------------------------------------------- */
++
++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
++
++static int surface_dtx_ssam_probe(struct ssam_device *sdev)
++{
++	struct sdtx_device *ddev;
++
++	ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
++	if (IS_ERR(ddev))
++		return PTR_ERR(ddev);
++
++	ssam_device_set_drvdata(sdev, ddev);
++	return 0;
++}
++
++static void surface_dtx_ssam_remove(struct ssam_device *sdev)
++{
++	sdtx_device_destroy(ssam_device_get_drvdata(sdev));
++}
++
++static const struct ssam_device_id surface_dtx_ssam_match[] = {
++	{ SSAM_SDEV(BAS, 0x01, 0x00, 0x00) },
++	{ },
++};
++MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
++
++static struct ssam_device_driver surface_dtx_ssam_driver = {
++	.probe = surface_dtx_ssam_probe,
++	.remove = surface_dtx_ssam_remove,
++	.match_table = surface_dtx_ssam_match,
++	.driver = {
++		.name = "surface_dtx",
++		.pm = &surface_dtx_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++
++static int ssam_dtx_driver_register(void)
++{
++	return ssam_device_driver_register(&surface_dtx_ssam_driver);
++}
++
++static void ssam_dtx_driver_unregister(void)
++{
++	ssam_device_driver_unregister(&surface_dtx_ssam_driver);
++}
++
++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
++
++static int ssam_dtx_driver_register(void)
++{
++	return 0;
++}
++
++static void ssam_dtx_driver_unregister(void)
++{
++}
++
++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
++
++
++/* -- Module setup. --------------------------------------------------------- */
++
++static int __init surface_dtx_init(void)
++{
++	int status;
++
++	status = ssam_dtx_driver_register();
++	if (status)
++		return status;
++
++	status = platform_driver_register(&surface_dtx_platform_driver);
++	if (status)
++		ssam_dtx_driver_unregister();
++
++	return status;
++}
++module_init(surface_dtx_init);
++
++static void __exit surface_dtx_exit(void)
++{
++	platform_driver_unregister(&surface_dtx_platform_driver);
++	ssam_dtx_driver_unregister();
++}
++module_exit(surface_dtx_exit);
+ 
+ MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
+ MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
+-- 
+2.31.1
+
+From 942c6392c91293171d960506a8d0a65896bc4805 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Mon, 8 Mar 2021 19:48:19 +0100
+Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation
+
+Add documentation for the user-space interface of the Surface DTX
+(detachment system) driver, used on Microsoft Surface Book series
+devices.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface_aggregator/clients/dtx.rst        | 718 ++++++++++++++++++
+ .../surface_aggregator/clients/index.rst      |   1 +
+ MAINTAINERS                                   |   1 +
+ 3 files changed, 720 insertions(+)
+ create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst
+
+diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
+new file mode 100644
+index 000000000000..e7e7c20007f0
+--- /dev/null
++++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
+@@ -0,0 +1,718 @@
++.. SPDX-License-Identifier: GPL-2.0+
++
++.. |__u16| replace:: :c:type:`__u16 <__u16>`
++.. |sdtx_event| replace:: :c:type:`struct sdtx_event <sdtx_event>`
++.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code <sdtx_event_code>`
++.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info <sdtx_base_info>`
++.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode <sdtx_device_mode>`
++
++======================================================
++User-Space DTX (Clipboard Detachment System) Interface
++======================================================
++
++The ``surface_dtx`` driver is responsible for proper clipboard detachment
++and re-attachment handling. To this end, it provides the ``/dev/surface/dtx``
++device file, through which it can interface with a user-space daemon. This
++daemon is then ultimately responsible for determining and taking necessary
++actions, such as unmounting devices attached to the base,
++unloading/reloading the graphics-driver, user-notifications, etc.
++
++There are two basic communication principles used in this driver: Commands
++(in other parts of the documentation also referred to as requests) and
++events. Commands are sent to the EC and may have a different implications in
++different contexts. Events are sent by the EC upon some internal state
++change. Commands are always driver-initiated, whereas events are always
++initiated by the EC.
++
++.. contents::
++
++Nomenclature
++============
++
++* **Clipboard:**
++  The detachable upper part of the Surface Book, housing the screen and CPU.
++
++* **Base:**
++  The lower part of the Surface Book from which the clipboard can be
++  detached, optionally (model dependent) housing the discrete GPU (dGPU).
++
++* **Latch:**
++  The mechanism keeping the clipboard attached to the base in normal
++  operation and allowing it to be detached when requested.
++
++* **Silently ignored commands:**
++  The command is accepted by the EC as a valid command and acknowledged
++  (following the standard communication protocol), but the EC does not act
++  upon it, i.e. ignores it.e upper part of the
++
++
++Detachment Process
++==================
++
++Warning: This part of the documentation is based on reverse engineering and
++testing and thus may contain errors or be incomplete.
++
++Latch States
++------------
++
++The latch mechanism has two major states: *open* and *closed*. In the
++*closed* state (default), the clipboard is secured to the base, whereas in
++the *open* state, the clipboard can be removed by a user.
++
++The latch can additionally be locked and, correspondingly, unlocked, which
++can influence the detachment procedure. Specifically, this locking mechanism
++is intended to prevent the dGPU, positioned in the base of the device, from
++being hot-unplugged while in use. More details can be found in the
++documentation for the detachment procedure below. By default, the latch is
++unlocked.
++
++Detachment Procedure
++--------------------
++
++Note that the detachment process is governed fully by the EC. The
++``surface_dtx`` driver only relays events from the EC to user-space and
++commands from user-space to the EC, i.e. it does not influence this process.
++
++The detachment process is started with the user pressing the *detach* button
++on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL.
++Following that:
++
++1. The EC turns on the indicator led on the detach-button, sends a
++   *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further
++   instructions/commands. In case the latch is unlocked, the led will flash
++   green. If the latch has been locked, the led will be solid red
++
++2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where
++   an appropriate user-space daemon can handle it and send instructions back
++   to the EC via IOCTLs provided by this driver.
++
++3. The EC waits for instructions from user-space and acts according to them.
++   If the EC does not receive any instructions in a given period, it will
++   time out and continue as follows:
++
++   - If the latch is unlocked, the EC will open the latch and the clipboard
++     can be detached from the base. This is the exact behavior as without
++     this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM``
++     description below for more details on the follow-up behavior of the EC.
++
++   - If the latch is locked, the EC will *not* open the latch, meaning the
++     clipboard cannot be detached from the base. Furthermore, the EC sends
++     an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel
++     reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details).
++
++Valid responses by a user-space daemon to a detachment request event are:
++
++- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the
++  detachment process. Furthermore, the EC will send a detach-request event,
++  similar to the user pressing the detach-button to cancel said process (see
++  below).
++
++- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the
++  latch, after which the user can separate clipboard and base.
++
++  As this changes the latch state, a *latch-status* event
++  (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened
++  successfully. If the EC fails to open the latch, e.g. due to hardware
++  error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be
++  sent with the cancel reason indicating the specific failure.
++
++  If the latch is currently locked, the latch will automatically be
++  unlocked before it is opened.
++
++- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout.
++  No other actions will be performed, i.e. the detachment process will neither
++  be completed nor canceled, and the EC will still be waiting for further
++  responses.
++
++- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process,
++  similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button
++  press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``)
++  is send in response to this. In contrast to those, however, this command
++  does not trigger a new detachment process if none is currently in
++  progress.
++
++- Do nothing. The detachment process eventually times out as described in
++  point 3.
++
++See :ref:`ioctls` for more details on these responses.
++
++It is important to note that, if the user presses the detach button at any
++point when a detachment operation is in progress (i.e. after the EC has sent
++the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it
++received the corresponding response concluding the process), the detachment
++process is canceled on the EC-level and an identical event is being sent.
++Thus a *detach-request* event, by itself, does not signal the start of the
++detachment process.
++
++The detachment process may further be canceled by the EC due to hardware
++failures or a low clipboard battery. This is done via a cancel event
++(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason.
++
++
++User-Space Interface Documentation
++==================================
++
++Error Codes and Status Values
++-----------------------------
++
++Error and status codes are divided into different categories, which can be
++used to determine if the status code is an error, and, if it is, the
++severity and type of that error. The current categories are:
++
++.. flat-table:: Overview of Status/Error Categories.
++   :widths: 2 1 3
++   :header-rows: 1
++
++   * - Name
++     - Value
++     - Short Description
++
++   * - ``STATUS``
++     - ``0x0000``
++     - Non-error status codes.
++
++   * - ``RUNTIME_ERROR``
++     - ``0x1000``
++     - Non-critical runtime errors.
++
++   * - ``HARDWARE_ERROR``
++     - ``0x2000``
++     - Critical hardware failures.
++
++   * - ``UNKNOWN``
++     - ``0xF000``
++     - Unknown error codes.
++
++Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro
++can be used to determine the category of any status value. The
++``SDTX_SUCCESS()`` macro can be used to check if the status value is a
++success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure.
++
++Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN``
++category by the driver and may be implemented via their own code in the
++future.
++
++Currently used error codes are:
++
++.. flat-table:: Overview of Error Codes.
++   :widths: 2 1 1 3
++   :header-rows: 1
++
++   * - Name
++     - Category
++     - Value
++     - Short Description
++
++   * - ``SDTX_DETACH_NOT_FEASIBLE``
++     - ``RUNTIME``
++     - ``0x1001``
++     - Detachment not feasible due to low clipboard battery.
++
++   * - ``SDTX_DETACH_TIMEDOUT``
++     - ``RUNTIME``
++     - ``0x1002``
++     - Detachment process timed out while the latch was locked.
++
++   * - ``SDTX_ERR_FAILED_TO_OPEN``
++     - ``HARDWARE``
++     - ``0x2001``
++     - Failed to open latch.
++
++   * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``
++     - ``HARDWARE``
++     - ``0x2002``
++     - Failed to keep latch open.
++
++   * - ``SDTX_ERR_FAILED_TO_CLOSE``
++     - ``HARDWARE``
++     - ``0x2003``
++     - Failed to close latch.
++
++Other error codes are reserved for future use. Non-error status codes may
++overlap and are generally only unique within their use-case:
++
++.. flat-table:: Latch Status Codes.
++   :widths: 2 1 1 3
++   :header-rows: 1
++
++   * - Name
++     - Category
++     - Value
++     - Short Description
++
++   * - ``SDTX_LATCH_CLOSED``
++     - ``STATUS``
++     - ``0x0000``
++     - Latch is closed/has been closed.
++
++   * - ``SDTX_LATCH_OPENED``
++     - ``STATUS``
++     - ``0x0001``
++     - Latch is open/has been opened.
++
++.. flat-table:: Base State Codes.
++   :widths: 2 1 1 3
++   :header-rows: 1
++
++   * - Name
++     - Category
++     - Value
++     - Short Description
++
++   * - ``SDTX_BASE_DETACHED``
++     - ``STATUS``
++     - ``0x0000``
++     - Base has been detached/is not present.
++
++   * - ``SDTX_BASE_ATTACHED``
++     - ``STATUS``
++     - ``0x0001``
++     - Base has been attached/is present.
++
++Again, other codes are reserved for future use.
++
++.. _events:
++
++Events
++------
++
++Events can be received by reading from the device file. They are disabled by
++default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE``
++first. All events follow the layout prescribed by |sdtx_event|. Specific
++event types can be identified by their event code, described in
++|sdtx_event_code|. Note that other event codes are reserved for future use,
++thus an event parser must be able to handle any unknown/unsupported event
++types gracefully, by relying on the payload length given in the event header.
++
++Currently provided event types are:
++
++.. flat-table:: Overview of DTX events.
++   :widths: 2 1 1 3
++   :header-rows: 1
++
++   * - Name
++     - Code
++     - Payload
++     - Short Description
++
++   * - ``SDTX_EVENT_REQUEST``
++     - ``1``
++     - ``0`` bytes
++     - Detachment process initiated/aborted.
++
++   * - ``SDTX_EVENT_CANCEL``
++     - ``2``
++     - ``2`` bytes
++     - EC canceled detachment process.
++
++   * - ``SDTX_EVENT_BASE_CONNECTION``
++     - ``3``
++     - ``4`` bytes
++     - Base connection state changed.
++
++   * - ``SDTX_EVENT_LATCH_STATUS``
++     - ``4``
++     - ``2`` bytes
++     - Latch status changed.
++
++   * - ``SDTX_EVENT_DEVICE_MODE``
++     - ``5``
++     - ``2`` bytes
++     - Device mode changed.
++
++Individual events in more detail:
++
++``SDTX_EVENT_REQUEST``
++^^^^^^^^^^^^^^^^^^^^^^
++
++Sent when a detachment process is started or, if in progress, aborted by the
++user, either via a detach button press or a detach request
++(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space.
++
++Does not have any payload.
++
++``SDTX_EVENT_CANCEL``
++^^^^^^^^^^^^^^^^^^^^^
++
++Sent when a detachment process is canceled by the EC due to unfulfilled
++preconditions (e.g. clipboard battery too low to detach) or hardware
++failure. The reason for cancellation is given in the event payload detailed
++below and can be one of
++
++* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked.
++  The latch has neither been opened nor unlocked.
++
++* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard
++  battery.
++
++* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure).
++
++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware
++  failure).
++
++* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure).
++
++Other error codes in this context are reserved for future use.
++
++These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern
++between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or
++runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may
++happen during normal operation if certain preconditions for detachment are
++not given.
++
++.. flat-table:: Detachment Cancel Event Payload
++   :widths: 1 1 4
++   :header-rows: 1
++
++   * - Field
++     - Type
++     - Description
++
++   * - ``reason``
++     - |__u16|
++     - Reason for cancellation.
++
++``SDTX_EVENT_BASE_CONNECTION``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Sent when the base connection state has changed, i.e. when the base has been
++attached, detached, or detachment has become infeasible due to low clipboard
++battery. The new state and, if a base is connected, ID of the base is
++provided as payload of type |sdtx_base_info| with its layout presented
++below:
++
++.. flat-table:: Base-Connection-Change Event Payload
++   :widths: 1 1 4
++   :header-rows: 1
++
++   * - Field
++     - Type
++     - Description
++
++   * - ``state``
++     - |__u16|
++     - Base connection state.
++
++   * - ``base_id``
++     - |__u16|
++     - Type of base connected (zero if none).
++
++Possible values for ``state`` are:
++
++* ``SDTX_BASE_DETACHED``,
++* ``SDTX_BASE_ATTACHED``, and
++* ``SDTX_DETACH_NOT_FEASIBLE``.
++
++Other values are reserved for future use.
++
++``SDTX_EVENT_LATCH_STATUS``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Sent when the latch status has changed, i.e. when the latch has been opened,
++closed, or an error occurred. The current status is provided as payload:
++
++.. flat-table:: Latch-Status-Change Event Payload
++   :widths: 1 1 4
++   :header-rows: 1
++
++   * - Field
++     - Type
++     - Description
++
++   * - ``status``
++     - |__u16|
++     - Latch status.
++
++Possible values for ``status`` are:
++
++* ``SDTX_LATCH_CLOSED``,
++* ``SDTX_LATCH_OPENED``,
++* ``SDTX_ERR_FAILED_TO_OPEN``,
++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
++* ``SDTX_ERR_FAILED_TO_CLOSE``.
++
++Other values are reserved for future use.
++
++``SDTX_EVENT_DEVICE_MODE``
++^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Sent when the device mode has changed. The new device mode is provided as
++payload:
++
++.. flat-table:: Device-Mode-Change Event Payload
++   :widths: 1 1 4
++   :header-rows: 1
++
++   * - Field
++     - Type
++     - Description
++
++   * - ``mode``
++     - |__u16|
++     - Device operation mode.
++
++Possible values for ``mode`` are:
++
++* ``SDTX_DEVICE_MODE_TABLET``,
++* ``SDTX_DEVICE_MODE_LAPTOP``, and
++* ``SDTX_DEVICE_MODE_STUDIO``.
++
++Other values are reserved for future use.
++
++.. _ioctls:
++
++IOCTLs
++------
++
++The following IOCTLs are provided:
++
++.. flat-table:: Overview of DTX IOCTLs
++   :widths: 1 1 1 1 4
++   :header-rows: 1
++
++   * - Type
++     - Number
++     - Direction
++     - Name
++     - Description
++
++   * - ``0xA5``
++     - ``0x21``
++     - ``-``
++     - ``EVENTS_ENABLE``
++     - Enable events for the current file descriptor.
++
++   * - ``0xA5``
++     - ``0x22``
++     - ``-``
++     - ``EVENTS_DISABLE``
++     - Disable events for the current file descriptor.
++
++   * - ``0xA5``
++     - ``0x23``
++     - ``-``
++     - ``LATCH_LOCK``
++     - Lock the latch.
++
++   * - ``0xA5``
++     - ``0x24``
++     - ``-``
++     - ``LATCH_UNLOCK``
++     - Unlock the latch.
++
++   * - ``0xA5``
++     - ``0x25``
++     - ``-``
++     - ``LATCH_REQUEST``
++     - Request clipboard detachment.
++
++   * - ``0xA5``
++     - ``0x26``
++     - ``-``
++     - ``LATCH_CONFIRM``
++     - Confirm clipboard detachment request.
++
++   * - ``0xA5``
++     - ``0x27``
++     - ``-``
++     - ``LATCH_HEARTBEAT``
++     - Send heartbeat signal to EC.
++
++   * - ``0xA5``
++     - ``0x28``
++     - ``-``
++     - ``LATCH_CANCEL``
++     - Cancel detachment process.
++
++   * - ``0xA5``
++     - ``0x29``
++     - ``R``
++     - ``GET_BASE_INFO``
++     - Get current base/connection information.
++
++   * - ``0xA5``
++     - ``0x2A``
++     - ``R``
++     - ``GET_DEVICE_MODE``
++     - Get current device operation mode.
++
++   * - ``0xA5``
++     - ``0x2B``
++     - ``R``
++     - ``GET_LATCH_STATUS``
++     - Get current device latch status.
++
++``SDTX_IOCTL_EVENTS_ENABLE``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x22)``.
++
++Enable events for the current file descriptor. Events can be obtained by
++reading from the device, if enabled. Events are disabled by default.
++
++``SDTX_IOCTL_EVENTS_DISABLE``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x22)``.
++
++Disable events for the current file descriptor. Events can be obtained by
++reading from the device, if enabled. Events are disabled by default.
++
++``SDTX_IOCTL_LATCH_LOCK``
++^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x23)``.
++
++Locks the latch, causing the detachment procedure to abort without opening
++the latch on timeout. The latch is unlocked by default. This command will be
++silently ignored if the latch is already locked.
++
++``SDTX_IOCTL_LATCH_UNLOCK``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x24)``.
++
++Unlocks the latch, causing the detachment procedure to open the latch on
++timeout. The latch is unlocked by default. This command will not open the
++latch when sent during an ongoing detachment process. It will be silently
++ignored if the latch is already unlocked.
++
++``SDTX_IOCTL_LATCH_REQUEST``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x25)``.
++
++Generic latch request. Behavior depends on the context: If no
++detachment-process is active, detachment is requested. Otherwise the
++currently active detachment-process will be aborted.
++
++If a detachment process is canceled by this operation, a generic detachment
++request event (``SDTX_EVENT_REQUEST``) will be sent.
++
++This essentially behaves the same as a detachment button press.
++
++``SDTX_IOCTL_LATCH_CONFIRM``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x26)``.
++
++Acknowledges and confirms a latch request. If sent during an ongoing
++detachment process, this command causes the latch to be opened immediately.
++The latch will also be opened if it has been locked. In this case, the latch
++lock is reset to the unlocked state.
++
++This command will be silently ignored if there is currently no detachment
++procedure in progress.
++
++``SDTX_IOCTL_LATCH_HEARTBEAT``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x27)``.
++
++Sends a heartbeat, essentially resetting the detachment timeout. This
++command can be used to keep the detachment process alive while work required
++for the detachment to succeed is still in progress.
++
++This command will be silently ignored if there is currently no detachment
++procedure in progress.
++
++``SDTX_IOCTL_LATCH_CANCEL``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IO(0xA5, 0x28)``.
++
++Cancels detachment in progress (if any). If a detachment process is canceled
++by this operation, a generic detachment request event
++(``SDTX_EVENT_REQUEST``) will be sent.
++
++This command will be silently ignored if there is currently no detachment
++procedure in progress.
++
++``SDTX_IOCTL_GET_BASE_INFO``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``.
++
++Get the current base connection state (i.e. attached/detached) and the type
++of the base connected to the clipboard. This is command essentially provides
++a way to query the information provided by the base connection change event
++(``SDTX_EVENT_BASE_CONNECTION``).
++
++Possible values for ``struct sdtx_base_info.state`` are:
++
++* ``SDTX_BASE_DETACHED``,
++* ``SDTX_BASE_ATTACHED``, and
++* ``SDTX_DETACH_NOT_FEASIBLE``.
++
++Other values are reserved for future use.
++
++``SDTX_IOCTL_GET_DEVICE_MODE``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IOR(0xA5, 0x2A, __u16)``.
++
++Returns the device operation mode, indicating if and how the base is
++attached to the clipboard. This is command essentially provides a way to
++query the information provided by the device mode change event
++(``SDTX_EVENT_DEVICE_MODE``).
++
++Returned values are:
++
++* ``SDTX_DEVICE_MODE_LAPTOP``
++* ``SDTX_DEVICE_MODE_TABLET``
++* ``SDTX_DEVICE_MODE_STUDIO``
++
++See |sdtx_device_mode| for details. Other values are reserved for future
++use.
++
++
++``SDTX_IOCTL_GET_LATCH_STATUS``
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Defined as ``_IOR(0xA5, 0x2B, __u16)``.
++
++Get the current latch status or (presumably) the last error encountered when
++trying to open/close the latch. This is command essentially provides a way
++to query the information provided by the latch status change event
++(``SDTX_EVENT_LATCH_STATUS``).
++
++Returned values are:
++
++* ``SDTX_LATCH_CLOSED``,
++* ``SDTX_LATCH_OPENED``,
++* ``SDTX_ERR_FAILED_TO_OPEN``,
++* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
++* ``SDTX_ERR_FAILED_TO_CLOSE``.
++
++Other values are reserved for future use.
++
++A Note on Base IDs
++------------------
++
++Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or
++``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower
++byte of the combined |__u16| value, with the driver storing the EC type from
++which this ID comes in the high byte (without this, base IDs over different
++types of ECs may be overlapping).
++
++The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device
++type. This can be one of
++
++* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and
++
++* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial
++  Hub.
++
++Note that currently only the ``SSH`` type EC is supported, however ``HID``
++type is reserved for future use.
++
++Structures and Enums
++--------------------
++
++.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h
++
++API Users
++=========
++
++A user-space daemon utilizing this API can be found at
++https://github.com/linux-surface/surface-dtx-daemon.
+diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst
+index 3ccabce23271..98ea9946b8a2 100644
+--- a/Documentation/driver-api/surface_aggregator/clients/index.rst
++++ b/Documentation/driver-api/surface_aggregator/clients/index.rst
+@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to
+    :maxdepth: 1
+ 
+    cdev
++   dtx
+    san
+ 
+ .. only::  subproject and html
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 3917e7363520..da1487d672a8 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER
+ M:	Maximilian Luz <luzmaximilian@gmail.com>
+ L:	platform-driver-x86@vger.kernel.org
+ S:	Maintained
++F:	Documentation/driver-api/surface_aggregator/clients/dtx.rst
+ F:	drivers/platform/surface/surface_dtx.c
+ F:	include/uapi/linux/surface_aggregator/dtx.h
+ 
+-- 
+2.31.1
+
+From d3739c04cbefe7605af24308c13d0a690203af6a Mon Sep 17 00:00:00 2001
+From: Wei Yongjun <weiyongjun1@huawei.com>
+Date: Tue, 9 Mar 2021 13:15:00 +0000
+Subject: [PATCH] platform/surface: aggregator_registry: Make symbol
+ 'ssam_base_hub_group' static
+
+The sparse tool complains as follows:
+
+drivers/platform/surface/surface_aggregator_registry.c:355:30: warning:
+ symbol 'ssam_base_hub_group' was not declared. Should it be static?
+
+This symbol is not used outside of surface_aggregator_registry.c, so this
+commit marks it static.
+
+Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub")
+Reported-by: Hulk Robot <hulkci@huawei.com>
+Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
+Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/surface_aggregator_registry.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index cdb4a95af3e8..86cff5fce3cd 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = {
+ 	NULL,
+ };
+ 
+-const struct attribute_group ssam_base_hub_group = {
++static const struct attribute_group ssam_base_hub_group = {
+ 	.attrs = ssam_base_hub_attrs,
+ };
+ 
+-- 
+2.31.1
+
+From 3b80ee5034052a82df2fb0b1370a2f8511f3bc1f Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Tue, 9 Mar 2021 17:25:50 +0100
+Subject: [PATCH] platform/surface: aggregator_registry: Add support for
+ Surface Pro 7+
+
+The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with
+updated hardware and a new WSID identifier.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/surface_aggregator_registry.c | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index 86cff5fce3cd..eccb9d1007cd 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = {
+ 	NULL,
+ };
+ 
+-/* Devices for Surface Pro 7. */
++/* Devices for Surface Pro 7 and Surface Pro 7+. */
+ static const struct software_node *ssam_node_group_sp7[] = {
+ 	&ssam_node_root,
+ 	&ssam_node_bat_ac,
+@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
+ 	/* Surface Pro 7 */
+ 	{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
+ 
++	/* Surface Pro 7+ */
++	{ "MSHW0119", (unsigned long)ssam_node_group_sp7 },
++
+ 	/* Surface Book 2 */
+ 	{ "MSHW0107", (unsigned long)ssam_node_group_sb2 },
+ 
+-- 
+2.31.1
+
+From e7d4cc3e3f1abba7d83303c452e9c718ab0e2e4c Mon Sep 17 00:00:00 2001
+From: kernel test robot <lkp@intel.com>
+Date: Fri, 19 Mar 2021 13:19:19 +0800
+Subject: [PATCH] platform/surface: fix semicolon.cocci warnings
+
+drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon
+
+ Remove unneeded semicolon.
+
+Generated by: scripts/coccinelle/misc/semicolon.cocci
+
+Fixes: 1d609992832e ("platform/surface: Add DTX driver")
+CC: Maximilian Luz <luzmaximilian@gmail.com>
+Reported-by: kernel test robot <lkp@intel.com>
+Signed-off-by: kernel test robot <lkp@intel.com>
+Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/surface_dtx.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
+index 85451eb94d98..1fedacf74050 100644
+--- a/drivers/platform/surface/surface_dtx.c
++++ b/drivers/platform/surface/surface_dtx.c
+@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event
+ 
+ 	default:
+ 		return 0;
+-	};
++	}
+ 
+ 	if (in->length != len) {
+ 		dev_err(ddev->dev,
+-- 
+2.31.1
+
+From 2eeddec425f7ea847bd1dc9ff9baf1a112814cfe Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <dan.carpenter@oracle.com>
+Date: Fri, 26 Mar 2021 15:28:48 +0300
+Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read()
+
+The "&client->ddev->lock" and "&ddev->lock" are the same thing.  Let's
+use "&ddev->lock" consistently.
+
+Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
+Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/surface_dtx.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
+index 1fedacf74050..63ce587e79e3 100644
+--- a/drivers/platform/surface/surface_dtx.c
++++ b/drivers/platform/surface/surface_dtx.c
+@@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun
+ 			if (status < 0)
+ 				return status;
+ 
+-			if (down_read_killable(&client->ddev->lock))
++			if (down_read_killable(&ddev->lock))
+ 				return -ERESTARTSYS;
+ 
+ 			/* Need to check that we're not shut down again. */
+-- 
+2.31.1
+
+From b37d3dc077939e99cf0950a6a3009764531bcc15 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Tue, 6 Apr 2021 01:12:22 +0200
+Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to
+ set up when connecting
+
+Sometimes, the "base connected" event that we rely on to (re-)attach the
+device connected to the base is sent a bit too early. When this happens,
+some devices may not be completely ready yet.
+
+Specifically, the battery has been observed to report zero-values for
+things like full charge capacity, which, however, is only loaded once
+when the driver for that device probes. This can thus result in battery
+readings being unavailable.
+
+As we cannot easily and reliably discern between devices that are not
+ready yet and devices that are not connected (i.e. will never be ready),
+delay adding these devices. This should give them enough time to set up.
+
+The delay is set to 2.5 seconds, which should give us a good safety
+margin based on testing and still be fairly responsive for users.
+
+To achieve that delay switch to updating via a delayed work struct,
+which means that we can also get rid of some locking.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ .../surface/surface_aggregator_registry.c     | 98 ++++++++-----------
+ 1 file changed, 40 insertions(+), 58 deletions(-)
+
+diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
+index eccb9d1007cd..685d37a7add1 100644
+--- a/drivers/platform/surface/surface_aggregator_registry.c
++++ b/drivers/platform/surface/surface_aggregator_registry.c
+@@ -13,10 +13,10 @@
+ #include <linux/kernel.h>
+ #include <linux/limits.h>
+ #include <linux/module.h>
+-#include <linux/mutex.h>
+ #include <linux/platform_device.h>
+ #include <linux/property.h>
+ #include <linux/types.h>
++#include <linux/workqueue.h>
+ 
+ #include <linux/surface_aggregator/controller.h>
+ #include <linux/surface_aggregator/device.h>
+@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
+ 
+ /* -- SSAM base-hub driver. ------------------------------------------------- */
+ 
++/*
++ * Some devices (especially battery) may need a bit of time to be fully usable
++ * after being (re-)connected. This delay has been determined via
++ * experimentation.
++ */
++#define SSAM_BASE_UPDATE_CONNECT_DELAY		msecs_to_jiffies(2500)
++
+ enum ssam_base_hub_state {
+ 	SSAM_BASE_HUB_UNINITIALIZED,
+ 	SSAM_BASE_HUB_CONNECTED,
+@@ -296,8 +303,8 @@ enum ssam_base_hub_state {
+ struct ssam_base_hub {
+ 	struct ssam_device *sdev;
+ 
+-	struct mutex lock;  /* Guards state update checks and transitions. */
+ 	enum ssam_base_hub_state state;
++	struct delayed_work update_work;
+ 
+ 	struct ssam_event_notifier notif;
+ };
+@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib
+ 					char *buf)
+ {
+ 	struct ssam_base_hub *hub = dev_get_drvdata(dev);
+-	bool connected;
+-
+-	mutex_lock(&hub->lock);
+-	connected = hub->state == SSAM_BASE_HUB_CONNECTED;
+-	mutex_unlock(&hub->lock);
++	bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
+ 
+ 	return sysfs_emit(buf, "%d\n", connected);
+ }
+@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = {
+ 	.attrs = ssam_base_hub_attrs,
+ };
+ 
+-static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
++static void ssam_base_hub_update_workfn(struct work_struct *work)
+ {
++	struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
+ 	struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
++	enum ssam_base_hub_state state;
+ 	int status = 0;
+ 
+-	lockdep_assert_held(&hub->lock);
++	status = ssam_base_hub_query_state(hub, &state);
++	if (status)
++		return;
+ 
+-	if (hub->state == new)
+-		return 0;
+-	hub->state = new;
++	if (hub->state == state)
++		return;
++	hub->state = state;
+ 
+ 	if (hub->state == SSAM_BASE_HUB_CONNECTED)
+ 		status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
+@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_
+ 
+ 	if (status)
+ 		dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
+-
+-	return status;
+-}
+-
+-static int ssam_base_hub_update(struct ssam_base_hub *hub)
+-{
+-	enum ssam_base_hub_state state;
+-	int status;
+-
+-	mutex_lock(&hub->lock);
+-
+-	status = ssam_base_hub_query_state(hub, &state);
+-	if (!status)
+-		status = __ssam_base_hub_update(hub, state);
+-
+-	mutex_unlock(&hub->lock);
+-	return status;
+ }
+ 
+ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
+ {
+-	struct ssam_base_hub *hub;
+-	struct ssam_device *sdev;
+-	enum ssam_base_hub_state new;
+-
+-	hub = container_of(nf, struct ssam_base_hub, notif);
+-	sdev = hub->sdev;
++	struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
++	unsigned long delay;
+ 
+ 	if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
+ 		return 0;
+ 
+ 	if (event->length < 1) {
+-		dev_err(&sdev->dev, "unexpected payload size: %u\n",
+-			event->length);
++		dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
+ 		return 0;
+ 	}
+ 
+-	if (event->data[0])
+-		new = SSAM_BASE_HUB_CONNECTED;
+-	else
+-		new = SSAM_BASE_HUB_DISCONNECTED;
++	/*
++	 * Delay update when the base is being connected to give devices/EC
++	 * some time to set up.
++	 */
++	delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
+ 
+-	mutex_lock(&hub->lock);
+-	__ssam_base_hub_update(hub, new);
+-	mutex_unlock(&hub->lock);
++	schedule_delayed_work(&hub->update_work, delay);
+ 
+ 	/*
+ 	 * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
+@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam
+ 
+ static int __maybe_unused ssam_base_hub_resume(struct device *dev)
+ {
+-	return ssam_base_hub_update(dev_get_drvdata(dev));
++	struct ssam_base_hub *hub = dev_get_drvdata(dev);
++
++	schedule_delayed_work(&hub->update_work, 0);
++	return 0;
+ }
+ static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
+ 
+@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
+ 	if (!hub)
+ 		return -ENOMEM;
+ 
+-	mutex_init(&hub->lock);
+-
+ 	hub->sdev = sdev;
+ 	hub->state = SSAM_BASE_HUB_UNINITIALIZED;
+ 
+@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
+ 	hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
+ 	hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
+ 
++	INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
++
+ 	ssam_device_set_drvdata(sdev, hub);
+ 
+ 	status = ssam_notifier_register(sdev->ctrl, &hub->notif);
+ 	if (status)
+-		goto err_register;
+-
+-	status = ssam_base_hub_update(hub);
+-	if (status)
+-		goto err_update;
++		return status;
+ 
+ 	status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
+ 	if (status)
+-		goto err_update;
++		goto err;
+ 
++	schedule_delayed_work(&hub->update_work, 0);
+ 	return 0;
+ 
+-err_update:
++err:
+ 	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
++	cancel_delayed_work_sync(&hub->update_work);
+ 	ssam_hub_remove_devices(&sdev->dev);
+-err_register:
+-	mutex_destroy(&hub->lock);
+ 	return status;
+ }
+ 
+@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev)
+ 	sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
+ 
+ 	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
++	cancel_delayed_work_sync(&hub->update_work);
+ 	ssam_hub_remove_devices(&sdev->dev);
+-
+-	mutex_destroy(&hub->lock);
+ }
+ 
+ static const struct ssam_device_id ssam_base_hub_match[] = {
+-- 
+2.31.1
+
+From 5c89063cd80d956a0060bba8f01cdb32e4b8647a Mon Sep 17 00:00:00 2001
+From: Barry Song <song.bao.hua@hisilicon.com>
+Date: Wed, 3 Mar 2021 11:49:15 +1300
+Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi()
+
+Many drivers don't want interrupts enabled automatically via request_irq().
+So they are handling this issue by either way of the below two:
+
+(1)
+  irq_set_status_flags(irq, IRQ_NOAUTOEN);
+  request_irq(dev, irq...);
+
+(2)
+  request_irq(dev, irq...);
+  disable_irq(irq);
+
+The code in the second way is silly and unsafe. In the small time gap
+between request_irq() and disable_irq(), interrupts can still come.
+
+The code in the first way is safe though it's subobtimal.
+
+Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to
+request_irq() and request_nmi(). It prevents the automatic enabling of the
+requested interrupt/nmi in the same safe way as #1 above. With that the
+various usage sites of #1 and #2 above can be simplified and corrected.
+
+Signed-off-by: Barry Song <song.bao.hua@hisilicon.com>
+Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
+Signed-off-by: Ingo Molnar <mingo@kernel.org>
+Cc: dmitry.torokhov@gmail.com
+Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com
+Patchset: surface-sam
+---
+ include/linux/interrupt.h |  4 ++++
+ kernel/irq/manage.c       | 11 +++++++++--
+ 2 files changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
+index 967e25767153..76f1161a441a 100644
+--- a/include/linux/interrupt.h
++++ b/include/linux/interrupt.h
+@@ -61,6 +61,9 @@
+  *                interrupt handler after suspending interrupts. For system
+  *                wakeup devices users need to implement wakeup detection in
+  *                their interrupt handlers.
++ * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it.
++ *                Users will enable it explicitly by enable_irq() or enable_nmi()
++ *                later.
+  */
+ #define IRQF_SHARED		0x00000080
+ #define IRQF_PROBE_SHARED	0x00000100
+@@ -74,6 +77,7 @@
+ #define IRQF_NO_THREAD		0x00010000
+ #define IRQF_EARLY_RESUME	0x00020000
+ #define IRQF_COND_SUSPEND	0x00040000
++#define IRQF_NO_AUTOEN		0x00080000
+ 
+ #define IRQF_TIMER		(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
+ 
+diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
+index 21ea370fccda..49288e941365 100644
+--- a/kernel/irq/manage.c
++++ b/kernel/irq/manage.c
+@@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
+ 			irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
+ 		}
+ 
+-		if (irq_settings_can_autoenable(desc)) {
++		if (!(new->flags & IRQF_NO_AUTOEN) &&
++		    irq_settings_can_autoenable(desc)) {
+ 			irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
+ 		} else {
+ 			/*
+@@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
+ 	 * which interrupt is which (messes up the interrupt freeing
+ 	 * logic etc).
+ 	 *
++	 * Also shared interrupts do not go well with disabling auto enable.
++	 * The sharing interrupt might request it while it's still disabled
++	 * and then wait for interrupts forever.
++	 *
+ 	 * Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
+ 	 * it cannot be set along with IRQF_NO_SUSPEND.
+ 	 */
+ 	if (((irqflags & IRQF_SHARED) && !dev_id) ||
++	    ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
+ 	    (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
+ 	    ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
+ 		return -EINVAL;
+@@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
+ 
+ 	desc = irq_to_desc(irq);
+ 
+-	if (!desc || irq_settings_can_autoenable(desc) ||
++	if (!desc || (irq_settings_can_autoenable(desc) &&
++	    !(irqflags & IRQF_NO_AUTOEN)) ||
+ 	    !irq_settings_can_request(desc) ||
+ 	    WARN_ON(irq_settings_is_per_cpu_devid(desc)) ||
+ 	    !irq_supports_nmi(desc))
+-- 
+2.31.1
+
+From 4c27478492346cbc69ad1779f0ac0f2dd93c4ec2 Mon Sep 17 00:00:00 2001
+From: Tian Tao <tiantao6@hisilicon.com>
+Date: Wed, 7 Apr 2021 15:00:52 +0800
+Subject: [PATCH] platform/surface: aggregator: move to use request_irq by
+ IRQF_NO_AUTOEN flag
+
+disable_irq() after request_irq() still has a time gap in which
+interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will
+disable IRQ auto-enable because of requesting.
+
+this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which
+is being merged: https://lore.kernel.org/patchwork/patch/1388765/
+
+Signed-off-by: Tian Tao <tiantao6@hisilicon.com>
+Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/aggregator/controller.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
+index aa6f37b4f46e..00e38284885a 100644
+--- a/drivers/platform/surface/aggregator/controller.c
++++ b/drivers/platform/surface/aggregator/controller.c
+@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
+ 	 * interrupt, and let the SAM resume callback during the controller
+ 	 * resume process clear it.
+ 	 */
+-	const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING;
++	const int irqf = IRQF_SHARED | IRQF_ONESHOT |
++			 IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
+ 
+ 	gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
+ 	if (IS_ERR(gpiod))
+@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
+ 		return status;
+ 
+ 	ctrl->irq.num = irq;
+-	disable_irq(ctrl->irq.num);
+ 	return 0;
+ }
+ 
+-- 
+2.31.1
+
+From 8dcb1d5d4c05dbea8d40e4e9f23e631f1e5cdd3c Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Wed, 5 May 2021 14:53:45 +0200
+Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared
+
+Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes
+request_threaded_irq() to return with -EINVAL (see comment in flag
+validation in that function). As the interrupt is currently not shared
+between multiple devices, drop the IRQF_SHARED flag.
+
+Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag")
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/aggregator/controller.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
+index 00e38284885a..1f42fcd5d8c3 100644
+--- a/drivers/platform/surface/aggregator/controller.c
++++ b/drivers/platform/surface/aggregator/controller.c
+@@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
+ 	 * interrupt, and let the SAM resume callback during the controller
+ 	 * resume process clear it.
+ 	 */
+-	const int irqf = IRQF_SHARED | IRQF_ONESHOT |
+-			 IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
++	const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
+ 
+ 	gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
+ 	if (IS_ERR(gpiod))
+-- 
+2.31.1
+
+From afcab874b65e164fd01bee6905b281642e4785c5 Mon Sep 17 00:00:00 2001
+From: Dan Carpenter <dan.carpenter@oracle.com>
+Date: Tue, 20 Apr 2021 11:44:02 +0300
+Subject: [PATCH] platform/surface: aggregator: fix a bit test
+
+The "funcs" variable is a u64.  If "func" is more than 31 then the
+BIT() shift will wrap instead of testing the high bits.
+
+Fixes: c167b9c7e3d6 ("platform/surface: Add Surface Aggregator subsystem")
+Reported-by: kernel test robot <lkp@intel.com>
+Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
+Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
+Link: https://lore.kernel.org/r/YH6UUhJhGk3mk13b@mwanda
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Patchset: surface-sam
+---
+ drivers/platform/surface/aggregator/controller.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
+index 1f42fcd5d8c3..8a70df60142c 100644
+--- a/drivers/platform/surface/aggregator/controller.c
++++ b/drivers/platform/surface/aggregator/controller.c
+@@ -1040,7 +1040,7 @@ static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret)
+ 	union acpi_object *obj;
+ 	u64 val;
+ 
+-	if (!(funcs & BIT(func)))
++	if (!(funcs & BIT_ULL(func)))
+ 		return 0; /* Not supported, leave *ret at its default value */
+ 
+ 	obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID,
+-- 
+2.31.1
+
+From 8636b17320f580819226a2108b9cb211d36f0fcb Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Wed, 10 Mar 2021 23:53:28 +0100
+Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Add a HID transport driver to support integrated HID devices on newer
+Microsoft Surface models (specifically 7th-generation, i.e. Surface
+Laptop 3, Surface Book 3, and later).
+
+On those models, the internal keyboard and touchpad (as well as some
+other HID devices with currently unknown function) are connected via the
+generic HID subsystem (TC=0x15) of the Surface System Aggregator Module
+(SSAM). This subsystem provides a generic HID transport layer, support
+for which is implemented by this driver.
+
+Co-developed-by: Blaž Hrastnik <blaz@mxxn.io>
+Signed-off-by: Blaž Hrastnik <blaz@mxxn.io>
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.cz>
+Patchset: surface-sam
+---
+ MAINTAINERS                                |   7 +
+ drivers/hid/Kconfig                        |   2 +
+ drivers/hid/Makefile                       |   2 +
+ drivers/hid/surface-hid/Kconfig            |  28 +++
+ drivers/hid/surface-hid/Makefile           |   6 +
+ drivers/hid/surface-hid/surface_hid.c      | 253 +++++++++++++++++++
+ drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++
+ drivers/hid/surface-hid/surface_hid_core.h |  77 ++++++
+ 8 files changed, 647 insertions(+)
+ create mode 100644 drivers/hid/surface-hid/Kconfig
+ create mode 100644 drivers/hid/surface-hid/Makefile
+ create mode 100644 drivers/hid/surface-hid/surface_hid.c
+ create mode 100644 drivers/hid/surface-hid/surface_hid_core.c
+ create mode 100644 drivers/hid/surface-hid/surface_hid_core.h
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index da1487d672a8..f54b22333ec6 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11891,6 +11891,13 @@ S:	Maintained
+ T:	git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
+ F:	drivers/platform/surface/
+ 
++MICROSOFT SURFACE HID TRANSPORT DRIVER
++M:	Maximilian Luz <luzmaximilian@gmail.com>
++L:	linux-input@vger.kernel.org
++L:	platform-driver-x86@vger.kernel.org
++S:	Maintained
++F:	drivers/hid/surface-hid/
++
+ MICROSOFT SURFACE HOT-PLUG DRIVER
+ M:	Maximilian Luz <luzmaximilian@gmail.com>
+ L:	platform-driver-x86@vger.kernel.org
+diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
+index 786b71ef7738..26e06097ba08 100644
+--- a/drivers/hid/Kconfig
++++ b/drivers/hid/Kconfig
+@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
+ 
+ source "drivers/hid/amd-sfh-hid/Kconfig"
+ 
++source "drivers/hid/surface-hid/Kconfig"
++
+ endmenu
+diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
+index c4f6d5c613dc..1044ed238856 100644
+--- a/drivers/hid/Makefile
++++ b/drivers/hid/Makefile
+@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID)	+= intel-ish-hid/
+ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER)	+= intel-ish-hid/
+ 
+ obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
++
++obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
+diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
+new file mode 100644
+index 000000000000..642c7f0e64fe
+--- /dev/null
++++ b/drivers/hid/surface-hid/Kconfig
+@@ -0,0 +1,28 @@
++# SPDX-License-Identifier: GPL-2.0+
++menu "Surface System Aggregator Module HID support"
++	depends on SURFACE_AGGREGATOR
++	depends on INPUT
++
++config SURFACE_HID
++	tristate "HID transport driver for Surface System Aggregator Module"
++	depends on SURFACE_AGGREGATOR_REGISTRY
++	select SURFACE_HID_CORE
++	help
++	  Driver to support integrated HID devices on newer Microsoft Surface
++	  models.
++
++	  This driver provides support for the HID transport protocol provided
++	  by the Surface Aggregator Module (i.e. the embedded controller) on
++	  7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
++	  Surface Laptop 3. On those models, it is mainly used to connect the
++	  integrated touchpad and keyboard.
++
++	  Say M or Y here, if you want support for integrated HID devices, i.e.
++	  integrated touchpad and keyboard, on 7th generation Microsoft Surface
++	  models.
++
++endmenu
++
++config SURFACE_HID_CORE
++	tristate
++	select HID
+diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
+new file mode 100644
+index 000000000000..62fc04632d3d
+--- /dev/null
++++ b/drivers/hid/surface-hid/Makefile
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: GPL-2.0+
++#
++# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
++#
++obj-$(CONFIG_SURFACE_HID_CORE)	+= surface_hid_core.o
++obj-$(CONFIG_SURFACE_HID)	+= surface_hid.o
+diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
+new file mode 100644
+index 000000000000..3477b31611ae
+--- /dev/null
++++ b/drivers/hid/surface-hid/surface_hid.c
+@@ -0,0 +1,253 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Surface System Aggregator Module (SSAM) HID transport driver for the
++ * generic HID interface (HID/TC=0x15 subsystem). Provides support for
++ * integrated HID devices on Surface Laptop 3, Book 3, and later.
++ *
++ * Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
++ *                         Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/hid.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/types.h>
++
++#include <linux/surface_aggregator/controller.h>
++#include <linux/surface_aggregator/device.h>
++
++#include "surface_hid_core.h"
++
++
++/* -- SAM interface. -------------------------------------------------------- */
++
++struct surface_hid_buffer_slice {
++	__u8 entry;
++	__le32 offset;
++	__le32 length;
++	__u8 end;
++	__u8 data[];
++} __packed;
++
++static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
++
++enum surface_hid_cid {
++	SURFACE_HID_CID_OUTPUT_REPORT      = 0x01,
++	SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
++	SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
++	SURFACE_HID_CID_GET_DESCRIPTOR     = 0x04,
++};
++
++static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
++{
++	u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
++	struct surface_hid_buffer_slice *slice;
++	struct ssam_request rqst;
++	struct ssam_response rsp;
++	u32 buffer_len, offset, length;
++	int status;
++
++	/*
++	 * Note: The 0x76 above has been chosen because that's what's used by
++	 * the Windows driver. Together with the header, this leads to a 128
++	 * byte payload in total.
++	 */
++
++	buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
++	rqst.instance_id = shid->uid.instance;
++	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
++	rqst.length = sizeof(struct surface_hid_buffer_slice);
++	rqst.payload = buffer;
++
++	rsp.capacity = ARRAY_SIZE(buffer);
++	rsp.pointer = buffer;
++
++	slice = (struct surface_hid_buffer_slice *)buffer;
++	slice->entry = entry;
++	slice->end = 0;
++
++	offset = 0;
++	length = buffer_len;
++
++	while (!slice->end && offset < len) {
++		put_unaligned_le32(offset, &slice->offset);
++		put_unaligned_le32(length, &slice->length);
++
++		rsp.length = 0;
++
++		status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
++				    sizeof(*slice));
++		if (status)
++			return status;
++
++		offset = get_unaligned_le32(&slice->offset);
++		length = get_unaligned_le32(&slice->length);
++
++		/* Don't mess stuff up in case we receive garbage. */
++		if (length > buffer_len || offset > len)
++			return -EPROTO;
++
++		if (offset + length > len)
++			length = len - offset;
++
++		memcpy(buf + offset, &slice->data[0], length);
++
++		offset += length;
++		length = buffer_len;
++	}
++
++	if (offset != len) {
++		dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
++			offset, len);
++		return -EPROTO;
++	}
++
++	return 0;
++}
++
++static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
++				   u8 *buf, size_t len)
++{
++	struct ssam_request rqst;
++	u8 cid;
++
++	if (feature)
++		cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
++	else
++		cid = SURFACE_HID_CID_OUTPUT_REPORT;
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.instance_id = shid->uid.instance;
++	rqst.command_id = cid;
++	rqst.flags = 0;
++	rqst.length = len;
++	rqst.payload = buf;
++
++	buf[0] = rprt_id;
++
++	return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
++}
++
++static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	struct ssam_request rqst;
++	struct ssam_response rsp;
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.instance_id = shid->uid.instance;
++	rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
++	rqst.flags = 0;
++	rqst.length = sizeof(rprt_id);
++	rqst.payload = &rprt_id;
++
++	rsp.capacity = len;
++	rsp.length = 0;
++	rsp.pointer = buf;
++
++	return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
++}
++
++static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
++{
++	struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
++
++	if (event->command_id != 0x00)
++		return 0;
++
++	hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
++	return SSAM_NOTIF_HANDLED;
++}
++
++
++/* -- Transport driver. ----------------------------------------------------- */
++
++static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	int status;
++
++	status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
++	return status >= 0 ? len : status;
++}
++
++static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	int status;
++
++	status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
++	return status >= 0 ? len : status;
++}
++
++static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	int status;
++
++	status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
++	return status >= 0 ? len : status;
++}
++
++
++/* -- Driver setup. --------------------------------------------------------- */
++
++static int surface_hid_probe(struct ssam_device *sdev)
++{
++	struct surface_hid_device *shid;
++
++	shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
++	if (!shid)
++		return -ENOMEM;
++
++	shid->dev = &sdev->dev;
++	shid->ctrl = sdev->ctrl;
++	shid->uid = sdev->uid;
++
++	shid->notif.base.priority = 1;
++	shid->notif.base.fn = ssam_hid_event_fn;
++	shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
++	shid->notif.event.id.target_category = sdev->uid.category;
++	shid->notif.event.id.instance = sdev->uid.instance;
++	shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
++	shid->notif.event.flags = 0;
++
++	shid->ops.get_descriptor = ssam_hid_get_descriptor;
++	shid->ops.output_report = shid_output_report;
++	shid->ops.get_feature_report = shid_get_feature_report;
++	shid->ops.set_feature_report = shid_set_feature_report;
++
++	ssam_device_set_drvdata(sdev, shid);
++	return surface_hid_device_add(shid);
++}
++
++static void surface_hid_remove(struct ssam_device *sdev)
++{
++	surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
++}
++
++static const struct ssam_device_id surface_hid_match[] = {
++	{ SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
++	{ },
++};
++MODULE_DEVICE_TABLE(ssam, surface_hid_match);
++
++static struct ssam_device_driver surface_hid_driver = {
++	.probe = surface_hid_probe,
++	.remove = surface_hid_remove,
++	.match_table = surface_hid_match,
++	.driver = {
++		.name = "surface_hid",
++		.pm = &surface_hid_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_ssam_device_driver(surface_hid_driver);
++
++MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
+new file mode 100644
+index 000000000000..7b27ec392232
+--- /dev/null
++++ b/drivers/hid/surface-hid/surface_hid_core.c
+@@ -0,0 +1,272 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Common/core components for the Surface System Aggregator Module (SSAM) HID
++ * transport driver. Provides support for integrated HID devices on Microsoft
++ * Surface models.
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/hid.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/types.h>
++#include <linux/usb/ch9.h>
++
++#include <linux/surface_aggregator/controller.h>
++
++#include "surface_hid_core.h"
++
++
++/* -- Device descriptor access. --------------------------------------------- */
++
++static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
++{
++	int status;
++
++	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
++			(u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
++	if (status)
++		return status;
++
++	if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
++		dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
++			shid->hid_desc.desc_len, sizeof(shid->hid_desc));
++		return -EPROTO;
++	}
++
++	if (shid->hid_desc.desc_type != HID_DT_HID) {
++		dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
++			shid->hid_desc.desc_type, HID_DT_HID);
++		return -EPROTO;
++	}
++
++	if (shid->hid_desc.num_descriptors != 1) {
++		dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
++			shid->hid_desc.num_descriptors);
++		return -EPROTO;
++	}
++
++	if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
++		dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
++			shid->hid_desc.report_desc_type, HID_DT_REPORT);
++		return -EPROTO;
++	}
++
++	return 0;
++}
++
++static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
++{
++	int status;
++
++	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
++			(u8 *)&shid->attrs, sizeof(shid->attrs));
++	if (status)
++		return status;
++
++	if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
++		dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
++			get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
++		return -EPROTO;
++	}
++
++	return 0;
++}
++
++
++/* -- Transport driver (common). -------------------------------------------- */
++
++static int surface_hid_start(struct hid_device *hid)
++{
++	struct surface_hid_device *shid = hid->driver_data;
++
++	return ssam_notifier_register(shid->ctrl, &shid->notif);
++}
++
++static void surface_hid_stop(struct hid_device *hid)
++{
++	struct surface_hid_device *shid = hid->driver_data;
++
++	/* Note: This call will log errors for us, so ignore them here. */
++	ssam_notifier_unregister(shid->ctrl, &shid->notif);
++}
++
++static int surface_hid_open(struct hid_device *hid)
++{
++	return 0;
++}
++
++static void surface_hid_close(struct hid_device *hid)
++{
++}
++
++static int surface_hid_parse(struct hid_device *hid)
++{
++	struct surface_hid_device *shid = hid->driver_data;
++	size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
++	u8 *buf;
++	int status;
++
++	buf = kzalloc(len, GFP_KERNEL);
++	if (!buf)
++		return -ENOMEM;
++
++	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
++	if (!status)
++		status = hid_parse_report(hid, buf, len);
++
++	kfree(buf);
++	return status;
++}
++
++static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
++				   size_t len, unsigned char rtype, int reqtype)
++{
++	struct surface_hid_device *shid = hid->driver_data;
++
++	if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
++		return shid->ops.output_report(shid, reportnum, buf, len);
++
++	else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
++		return shid->ops.get_feature_report(shid, reportnum, buf, len);
++
++	else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
++		return shid->ops.set_feature_report(shid, reportnum, buf, len);
++
++	return -EIO;
++}
++
++static struct hid_ll_driver surface_hid_ll_driver = {
++	.start       = surface_hid_start,
++	.stop        = surface_hid_stop,
++	.open        = surface_hid_open,
++	.close       = surface_hid_close,
++	.parse       = surface_hid_parse,
++	.raw_request = surface_hid_raw_request,
++};
++
++
++/* -- Common device setup. -------------------------------------------------- */
++
++int surface_hid_device_add(struct surface_hid_device *shid)
++{
++	int status;
++
++	status = surface_hid_load_hid_descriptor(shid);
++	if (status)
++		return status;
++
++	status = surface_hid_load_device_attributes(shid);
++	if (status)
++		return status;
++
++	shid->hid = hid_allocate_device();
++	if (IS_ERR(shid->hid))
++		return PTR_ERR(shid->hid);
++
++	shid->hid->dev.parent = shid->dev;
++	shid->hid->bus = BUS_HOST;
++	shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
++	shid->hid->product = cpu_to_le16(shid->attrs.product);
++	shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
++	shid->hid->country = shid->hid_desc.country_code;
++
++	snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
++		 shid->hid->vendor, shid->hid->product);
++
++	strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
++
++	shid->hid->driver_data = shid;
++	shid->hid->ll_driver = &surface_hid_ll_driver;
++
++	status = hid_add_device(shid->hid);
++	if (status)
++		hid_destroy_device(shid->hid);
++
++	return status;
++}
++EXPORT_SYMBOL_GPL(surface_hid_device_add);
++
++void surface_hid_device_destroy(struct surface_hid_device *shid)
++{
++	hid_destroy_device(shid->hid);
++}
++EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
++
++
++/* -- PM ops. --------------------------------------------------------------- */
++
++#ifdef CONFIG_PM_SLEEP
++
++static int surface_hid_suspend(struct device *dev)
++{
++	struct surface_hid_device *d = dev_get_drvdata(dev);
++
++	if (d->hid->driver && d->hid->driver->suspend)
++		return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
++
++	return 0;
++}
++
++static int surface_hid_resume(struct device *dev)
++{
++	struct surface_hid_device *d = dev_get_drvdata(dev);
++
++	if (d->hid->driver && d->hid->driver->resume)
++		return d->hid->driver->resume(d->hid);
++
++	return 0;
++}
++
++static int surface_hid_freeze(struct device *dev)
++{
++	struct surface_hid_device *d = dev_get_drvdata(dev);
++
++	if (d->hid->driver && d->hid->driver->suspend)
++		return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
++
++	return 0;
++}
++
++static int surface_hid_poweroff(struct device *dev)
++{
++	struct surface_hid_device *d = dev_get_drvdata(dev);
++
++	if (d->hid->driver && d->hid->driver->suspend)
++		return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
++
++	return 0;
++}
++
++static int surface_hid_restore(struct device *dev)
++{
++	struct surface_hid_device *d = dev_get_drvdata(dev);
++
++	if (d->hid->driver && d->hid->driver->reset_resume)
++		return d->hid->driver->reset_resume(d->hid);
++
++	return 0;
++}
++
++const struct dev_pm_ops surface_hid_pm_ops = {
++	.freeze   = surface_hid_freeze,
++	.thaw     = surface_hid_resume,
++	.suspend  = surface_hid_suspend,
++	.resume   = surface_hid_resume,
++	.poweroff = surface_hid_poweroff,
++	.restore  = surface_hid_restore,
++};
++EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
++
++#else /* CONFIG_PM_SLEEP */
++
++const struct dev_pm_ops surface_hid_pm_ops = { };
++EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
++
++#endif /* CONFIG_PM_SLEEP */
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h
+new file mode 100644
+index 000000000000..4b1a7b57e035
+--- /dev/null
++++ b/drivers/hid/surface-hid/surface_hid_core.h
+@@ -0,0 +1,77 @@
++/* SPDX-License-Identifier: GPL-2.0+ */
++/*
++ * Common/core components for the Surface System Aggregator Module (SSAM) HID
++ * transport driver. Provides support for integrated HID devices on Microsoft
++ * Surface models.
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#ifndef SURFACE_HID_CORE_H
++#define SURFACE_HID_CORE_H
++
++#include <linux/hid.h>
++#include <linux/pm.h>
++#include <linux/types.h>
++
++#include <linux/surface_aggregator/controller.h>
++#include <linux/surface_aggregator/device.h>
++
++enum surface_hid_descriptor_entry {
++	SURFACE_HID_DESC_HID    = 0,
++	SURFACE_HID_DESC_REPORT = 1,
++	SURFACE_HID_DESC_ATTRS  = 2,
++};
++
++struct surface_hid_descriptor {
++	__u8 desc_len;			/* = 9 */
++	__u8 desc_type;			/* = HID_DT_HID */
++	__le16 hid_version;
++	__u8 country_code;
++	__u8 num_descriptors;		/* = 1 */
++
++	__u8 report_desc_type;		/* = HID_DT_REPORT */
++	__le16 report_desc_len;
++} __packed;
++
++static_assert(sizeof(struct surface_hid_descriptor) == 9);
++
++struct surface_hid_attributes {
++	__le32 length;
++	__le16 vendor;
++	__le16 product;
++	__le16 version;
++	__u8 _unknown[22];
++} __packed;
++
++static_assert(sizeof(struct surface_hid_attributes) == 32);
++
++struct surface_hid_device;
++
++struct surface_hid_device_ops {
++	int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
++	int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
++	int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
++	int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
++};
++
++struct surface_hid_device {
++	struct device *dev;
++	struct ssam_controller *ctrl;
++	struct ssam_device_uid uid;
++
++	struct surface_hid_descriptor hid_desc;
++	struct surface_hid_attributes attrs;
++
++	struct ssam_event_notifier notif;
++	struct hid_device *hid;
++
++	struct surface_hid_device_ops ops;
++};
++
++int surface_hid_device_add(struct surface_hid_device *shid);
++void surface_hid_device_destroy(struct surface_hid_device *shid);
++
++extern const struct dev_pm_ops surface_hid_pm_ops;
++
++#endif /* SURFACE_HID_CORE_H */
+-- 
+2.31.1
+
+From 0b9f3fca97a121daae91fd91364f41c844f229e8 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Wed, 10 Mar 2021 23:53:29 +0100
+Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface
+
+Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of
+the Surface System Aggregator Module (SSAM) to the Surface HID driver.
+On Surface Laptops 1 and 2, this interface is used to connect the
+integrated keyboard.
+
+Note that this subsystem interface essentially provides a limited HID
+transport layer. In contrast to the generic HID interface (TC=0x15) used
+on newer Surface models, this interface only allows (as far as we know)
+for a single device to be connected and is otherwise severely limited in
+terms of support for feature- and output-reports. Specifically, only
+caps-lock-LED output-reports and a single read-only feature-report are
+supported.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Jiri Kosina <jkosina@suse.cz>
+Patchset: surface-sam
+---
+ drivers/hid/surface-hid/Kconfig       |  14 ++
+ drivers/hid/surface-hid/Makefile      |   1 +
+ drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++
+ 3 files changed, 315 insertions(+)
+ create mode 100644 drivers/hid/surface-hid/surface_kbd.c
+
+diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
+index 642c7f0e64fe..7ce9b5d641eb 100644
+--- a/drivers/hid/surface-hid/Kconfig
++++ b/drivers/hid/surface-hid/Kconfig
+@@ -21,6 +21,20 @@ config SURFACE_HID
+ 	  integrated touchpad and keyboard, on 7th generation Microsoft Surface
+ 	  models.
+ 
++config SURFACE_KBD
++	tristate "HID keyboard transport driver for Surface System Aggregator Module"
++	select SURFACE_HID_CORE
++	help
++	  Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
++
++	  This driver provides support for the HID transport protocol provided
++	  by the Surface Aggregator Module (i.e. the embedded controller) on
++	  Microsoft Surface Laptops 1 and 2. It is used to connect the
++	  integrated keyboard on those devices.
++
++	  Say M or Y here, if you want support for the integrated keyboard on
++	  Microsoft Surface Laptops 1 and 2.
++
+ endmenu
+ 
+ config SURFACE_HID_CORE
+diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
+index 62fc04632d3d..4ae11cf09b25 100644
+--- a/drivers/hid/surface-hid/Makefile
++++ b/drivers/hid/surface-hid/Makefile
+@@ -4,3 +4,4 @@
+ #
+ obj-$(CONFIG_SURFACE_HID_CORE)	+= surface_hid_core.o
+ obj-$(CONFIG_SURFACE_HID)	+= surface_hid.o
++obj-$(CONFIG_SURFACE_KBD)	+= surface_kbd.o
+diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
+new file mode 100644
+index 000000000000..0635341bc517
+--- /dev/null
++++ b/drivers/hid/surface-hid/surface_kbd.c
+@@ -0,0 +1,300 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy
++ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
++ * integrated HID keyboard on Surface Laptops 1 and 2.
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/hid.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/platform_device.h>
++#include <linux/types.h>
++
++#include <linux/surface_aggregator/controller.h>
++
++#include "surface_hid_core.h"
++
++
++/* -- SAM interface (KBD). -------------------------------------------------- */
++
++#define KBD_FEATURE_REPORT_SIZE			7  /* 6 + report ID */
++
++enum surface_kbd_cid {
++	SURFACE_KBD_CID_GET_DESCRIPTOR		= 0x00,
++	SURFACE_KBD_CID_SET_CAPSLOCK_LED	= 0x01,
++	SURFACE_KBD_CID_EVT_INPUT_GENERIC	= 0x03,
++	SURFACE_KBD_CID_EVT_INPUT_HOTKEYS	= 0x04,
++	SURFACE_KBD_CID_GET_FEATURE_REPORT	= 0x0b,
++};
++
++static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
++{
++	struct ssam_request rqst;
++	struct ssam_response rsp;
++	int status;
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
++	rqst.instance_id = shid->uid.instance;
++	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
++	rqst.length = sizeof(entry);
++	rqst.payload = &entry;
++
++	rsp.capacity = len;
++	rsp.length = 0;
++	rsp.pointer = buf;
++
++	status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
++	if (status)
++		return status;
++
++	if (rsp.length != len) {
++		dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
++			rsp.length, len);
++		return -EPROTO;
++	}
++
++	return 0;
++}
++
++static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
++{
++	struct ssam_request rqst;
++	u8 value_u8 = value;
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
++	rqst.instance_id = shid->uid.instance;
++	rqst.flags = 0;
++	rqst.length = sizeof(value_u8);
++	rqst.payload = &value_u8;
++
++	return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
++}
++
++static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
++{
++	struct ssam_request rqst;
++	struct ssam_response rsp;
++	u8 payload = 0;
++	int status;
++
++	rqst.target_category = shid->uid.category;
++	rqst.target_id = shid->uid.target;
++	rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
++	rqst.instance_id = shid->uid.instance;
++	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
++	rqst.length = sizeof(payload);
++	rqst.payload = &payload;
++
++	rsp.capacity = len;
++	rsp.length = 0;
++	rsp.pointer = buf;
++
++	status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
++	if (status)
++		return status;
++
++	if (rsp.length != len) {
++		dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
++			rsp.length, len);
++		return -EPROTO;
++	}
++
++	return 0;
++}
++
++static bool ssam_kbd_is_input_event(const struct ssam_event *event)
++{
++	if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
++		return true;
++
++	if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
++		return true;
++
++	return false;
++}
++
++static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
++{
++	struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
++
++	/*
++	 * Check against device UID manually, as registry and device target
++	 * category doesn't line up.
++	 */
++
++	if (shid->uid.category != event->target_category)
++		return 0;
++
++	if (shid->uid.target != event->target_id)
++		return 0;
++
++	if (shid->uid.instance != event->instance_id)
++		return 0;
++
++	if (!ssam_kbd_is_input_event(event))
++		return 0;
++
++	hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
++	return SSAM_NOTIF_HANDLED;
++}
++
++
++/* -- Transport driver (KBD). ----------------------------------------------- */
++
++static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
++{
++	struct hid_field *field;
++	unsigned int offset, size;
++	int i;
++
++	/* Get LED field. */
++	field = hidinput_get_led_field(hid);
++	if (!field)
++		return -ENOENT;
++
++	/* Check if we got the correct report. */
++	if (len != hid_report_len(field->report))
++		return -ENOENT;
++
++	if (rprt_id != field->report->id)
++		return -ENOENT;
++
++	/* Get caps lock LED index. */
++	for (i = 0; i < field->report_count; i++)
++		if ((field->usage[i].hid & 0xffff) == 0x02)
++			break;
++
++	if (i == field->report_count)
++		return -ENOENT;
++
++	/* Extract value. */
++	size = field->report_size;
++	offset = field->report_offset + i * size;
++	return !!hid_field_extract(hid, buf + 1, size, offset);
++}
++
++static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	int caps_led;
++	int status;
++
++	caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
++	if (caps_led < 0)
++		return -EIO;  /* Only caps LED output reports are supported. */
++
++	status = ssam_kbd_set_caps_led(shid, caps_led);
++	if (status < 0)
++		return status;
++
++	return len;
++}
++
++static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	u8 report[KBD_FEATURE_REPORT_SIZE];
++	int status;
++
++	/*
++	 * The keyboard only has a single hard-coded read-only feature report
++	 * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
++	 * report ID against the requested one.
++	 */
++
++	if (len < ARRAY_SIZE(report))
++		return -ENOSPC;
++
++	status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
++	if (status < 0)
++		return status;
++
++	if (rprt_id != report[0])
++		return -ENOENT;
++
++	memcpy(buf, report, ARRAY_SIZE(report));
++	return len;
++}
++
++static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
++{
++	/* Not supported. See skbd_get_feature_report() for details. */
++	return -EIO;
++}
++
++
++/* -- Driver setup. --------------------------------------------------------- */
++
++static int surface_kbd_probe(struct platform_device *pdev)
++{
++	struct ssam_controller *ctrl;
++	struct surface_hid_device *shid;
++
++	/* Add device link to EC. */
++	ctrl = ssam_client_bind(&pdev->dev);
++	if (IS_ERR(ctrl))
++		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
++
++	shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
++	if (!shid)
++		return -ENOMEM;
++
++	shid->dev = &pdev->dev;
++	shid->ctrl = ctrl;
++
++	shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
++	shid->uid.category = SSAM_SSH_TC_KBD;
++	shid->uid.target = 2;
++	shid->uid.instance = 0;
++	shid->uid.function = 0;
++
++	shid->notif.base.priority = 1;
++	shid->notif.base.fn = ssam_kbd_event_fn;
++	shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
++	shid->notif.event.id.target_category = shid->uid.category;
++	shid->notif.event.id.instance = shid->uid.instance;
++	shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
++	shid->notif.event.flags = 0;
++
++	shid->ops.get_descriptor = ssam_kbd_get_descriptor;
++	shid->ops.output_report = skbd_output_report;
++	shid->ops.get_feature_report = skbd_get_feature_report;
++	shid->ops.set_feature_report = skbd_set_feature_report;
++
++	platform_set_drvdata(pdev, shid);
++	return surface_hid_device_add(shid);
++}
++
++static int surface_kbd_remove(struct platform_device *pdev)
++{
++	surface_hid_device_destroy(platform_get_drvdata(pdev));
++	return 0;
++}
++
++static const struct acpi_device_id surface_kbd_match[] = {
++	{ "MSHW0096" },
++	{ },
++};
++MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
++
++static struct platform_driver surface_kbd_driver = {
++	.probe = surface_kbd_probe,
++	.remove = surface_kbd_remove,
++	.driver = {
++		.name = "surface_keyboard",
++		.acpi_match_table = surface_kbd_match,
++		.pm = &surface_hid_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_platform_driver(surface_kbd_driver);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From b533b1fbc3f818c8bd2bb2967093deb3ce665ea5 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Fri, 23 Apr 2021 00:51:22 +0200
+Subject: [PATCH] HID: surface-hid: Fix integer endian conversion
+
+We want to convert from 16 bit (unsigned) little endian values contained
+in a packed struct to CPU native endian values here, not the other way
+around. So replace cpu_to_le16() with get_unaligned_le16(), using the
+latter instead of le16_to_cpu() to acknowledge that we are reading from
+a packed struct.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Patchset: surface-sam
+---
+ drivers/hid/surface-hid/surface_hid_core.c | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
+index 7b27ec392232..5571e74abe91 100644
+--- a/drivers/hid/surface-hid/surface_hid_core.c
++++ b/drivers/hid/surface-hid/surface_hid_core.c
+@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid)
+ 
+ 	shid->hid->dev.parent = shid->dev;
+ 	shid->hid->bus = BUS_HOST;
+-	shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
+-	shid->hid->product = cpu_to_le16(shid->attrs.product);
+-	shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
++	shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
++	shid->hid->product = get_unaligned_le16(&shid->attrs.product);
++	shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
+ 	shid->hid->country = shid->hid_desc.country_code;
+ 
+ 	snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
+-- 
+2.31.1
+
+From da3b800dc4b1ca234c239cf30a0d5bfbf3540e53 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Tue, 6 Apr 2021 01:41:25 +0200
+Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator
+ Module
+
+On newer Microsoft Surface models (specifically 7th-generation, i.e.
+Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
+battery and AC status/information is no longer handled via standard ACPI
+devices, but instead directly via the Surface System Aggregator Module
+(SSAM), i.e. the embedded controller on those devices.
+
+While on previous generation models, battery status is also handled via
+SSAM, an ACPI shim was present to translate the standard ACPI battery
+interface to SSAM requests. The SSAM interface itself, which is modeled
+closely after the ACPI interface, has not changed.
+
+This commit introduces a new SSAM client device driver to support
+battery status/information via the aforementioned interface on said
+Surface models. It is in parts based on the standard ACPI battery
+driver.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
+Patchset: surface-sam
+---
+ .../ABI/testing/sysfs-class-power-surface     |  15 +
+ MAINTAINERS                                   |   7 +
+ drivers/power/supply/Kconfig                  |  16 +
+ drivers/power/supply/Makefile                 |   1 +
+ drivers/power/supply/surface_battery.c        | 865 ++++++++++++++++++
+ 5 files changed, 904 insertions(+)
+ create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface
+ create mode 100644 drivers/power/supply/surface_battery.c
+
+diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface
+new file mode 100644
+index 000000000000..79cde4dcf2f5
+--- /dev/null
++++ b/Documentation/ABI/testing/sysfs-class-power-surface
+@@ -0,0 +1,15 @@
++What:		/sys/class/power_supply/<supply_name>/alarm
++Date:		April 2021
++KernelVersion:	5.13
++Contact:	Maximilian Luz <luzmaximilian@gmail.com>
++Description:
++		Battery trip point. When the remaining battery capacity crosses this
++		value in either direction, the system will be notified and if
++		necessary woken.
++
++		Set to zero to clear/disable.
++
++		Access: Read, Write
++
++		Valid values: In micro-Wh or micro-Ah, depending on the power unit
++		of the battery
+diff --git a/MAINTAINERS b/MAINTAINERS
+index f54b22333ec6..7ee93b732270 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11868,6 +11868,13 @@ F:	drivers/scsi/smartpqi/smartpqi*.[ch]
+ F:	include/linux/cciss*.h
+ F:	include/uapi/linux/cciss*.h
+ 
++MICROSOFT SURFACE BATTERY AND AC DRIVERS
++M:	Maximilian Luz <luzmaximilian@gmail.com>
++L:	linux-pm@vger.kernel.org
++L:	platform-driver-x86@vger.kernel.org
++S:	Maintained
++F:	drivers/power/supply/surface_battery.c
++
+ MICROSOFT SURFACE DTX DRIVER
+ M:	Maximilian Luz <luzmaximilian@gmail.com>
+ L:	platform-driver-x86@vger.kernel.org
+diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
+index 006b95eca673..cebeff10d543 100644
+--- a/drivers/power/supply/Kconfig
++++ b/drivers/power/supply/Kconfig
+@@ -801,4 +801,20 @@ config BATTERY_ACER_A500
+ 	help
+ 	  Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
+ 
++config BATTERY_SURFACE
++	tristate "Battery driver for 7th-generation Microsoft Surface devices"
++	depends on SURFACE_AGGREGATOR_REGISTRY
++	help
++	  Driver for battery devices connected via/managed by the Surface System
++	  Aggregator Module (SSAM).
++
++	  This driver provides battery-information and -status support for
++	  Surface devices where said data is not exposed via the standard ACPI
++	  devices. On those models (7th-generation), battery-information is
++	  instead handled directly via SSAM client devices and this driver.
++
++	  Say M or Y here to include battery status support for 7th-generation
++	  Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
++	  Surface Book 3, and Surface Laptop Go.
++
+ endif # POWER_SUPPLY
+diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
+index 5e5fdbbef531..134041538d2c 100644
+--- a/drivers/power/supply/Makefile
++++ b/drivers/power/supply/Makefile
+@@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954)	+= bd99954-charger.o
+ obj-$(CONFIG_CHARGER_WILCO)	+= wilco-charger.o
+ obj-$(CONFIG_RN5T618_POWER)	+= rn5t618_power.o
+ obj-$(CONFIG_BATTERY_ACER_A500)	+= acer_a500_battery.o
++obj-$(CONFIG_BATTERY_SURFACE)	+= surface_battery.o
+diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
+new file mode 100644
+index 000000000000..4116dd839ecd
+--- /dev/null
++++ b/drivers/power/supply/surface_battery.c
+@@ -0,0 +1,865 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * Battery driver for 7th-generation Microsoft Surface devices via Surface
++ * System Aggregator Module (SSAM).
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/jiffies.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/power_supply.h>
++#include <linux/sysfs.h>
++#include <linux/types.h>
++#include <linux/workqueue.h>
++
++#include <linux/surface_aggregator/device.h>
++
++
++/* -- SAM interface. -------------------------------------------------------- */
++
++enum sam_event_cid_bat {
++	SAM_EVENT_CID_BAT_BIX         = 0x15,
++	SAM_EVENT_CID_BAT_BST         = 0x16,
++	SAM_EVENT_CID_BAT_ADP         = 0x17,
++	SAM_EVENT_CID_BAT_PROT        = 0x18,
++	SAM_EVENT_CID_BAT_DPTF        = 0x53,
++};
++
++enum sam_battery_sta {
++	SAM_BATTERY_STA_OK            = 0x0f,
++	SAM_BATTERY_STA_PRESENT	      = 0x10,
++};
++
++enum sam_battery_state {
++	SAM_BATTERY_STATE_DISCHARGING = BIT(0),
++	SAM_BATTERY_STATE_CHARGING    = BIT(1),
++	SAM_BATTERY_STATE_CRITICAL    = BIT(2),
++};
++
++enum sam_battery_power_unit {
++	SAM_BATTERY_POWER_UNIT_mW     = 0,
++	SAM_BATTERY_POWER_UNIT_mA     = 1,
++};
++
++/* Equivalent to data returned in ACPI _BIX method, revision 0. */
++struct spwr_bix {
++	u8  revision;
++	__le32 power_unit;
++	__le32 design_cap;
++	__le32 last_full_charge_cap;
++	__le32 technology;
++	__le32 design_voltage;
++	__le32 design_cap_warn;
++	__le32 design_cap_low;
++	__le32 cycle_count;
++	__le32 measurement_accuracy;
++	__le32 max_sampling_time;
++	__le32 min_sampling_time;
++	__le32 max_avg_interval;
++	__le32 min_avg_interval;
++	__le32 bat_cap_granularity_1;
++	__le32 bat_cap_granularity_2;
++	__u8 model[21];
++	__u8 serial[11];
++	__u8 type[5];
++	__u8 oem_info[21];
++} __packed;
++
++static_assert(sizeof(struct spwr_bix) == 119);
++
++/* Equivalent to data returned in ACPI _BST method. */
++struct spwr_bst {
++	__le32 state;
++	__le32 present_rate;
++	__le32 remaining_cap;
++	__le32 present_voltage;
++} __packed;
++
++static_assert(sizeof(struct spwr_bst) == 16);
++
++#define SPWR_BIX_REVISION		0
++#define SPWR_BATTERY_VALUE_UNKNOWN	0xffffffff
++
++/* Get battery status (_STA) */
++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x01,
++});
++
++/* Get battery static information (_BIX). */
++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x02,
++});
++
++/* Get battery dynamic information (_BST). */
++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x03,
++});
++
++/* Set battery trip point (_BTP). */
++SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x04,
++});
++
++
++/* -- Device structures. ---------------------------------------------------- */
++
++struct spwr_psy_properties {
++	const char *name;
++	struct ssam_event_registry registry;
++};
++
++struct spwr_battery_device {
++	struct ssam_device *sdev;
++
++	char name[32];
++	struct power_supply *psy;
++	struct power_supply_desc psy_desc;
++
++	struct delayed_work update_work;
++
++	struct ssam_event_notifier notif;
++
++	struct mutex lock;  /* Guards access to state data below. */
++	unsigned long timestamp;
++
++	__le32 sta;
++	struct spwr_bix bix;
++	struct spwr_bst bst;
++	u32 alarm;
++};
++
++
++/* -- Module parameters. ---------------------------------------------------- */
++
++static unsigned int cache_time = 1000;
++module_param(cache_time, uint, 0644);
++MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]");
++
++
++/* -- State management. ----------------------------------------------------- */
++
++/*
++ * Delay for battery update quirk. See spwr_external_power_changed() below
++ * for more details.
++ */
++#define SPWR_AC_BAT_UPDATE_DELAY	msecs_to_jiffies(5000)
++
++static bool spwr_battery_present(struct spwr_battery_device *bat)
++{
++	lockdep_assert_held(&bat->lock);
++
++	return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT;
++}
++
++static int spwr_battery_load_sta(struct spwr_battery_device *bat)
++{
++	lockdep_assert_held(&bat->lock);
++
++	return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta);
++}
++
++static int spwr_battery_load_bix(struct spwr_battery_device *bat)
++{
++	int status;
++
++	lockdep_assert_held(&bat->lock);
++
++	if (!spwr_battery_present(bat))
++		return 0;
++
++	status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix);
++
++	/* Enforce NULL terminated strings in case anything goes wrong... */
++	bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0;
++	bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0;
++	bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0;
++	bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0;
++
++	return status;
++}
++
++static int spwr_battery_load_bst(struct spwr_battery_device *bat)
++{
++	lockdep_assert_held(&bat->lock);
++
++	if (!spwr_battery_present(bat))
++		return 0;
++
++	return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst);
++}
++
++static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value)
++{
++	__le32 value_le = cpu_to_le32(value);
++
++	lockdep_assert_held(&bat->lock);
++
++	bat->alarm = value;
++	return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le);
++}
++
++static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached)
++{
++	unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time);
++	int status;
++
++	lockdep_assert_held(&bat->lock);
++
++	if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline))
++		return 0;
++
++	status = spwr_battery_load_sta(bat);
++	if (status)
++		return status;
++
++	status = spwr_battery_load_bst(bat);
++	if (status)
++		return status;
++
++	bat->timestamp = jiffies;
++	return 0;
++}
++
++static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached)
++{
++	int status;
++
++	mutex_lock(&bat->lock);
++	status = spwr_battery_update_bst_unlocked(bat, cached);
++	mutex_unlock(&bat->lock);
++
++	return status;
++}
++
++static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat)
++{
++	int status;
++
++	lockdep_assert_held(&bat->lock);
++
++	status = spwr_battery_load_sta(bat);
++	if (status)
++		return status;
++
++	status = spwr_battery_load_bix(bat);
++	if (status)
++		return status;
++
++	status = spwr_battery_load_bst(bat);
++	if (status)
++		return status;
++
++	if (bat->bix.revision != SPWR_BIX_REVISION)
++		dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision);
++
++	bat->timestamp = jiffies;
++	return 0;
++}
++
++static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat)
++{
++	u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap);
++
++	lockdep_assert_held(&bat->lock);
++
++	if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
++		full_cap = get_unaligned_le32(&bat->bix.design_cap);
++
++	return full_cap;
++}
++
++static bool spwr_battery_is_full(struct spwr_battery_device *bat)
++{
++	u32 state = get_unaligned_le32(&bat->bst.state);
++	u32 full_cap = sprw_battery_get_full_cap_safe(bat);
++	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
++
++	lockdep_assert_held(&bat->lock);
++
++	return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 &&
++		remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN &&
++		remaining_cap >= full_cap &&
++		state == 0;
++}
++
++static int spwr_battery_recheck_full(struct spwr_battery_device *bat)
++{
++	bool present;
++	u32 unit;
++	int status;
++
++	mutex_lock(&bat->lock);
++	unit = get_unaligned_le32(&bat->bix.power_unit);
++	present = spwr_battery_present(bat);
++
++	status = spwr_battery_update_bix_unlocked(bat);
++	if (status)
++		goto out;
++
++	/* If battery has been attached, (re-)initialize alarm. */
++	if (!present && spwr_battery_present(bat)) {
++		u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
++
++		status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
++		if (status)
++			goto out;
++	}
++
++	/*
++	 * Warn if the unit has changed. This is something we genuinely don't
++	 * expect to happen, so make this a big warning. If it does, we'll
++	 * need to add support for it.
++	 */
++	WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit));
++
++out:
++	mutex_unlock(&bat->lock);
++
++	if (!status)
++		power_supply_changed(bat->psy);
++
++	return status;
++}
++
++static int spwr_battery_recheck_status(struct spwr_battery_device *bat)
++{
++	int status;
++
++	status = spwr_battery_update_bst(bat, false);
++	if (!status)
++		power_supply_changed(bat->psy);
++
++	return status;
++}
++
++static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event)
++{
++	struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
++	int status;
++
++	dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
++		event->command_id, event->instance_id, event->target_id);
++
++	switch (event->command_id) {
++	case SAM_EVENT_CID_BAT_BIX:
++		status = spwr_battery_recheck_full(bat);
++		break;
++
++	case SAM_EVENT_CID_BAT_BST:
++		status = spwr_battery_recheck_status(bat);
++		break;
++
++	case SAM_EVENT_CID_BAT_PROT:
++		/*
++		 * TODO: Implement support for battery protection status change
++		 *       event.
++		 */
++		status = 0;
++		break;
++
++	case SAM_EVENT_CID_BAT_DPTF:
++		/*
++		 * TODO: Implement support for DPTF event.
++		 */
++		status = 0;
++		break;
++
++	default:
++		return 0;
++	}
++
++	return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
++}
++
++static void spwr_battery_update_bst_workfn(struct work_struct *work)
++{
++	struct delayed_work *dwork = to_delayed_work(work);
++	struct spwr_battery_device *bat;
++	int status;
++
++	bat = container_of(dwork, struct spwr_battery_device, update_work);
++
++	status = spwr_battery_update_bst(bat, false);
++	if (status) {
++		dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status);
++		return;
++	}
++
++	power_supply_changed(bat->psy);
++}
++
++static void spwr_external_power_changed(struct power_supply *psy)
++{
++	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
++
++	/*
++	 * Handle battery update quirk: When the battery is fully charged (or
++	 * charged up to the limit imposed by the UEFI battery limit) and the
++	 * adapter is plugged in or removed, the EC does not send a separate
++	 * event for the state (charging/discharging) change. Furthermore it
++	 * may take some time until the state is updated on the battery.
++	 * Schedule an update to solve this.
++	 */
++
++	schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY);
++}
++
++
++/* -- Properties. ----------------------------------------------------------- */
++
++static const enum power_supply_property spwr_battery_props_chg[] = {
++	POWER_SUPPLY_PROP_STATUS,
++	POWER_SUPPLY_PROP_PRESENT,
++	POWER_SUPPLY_PROP_TECHNOLOGY,
++	POWER_SUPPLY_PROP_CYCLE_COUNT,
++	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
++	POWER_SUPPLY_PROP_VOLTAGE_NOW,
++	POWER_SUPPLY_PROP_CURRENT_NOW,
++	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
++	POWER_SUPPLY_PROP_CHARGE_FULL,
++	POWER_SUPPLY_PROP_CHARGE_NOW,
++	POWER_SUPPLY_PROP_CAPACITY,
++	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
++	POWER_SUPPLY_PROP_MODEL_NAME,
++	POWER_SUPPLY_PROP_MANUFACTURER,
++	POWER_SUPPLY_PROP_SERIAL_NUMBER,
++};
++
++static const enum power_supply_property spwr_battery_props_eng[] = {
++	POWER_SUPPLY_PROP_STATUS,
++	POWER_SUPPLY_PROP_PRESENT,
++	POWER_SUPPLY_PROP_TECHNOLOGY,
++	POWER_SUPPLY_PROP_CYCLE_COUNT,
++	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
++	POWER_SUPPLY_PROP_VOLTAGE_NOW,
++	POWER_SUPPLY_PROP_POWER_NOW,
++	POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
++	POWER_SUPPLY_PROP_ENERGY_FULL,
++	POWER_SUPPLY_PROP_ENERGY_NOW,
++	POWER_SUPPLY_PROP_CAPACITY,
++	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
++	POWER_SUPPLY_PROP_MODEL_NAME,
++	POWER_SUPPLY_PROP_MANUFACTURER,
++	POWER_SUPPLY_PROP_SERIAL_NUMBER,
++};
++
++static int spwr_battery_prop_status(struct spwr_battery_device *bat)
++{
++	u32 state = get_unaligned_le32(&bat->bst.state);
++	u32 present_rate = get_unaligned_le32(&bat->bst.present_rate);
++
++	lockdep_assert_held(&bat->lock);
++
++	if (state & SAM_BATTERY_STATE_DISCHARGING)
++		return POWER_SUPPLY_STATUS_DISCHARGING;
++
++	if (state & SAM_BATTERY_STATE_CHARGING)
++		return POWER_SUPPLY_STATUS_CHARGING;
++
++	if (spwr_battery_is_full(bat))
++		return POWER_SUPPLY_STATUS_FULL;
++
++	if (present_rate == 0)
++		return POWER_SUPPLY_STATUS_NOT_CHARGING;
++
++	return POWER_SUPPLY_STATUS_UNKNOWN;
++}
++
++static int spwr_battery_prop_technology(struct spwr_battery_device *bat)
++{
++	lockdep_assert_held(&bat->lock);
++
++	if (!strcasecmp("NiCd", bat->bix.type))
++		return POWER_SUPPLY_TECHNOLOGY_NiCd;
++
++	if (!strcasecmp("NiMH", bat->bix.type))
++		return POWER_SUPPLY_TECHNOLOGY_NiMH;
++
++	if (!strcasecmp("LION", bat->bix.type))
++		return POWER_SUPPLY_TECHNOLOGY_LION;
++
++	if (!strncasecmp("LI-ION", bat->bix.type, 6))
++		return POWER_SUPPLY_TECHNOLOGY_LION;
++
++	if (!strcasecmp("LiP", bat->bix.type))
++		return POWER_SUPPLY_TECHNOLOGY_LIPO;
++
++	return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
++}
++
++static int spwr_battery_prop_capacity(struct spwr_battery_device *bat)
++{
++	u32 full_cap = sprw_battery_get_full_cap_safe(bat);
++	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
++
++	lockdep_assert_held(&bat->lock);
++
++	if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
++		return -ENODATA;
++
++	if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN)
++		return -ENODATA;
++
++	return remaining_cap * 100 / full_cap;
++}
++
++static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat)
++{
++	u32 state = get_unaligned_le32(&bat->bst.state);
++	u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
++
++	lockdep_assert_held(&bat->lock);
++
++	if (state & SAM_BATTERY_STATE_CRITICAL)
++		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
++
++	if (spwr_battery_is_full(bat))
++		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
++
++	if (remaining_cap <= bat->alarm)
++		return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
++
++	return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
++}
++
++static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp,
++				     union power_supply_propval *val)
++{
++	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
++	u32 value;
++	int status;
++
++	mutex_lock(&bat->lock);
++
++	status = spwr_battery_update_bst_unlocked(bat, true);
++	if (status)
++		goto out;
++
++	/* Abort if battery is not present. */
++	if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) {
++		status = -ENODEV;
++		goto out;
++	}
++
++	switch (psp) {
++	case POWER_SUPPLY_PROP_STATUS:
++		val->intval = spwr_battery_prop_status(bat);
++		break;
++
++	case POWER_SUPPLY_PROP_PRESENT:
++		val->intval = spwr_battery_present(bat);
++		break;
++
++	case POWER_SUPPLY_PROP_TECHNOLOGY:
++		val->intval = spwr_battery_prop_technology(bat);
++		break;
++
++	case POWER_SUPPLY_PROP_CYCLE_COUNT:
++		value = get_unaligned_le32(&bat->bix.cycle_count);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
++		value = get_unaligned_le32(&bat->bix.design_voltage);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
++		value = get_unaligned_le32(&bat->bst.present_voltage);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_CURRENT_NOW:
++	case POWER_SUPPLY_PROP_POWER_NOW:
++		value = get_unaligned_le32(&bat->bst.present_rate);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
++	case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
++		value = get_unaligned_le32(&bat->bix.design_cap);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_CHARGE_FULL:
++	case POWER_SUPPLY_PROP_ENERGY_FULL:
++		value = get_unaligned_le32(&bat->bix.last_full_charge_cap);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_CHARGE_NOW:
++	case POWER_SUPPLY_PROP_ENERGY_NOW:
++		value = get_unaligned_le32(&bat->bst.remaining_cap);
++		if (value != SPWR_BATTERY_VALUE_UNKNOWN)
++			val->intval = value * 1000;
++		else
++			status = -ENODATA;
++		break;
++
++	case POWER_SUPPLY_PROP_CAPACITY:
++		val->intval = spwr_battery_prop_capacity(bat);
++		break;
++
++	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
++		val->intval = spwr_battery_prop_capacity_level(bat);
++		break;
++
++	case POWER_SUPPLY_PROP_MODEL_NAME:
++		val->strval = bat->bix.model;
++		break;
++
++	case POWER_SUPPLY_PROP_MANUFACTURER:
++		val->strval = bat->bix.oem_info;
++		break;
++
++	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
++		val->strval = bat->bix.serial;
++		break;
++
++	default:
++		status = -EINVAL;
++		break;
++	}
++
++out:
++	mutex_unlock(&bat->lock);
++	return status;
++}
++
++
++/* -- Alarm attribute. ------------------------------------------------------ */
++
++static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf)
++{
++	struct power_supply *psy = dev_get_drvdata(dev);
++	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
++	int status;
++
++	mutex_lock(&bat->lock);
++	status = sysfs_emit(buf, "%d\n", bat->alarm * 1000);
++	mutex_unlock(&bat->lock);
++
++	return status;
++}
++
++static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf,
++			   size_t count)
++{
++	struct power_supply *psy = dev_get_drvdata(dev);
++	struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
++	unsigned long value;
++	int status;
++
++	status = kstrtoul(buf, 0, &value);
++	if (status)
++		return status;
++
++	mutex_lock(&bat->lock);
++
++	if (!spwr_battery_present(bat)) {
++		mutex_unlock(&bat->lock);
++		return -ENODEV;
++	}
++
++	status = spwr_battery_set_alarm_unlocked(bat, value / 1000);
++	if (status) {
++		mutex_unlock(&bat->lock);
++		return status;
++	}
++
++	mutex_unlock(&bat->lock);
++	return count;
++}
++
++DEVICE_ATTR_RW(alarm);
++
++static struct attribute *spwr_battery_attrs[] = {
++	&dev_attr_alarm.attr,
++	NULL,
++};
++ATTRIBUTE_GROUPS(spwr_battery);
++
++
++/* -- Device setup. --------------------------------------------------------- */
++
++static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev,
++			      struct ssam_event_registry registry, const char *name)
++{
++	mutex_init(&bat->lock);
++	strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1);
++
++	bat->sdev = sdev;
++
++	bat->notif.base.priority = 1;
++	bat->notif.base.fn = spwr_notify_bat;
++	bat->notif.event.reg = registry;
++	bat->notif.event.id.target_category = sdev->uid.category;
++	bat->notif.event.id.instance = 0;
++	bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
++	bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
++
++	bat->psy_desc.name = bat->name;
++	bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
++	bat->psy_desc.get_property = spwr_battery_get_property;
++
++	INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn);
++}
++
++static int spwr_battery_register(struct spwr_battery_device *bat)
++{
++	struct power_supply_config psy_cfg = {};
++	__le32 sta;
++	int status;
++
++	/* Make sure the device is there and functioning properly. */
++	status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta);
++	if (status)
++		return status;
++
++	if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
++		return -ENODEV;
++
++	/* Satisfy lockdep although we are in an exclusive context here. */
++	mutex_lock(&bat->lock);
++
++	status = spwr_battery_update_bix_unlocked(bat);
++	if (status) {
++		mutex_unlock(&bat->lock);
++		return status;
++	}
++
++	if (spwr_battery_present(bat)) {
++		u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
++
++		status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
++		if (status) {
++			mutex_unlock(&bat->lock);
++			return status;
++		}
++	}
++
++	mutex_unlock(&bat->lock);
++
++	bat->psy_desc.external_power_changed = spwr_external_power_changed;
++
++	switch (get_unaligned_le32(&bat->bix.power_unit)) {
++	case SAM_BATTERY_POWER_UNIT_mW:
++		bat->psy_desc.properties = spwr_battery_props_eng;
++		bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng);
++		break;
++
++	case SAM_BATTERY_POWER_UNIT_mA:
++		bat->psy_desc.properties = spwr_battery_props_chg;
++		bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg);
++		break;
++
++	default:
++		dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n",
++			get_unaligned_le32(&bat->bix.power_unit));
++		return -EINVAL;
++	}
++
++	psy_cfg.drv_data = bat;
++	psy_cfg.attr_grp = spwr_battery_groups;
++
++	bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg);
++	if (IS_ERR(bat->psy))
++		return PTR_ERR(bat->psy);
++
++	return ssam_notifier_register(bat->sdev->ctrl, &bat->notif);
++}
++
++
++/* -- Driver setup. --------------------------------------------------------- */
++
++static int __maybe_unused surface_battery_resume(struct device *dev)
++{
++	return spwr_battery_recheck_full(dev_get_drvdata(dev));
++}
++SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
++
++static int surface_battery_probe(struct ssam_device *sdev)
++{
++	const struct spwr_psy_properties *p;
++	struct spwr_battery_device *bat;
++
++	p = ssam_device_get_match_data(sdev);
++	if (!p)
++		return -ENODEV;
++
++	bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL);
++	if (!bat)
++		return -ENOMEM;
++
++	spwr_battery_init(bat, sdev, p->registry, p->name);
++	ssam_device_set_drvdata(sdev, bat);
++
++	return spwr_battery_register(bat);
++}
++
++static void surface_battery_remove(struct ssam_device *sdev)
++{
++	struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev);
++
++	ssam_notifier_unregister(sdev->ctrl, &bat->notif);
++	cancel_delayed_work_sync(&bat->update_work);
++}
++
++static const struct spwr_psy_properties spwr_psy_props_bat1 = {
++	.name = "BAT1",
++	.registry = SSAM_EVENT_REGISTRY_SAM,
++};
++
++static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
++	.name = "BAT2",
++	.registry = SSAM_EVENT_REGISTRY_KIP,
++};
++
++static const struct ssam_device_id surface_battery_match[] = {
++	{ SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1     },
++	{ SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
++	{ },
++};
++MODULE_DEVICE_TABLE(ssam, surface_battery_match);
++
++static struct ssam_device_driver surface_battery_driver = {
++	.probe = surface_battery_probe,
++	.remove = surface_battery_remove,
++	.match_table = surface_battery_match,
++	.driver = {
++		.name = "surface_battery",
++		.pm = &surface_battery_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_ssam_device_driver(surface_battery_driver);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From 51bdf381a1d059ecefb2e13f663752c4016e6156 Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Tue, 6 Apr 2021 01:41:26 +0200
+Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module
+
+On newer Microsoft Surface models (specifically 7th-generation, i.e.
+Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
+battery and AC status/information is no longer handled via standard ACPI
+devices, but instead directly via the Surface System Aggregator Module
+(SSAM), i.e. the embedded controller on those devices.
+
+While on previous generation models, AC status is also handled via SSAM,
+an ACPI shim was present to translate the standard ACPI AC interface to
+SSAM requests. The SSAM interface itself, which is modeled closely after
+the ACPI interface, has not changed.
+
+This commit introduces a new SSAM client device driver to support AC
+status/information via the aforementioned interface on said Surface
+models.
+
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
+Patchset: surface-sam
+---
+ MAINTAINERS                            |   1 +
+ drivers/power/supply/Kconfig           |  16 ++
+ drivers/power/supply/Makefile          |   1 +
+ drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++
+ 4 files changed, 300 insertions(+)
+ create mode 100644 drivers/power/supply/surface_charger.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 7ee93b732270..710617e26f3e 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -11874,6 +11874,7 @@ L:	linux-pm@vger.kernel.org
+ L:	platform-driver-x86@vger.kernel.org
+ S:	Maintained
+ F:	drivers/power/supply/surface_battery.c
++F:	drivers/power/supply/surface_charger.c
+ 
+ MICROSOFT SURFACE DTX DRIVER
+ M:	Maximilian Luz <luzmaximilian@gmail.com>
+diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
+index cebeff10d543..91f7cf425ac9 100644
+--- a/drivers/power/supply/Kconfig
++++ b/drivers/power/supply/Kconfig
+@@ -817,4 +817,20 @@ config BATTERY_SURFACE
+ 	  Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
+ 	  Surface Book 3, and Surface Laptop Go.
+ 
++config CHARGER_SURFACE
++	tristate "AC driver for 7th-generation Microsoft Surface devices"
++	depends on SURFACE_AGGREGATOR_REGISTRY
++	help
++	  Driver for AC devices connected via/managed by the Surface System
++	  Aggregator Module (SSAM).
++
++	  This driver provides AC-information and -status support for Surface
++	  devices where said data is not exposed via the standard ACPI devices.
++	  On those models (7th-generation), AC-information is instead handled
++	  directly via a SSAM client device and this driver.
++
++	  Say M or Y here to include AC status support for 7th-generation
++	  Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
++	  Surface Book 3, and Surface Laptop Go.
++
+ endif # POWER_SUPPLY
+diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
+index 134041538d2c..a7309a3d1a47 100644
+--- a/drivers/power/supply/Makefile
++++ b/drivers/power/supply/Makefile
+@@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO)	+= wilco-charger.o
+ obj-$(CONFIG_RN5T618_POWER)	+= rn5t618_power.o
+ obj-$(CONFIG_BATTERY_ACER_A500)	+= acer_a500_battery.o
+ obj-$(CONFIG_BATTERY_SURFACE)	+= surface_battery.o
++obj-$(CONFIG_CHARGER_SURFACE)	+= surface_charger.o
+diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
+new file mode 100644
+index 000000000000..c2dd7e604d14
+--- /dev/null
++++ b/drivers/power/supply/surface_charger.c
+@@ -0,0 +1,282 @@
++// SPDX-License-Identifier: GPL-2.0+
++/*
++ * AC driver for 7th-generation Microsoft Surface devices via Surface System
++ * Aggregator Module (SSAM).
++ *
++ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
++ */
++
++#include <asm/unaligned.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/power_supply.h>
++#include <linux/types.h>
++
++#include <linux/surface_aggregator/device.h>
++
++
++/* -- SAM interface. -------------------------------------------------------- */
++
++enum sam_event_cid_bat {
++	SAM_EVENT_CID_BAT_ADP   = 0x17,
++};
++
++enum sam_battery_sta {
++	SAM_BATTERY_STA_OK      = 0x0f,
++	SAM_BATTERY_STA_PRESENT	= 0x10,
++};
++
++/* Get battery status (_STA). */
++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x01,
++});
++
++/* Get platform power source for battery (_PSR / DPTF PSRC). */
++SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, {
++	.target_category = SSAM_SSH_TC_BAT,
++	.command_id      = 0x0d,
++});
++
++
++/* -- Device structures. ---------------------------------------------------- */
++
++struct spwr_psy_properties {
++	const char *name;
++	struct ssam_event_registry registry;
++};
++
++struct spwr_ac_device {
++	struct ssam_device *sdev;
++
++	char name[32];
++	struct power_supply *psy;
++	struct power_supply_desc psy_desc;
++
++	struct ssam_event_notifier notif;
++
++	struct mutex lock;  /* Guards access to state below. */
++
++	__le32 state;
++};
++
++
++/* -- State management. ----------------------------------------------------- */
++
++static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
++{
++	u32 old = ac->state;
++	int status;
++
++	lockdep_assert_held(&ac->lock);
++
++	status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state);
++	if (status < 0)
++		return status;
++
++	return old != ac->state;
++}
++
++static int spwr_ac_update(struct spwr_ac_device *ac)
++{
++	int status;
++
++	mutex_lock(&ac->lock);
++	status = spwr_ac_update_unlocked(ac);
++	mutex_unlock(&ac->lock);
++
++	return status;
++}
++
++static int spwr_ac_recheck(struct spwr_ac_device *ac)
++{
++	int status;
++
++	status = spwr_ac_update(ac);
++	if (status > 0)
++		power_supply_changed(ac->psy);
++
++	return status >= 0 ? 0 : status;
++}
++
++static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event)
++{
++	struct spwr_ac_device *ac;
++	int status;
++
++	ac = container_of(nf, struct spwr_ac_device, notif);
++
++	dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
++		event->command_id, event->instance_id, event->target_id);
++
++	/*
++	 * Allow events of all targets/instances here. Global adapter status
++	 * seems to be handled via target=1 and instance=1, but events are
++	 * reported on all targets/instances in use.
++	 *
++	 * While it should be enough to just listen on 1/1, listen everywhere to
++	 * make sure we don't miss anything.
++	 */
++
++	switch (event->command_id) {
++	case SAM_EVENT_CID_BAT_ADP:
++		status = spwr_ac_recheck(ac);
++		return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
++
++	default:
++		return 0;
++	}
++}
++
++
++/* -- Properties. ----------------------------------------------------------- */
++
++static const enum power_supply_property spwr_ac_props[] = {
++	POWER_SUPPLY_PROP_ONLINE,
++};
++
++static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp,
++				union power_supply_propval *val)
++{
++	struct spwr_ac_device *ac = power_supply_get_drvdata(psy);
++	int status;
++
++	mutex_lock(&ac->lock);
++
++	status = spwr_ac_update_unlocked(ac);
++	if (status)
++		goto out;
++
++	switch (psp) {
++	case POWER_SUPPLY_PROP_ONLINE:
++		val->intval = !!le32_to_cpu(ac->state);
++		break;
++
++	default:
++		status = -EINVAL;
++		goto out;
++	}
++
++out:
++	mutex_unlock(&ac->lock);
++	return status;
++}
++
++
++/* -- Device setup. --------------------------------------------------------- */
++
++static char *battery_supplied_to[] = {
++	"BAT1",
++	"BAT2",
++};
++
++static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev,
++			 struct ssam_event_registry registry, const char *name)
++{
++	mutex_init(&ac->lock);
++	strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1);
++
++	ac->sdev = sdev;
++
++	ac->notif.base.priority = 1;
++	ac->notif.base.fn = spwr_notify_ac;
++	ac->notif.event.reg = registry;
++	ac->notif.event.id.target_category = sdev->uid.category;
++	ac->notif.event.id.instance = 0;
++	ac->notif.event.mask = SSAM_EVENT_MASK_NONE;
++	ac->notif.event.flags = SSAM_EVENT_SEQUENCED;
++
++	ac->psy_desc.name = ac->name;
++	ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
++	ac->psy_desc.properties = spwr_ac_props;
++	ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props);
++	ac->psy_desc.get_property = spwr_ac_get_property;
++}
++
++static int spwr_ac_register(struct spwr_ac_device *ac)
++{
++	struct power_supply_config psy_cfg = {};
++	__le32 sta;
++	int status;
++
++	/* Make sure the device is there and functioning properly. */
++	status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta);
++	if (status)
++		return status;
++
++	if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
++		return -ENODEV;
++
++	psy_cfg.drv_data = ac;
++	psy_cfg.supplied_to = battery_supplied_to;
++	psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
++
++	ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg);
++	if (IS_ERR(ac->psy))
++		return PTR_ERR(ac->psy);
++
++	return ssam_notifier_register(ac->sdev->ctrl, &ac->notif);
++}
++
++
++/* -- Driver setup. --------------------------------------------------------- */
++
++static int __maybe_unused surface_ac_resume(struct device *dev)
++{
++	return spwr_ac_recheck(dev_get_drvdata(dev));
++}
++SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
++
++static int surface_ac_probe(struct ssam_device *sdev)
++{
++	const struct spwr_psy_properties *p;
++	struct spwr_ac_device *ac;
++
++	p = ssam_device_get_match_data(sdev);
++	if (!p)
++		return -ENODEV;
++
++	ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL);
++	if (!ac)
++		return -ENOMEM;
++
++	spwr_ac_init(ac, sdev, p->registry, p->name);
++	ssam_device_set_drvdata(sdev, ac);
++
++	return spwr_ac_register(ac);
++}
++
++static void surface_ac_remove(struct ssam_device *sdev)
++{
++	struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev);
++
++	ssam_notifier_unregister(sdev->ctrl, &ac->notif);
++}
++
++static const struct spwr_psy_properties spwr_psy_props_adp1 = {
++	.name = "ADP1",
++	.registry = SSAM_EVENT_REGISTRY_SAM,
++};
++
++static const struct ssam_device_id surface_ac_match[] = {
++	{ SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
++	{ },
++};
++MODULE_DEVICE_TABLE(ssam, surface_ac_match);
++
++static struct ssam_device_driver surface_ac_driver = {
++	.probe = surface_ac_probe,
++	.remove = surface_ac_remove,
++	.match_table = surface_ac_match,
++	.driver = {
++		.name = "surface_ac",
++		.pm = &surface_ac_pm_ops,
++		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
++	},
++};
++module_ssam_device_driver(surface_ac_driver);
++
++MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
++MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From c10193b61e2b7cd895ad7447c61dad50609f9173 Mon Sep 17 00:00:00 2001
+From: Qiheng Lin <linqiheng@huawei.com>
+Date: Sat, 10 Apr 2021 12:12:46 +0800
+Subject: [PATCH] power: supply: surface-battery: Make some symbols static
+
+The sparse tool complains as follows:
+
+drivers/power/supply/surface_battery.c:700:1: warning:
+ symbol 'dev_attr_alarm' was not declared. Should it be static?
+drivers/power/supply/surface_battery.c:805:1: warning:
+ symbol 'surface_battery_pm_ops' was not declared. Should it be static?
+
+This symbol is not used outside of surface_battery.c, so this
+commit marks it static.
+
+Reported-by: Hulk Robot <hulkci@huawei.com>
+Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
+Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
+Patchset: surface-sam
+---
+ drivers/power/supply/surface_battery.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
+index 4116dd839ecd..7efa431a62b2 100644
+--- a/drivers/power/supply/surface_battery.c
++++ b/drivers/power/supply/surface_battery.c
+@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co
+ 	return count;
+ }
+ 
+-DEVICE_ATTR_RW(alarm);
++static DEVICE_ATTR_RW(alarm);
+ 
+ static struct attribute *spwr_battery_attrs[] = {
+ 	&dev_attr_alarm.attr,
+@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev)
+ {
+ 	return spwr_battery_recheck_full(dev_get_drvdata(dev));
+ }
+-SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
++static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
+ 
+ static int surface_battery_probe(struct ssam_device *sdev)
+ {
+-- 
+2.31.1
+
+From 37fd14bed9096ff15a7ea94e0dfd295f13a08ef0 Mon Sep 17 00:00:00 2001
+From: Qiheng Lin <linqiheng@huawei.com>
+Date: Sat, 10 Apr 2021 12:12:49 +0800
+Subject: [PATCH] power: supply: surface-charger: Make symbol
+ 'surface_ac_pm_ops' static
+
+The sparse tool complains as follows:
+
+drivers/power/supply/surface_charger.c:229:1: warning:
+ symbol 'surface_ac_pm_ops' was not declared. Should it be static?
+
+This symbol is not used outside of surface_charger.c, so this
+commit marks it static.
+
+Reported-by: Hulk Robot <hulkci@huawei.com>
+Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
+Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
+Patchset: surface-sam
+---
+ drivers/power/supply/surface_charger.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
+index c2dd7e604d14..81a5b79822c9 100644
+--- a/drivers/power/supply/surface_charger.c
++++ b/drivers/power/supply/surface_charger.c
+@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev)
+ {
+ 	return spwr_ac_recheck(dev_get_drvdata(dev));
+ }
+-SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
++static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
+ 
+ static int surface_ac_probe(struct ssam_device *sdev)
+ {
+-- 
+2.31.1
+
+From ee96c5d16365a59aa098ed7a328c6e3f9366514c Mon Sep 17 00:00:00 2001
+From: Maximilian Luz <luzmaximilian@gmail.com>
+Date: Tue, 4 May 2021 20:00:46 +0200
+Subject: [PATCH] power: supply: surface_battery: Fix battery event handling
+
+The battery subsystem of the Surface Aggregator Module EC requires us to
+register the battery notifier with instance ID 0. However, battery
+events are actually sent with the instance ID corresponding to the
+device, which is nonzero. Thus, the strict-matching approach doesn't
+work here and will discard events that the driver is expected to handle.
+
+To fix this we have to fall back on notifier matching by target-category
+only and have to manually check the instance ID in the notifier
+callback.
+
+Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module")
+Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
+Patchset: surface-sam
+---
+ drivers/power/supply/surface_battery.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
+index 7efa431a62b2..5ec2e6bb2465 100644
+--- a/drivers/power/supply/surface_battery.c
++++ b/drivers/power/supply/surface_battery.c
+@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve
+ 	struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
+ 	int status;
+ 
++	/*
++	 * We cannot use strict matching when registering the notifier as the
++	 * EC expects us to register it against instance ID 0. Strict matching
++	 * would thus drop events, as those may have non-zero instance IDs in
++	 * this subsystem. So we need to check the instance ID of the event
++	 * here manually.
++	 */
++	if (event->instance_id != bat->sdev->uid.instance)
++		return 0;
++
+ 	dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
+ 		event->command_id, event->instance_id, event->target_id);
+ 
+@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic
+ 	bat->notif.base.fn = spwr_notify_bat;
+ 	bat->notif.event.reg = registry;
+ 	bat->notif.event.id.target_category = sdev->uid.category;
+-	bat->notif.event.id.instance = 0;
+-	bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
++	bat->notif.event.id.instance = 0;	/* need to register with instance 0 */
++	bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
+ 	bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
+ 
+ 	bat->psy_desc.name = bat->name;
+-- 
+2.31.1
+

+ 68 - 0
patches/5.12/0007-surface-hotplug.patch

@@ -0,0 +1,68 @@
+From eabd9f96cf8a3bda4c73397fbcc6b7a0a04e8041 Mon Sep 17 00:00:00 2001
+From: "Rafael J. Wysocki" <rafael.j.wysocki@intel.com>
+Date: Tue, 16 Mar 2021 16:51:40 +0100
+Subject: [PATCH] PCI: PM: Do not read power state in pci_enable_device_flags()
+
+It should not be necessary to update the current_state field of
+struct pci_dev in pci_enable_device_flags() before calling
+do_pci_enable_device() for the device, because none of the
+code between that point and the pci_set_power_state() call in
+do_pci_enable_device() invoked later depends on it.
+
+Moreover, doing that is actively harmful in some cases.  For example,
+if the given PCI device depends on an ACPI power resource whose _STA
+method initially returns 0 ("off"), but the config space of the PCI
+device is accessible and the power state retrieved from the
+PCI_PM_CTRL register is D0, the current_state field in the struct
+pci_dev representing that device will get out of sync with the
+power.state of its ACPI companion object and that will lead to
+power management issues going forward.
+
+To avoid such issues it is better to leave the current_state value
+as is until it is changed to PCI_D0 by do_pci_enable_device() as
+appropriate.  However, the power state of the device is not changed
+to PCI_D0 if it is already enabled when pci_enable_device_flags()
+gets called for it, so update its current_state in that case, but
+use pci_update_current_state() covering platform PM too for that.
+
+Link: https://lore.kernel.org/lkml/20210314000439.3138941-1-luzmaximilian@gmail.com/
+Reported-by: Maximilian Luz <luzmaximilian@gmail.com>
+Tested-by: Maximilian Luz <luzmaximilian@gmail.com>
+Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
+Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Patchset: surface-hotplug
+---
+ drivers/pci/pci.c | 16 +++-------------
+ 1 file changed, 3 insertions(+), 13 deletions(-)
+
+diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c
+index 16a17215f633..e4d4e399004b 100644
+--- a/drivers/pci/pci.c
++++ b/drivers/pci/pci.c
+@@ -1870,20 +1870,10 @@ static int pci_enable_device_flags(struct pci_dev *dev, unsigned long flags)
+ 	int err;
+ 	int i, bars = 0;
+ 
+-	/*
+-	 * Power state could be unknown at this point, either due to a fresh
+-	 * boot or a device removal call.  So get the current power state
+-	 * so that things like MSI message writing will behave as expected
+-	 * (e.g. if the device really is in D0 at enable time).
+-	 */
+-	if (dev->pm_cap) {
+-		u16 pmcsr;
+-		pci_read_config_word(dev, dev->pm_cap + PCI_PM_CTRL, &pmcsr);
+-		dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
+-	}
+-
+-	if (atomic_inc_return(&dev->enable_cnt) > 1)
++	if (atomic_inc_return(&dev->enable_cnt) > 1) {
++		pci_update_current_state(dev, dev->current_state);
+ 		return 0;		/* already enabled */
++	}
+ 
+ 	bridge = pci_upstream_bridge(dev);
+ 	if (bridge)
+-- 
+2.31.1
+

+ 233 - 0
patches/5.12/0008-surface-typecover.patch

@@ -0,0 +1,233 @@
+From 449a4cc68d442a4551fa3aa435a0abcbe5f1fa52 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= <verdre@v0yd.nl>
+Date: Thu, 5 Nov 2020 13:09:45 +0100
+Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when
+ suspending
+
+The Type Cover for Microsoft Surface devices supports a special usb
+control request to disable or enable the built-in keyboard backlight.
+On Windows, this request happens when putting the device into suspend or
+resuming it, without it the backlight of the Type Cover will remain
+enabled for some time even though the computer is suspended, which looks
+weird to the user.
+
+So add support for this special usb control request to hid-multitouch,
+which is the driver that's handling the Type Cover.
+
+The reason we have to use a pm_notifier for this instead of the usual
+suspend/resume methods is that those won't get called in case the usb
+device is already autosuspended.
+
+Also, if the device is autosuspended, we have to briefly autoresume it
+in order to send the request. Doing that should be fine, the usb-core
+driver does something similar during suspend inside choose_wakeup().
+
+To make sure we don't send that request to every device but only to
+devices which support it, add a new quirk
+MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk
+is only enabled for the usb id of the Surface Pro 2017 Type Cover, which
+is where I confirmed that it's working.
+
+Patchset: surface-typecover
+---
+ drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++-
+ 1 file changed, 98 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c
+index 9d9f3e1bd5f4..800476dbc327 100644
+--- a/drivers/hid/hid-multitouch.c
++++ b/drivers/hid/hid-multitouch.c
+@@ -34,7 +34,10 @@
+ #include <linux/device.h>
+ #include <linux/hid.h>
+ #include <linux/module.h>
++#include <linux/pm_runtime.h>
+ #include <linux/slab.h>
++#include <linux/suspend.h>
++#include <linux/usb.h>
+ #include <linux/input/mt.h>
+ #include <linux/jiffies.h>
+ #include <linux/string.h>
+@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels");
+ MODULE_LICENSE("GPL");
+ 
+ #include "hid-ids.h"
++#include "usbhid/usbhid.h"
+ 
+ /* quirks to control the device */
+ #define MT_QUIRK_NOT_SEEN_MEANS_UP	BIT(0)
+@@ -70,12 +74,15 @@ MODULE_LICENSE("GPL");
+ #define MT_QUIRK_WIN8_PTP_BUTTONS	BIT(18)
+ #define MT_QUIRK_SEPARATE_APP_REPORT	BIT(19)
+ #define MT_QUIRK_FORCE_MULTI_INPUT	BIT(20)
++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT	BIT(21)
+ 
+ #define MT_INPUTMODE_TOUCHSCREEN	0x02
+ #define MT_INPUTMODE_TOUCHPAD		0x03
+ 
+ #define MT_BUTTONTYPE_CLICKPAD		0
+ 
++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE	0xff050086
++
+ enum latency_mode {
+ 	HID_LATENCY_NORMAL = 0,
+ 	HID_LATENCY_HIGH = 1,
+@@ -167,6 +174,8 @@ struct mt_device {
+ 
+ 	struct list_head applications;
+ 	struct list_head reports;
++
++	struct notifier_block pm_notifier;
+ };
+ 
+ static void mt_post_parse_default_settings(struct mt_device *td,
+@@ -208,6 +217,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app);
+ #define MT_CLS_GOOGLE				0x0111
+ #define MT_CLS_RAZER_BLADE_STEALTH		0x0112
+ #define MT_CLS_SMART_TECH			0x0113
++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER	0x0114
+ 
+ #define MT_DEFAULT_MAXCONTACT	10
+ #define MT_MAX_MAXCONTACT	250
+@@ -367,6 +377,16 @@ static const struct mt_class mt_classes[] = {
+ 			MT_QUIRK_CONTACT_CNT_ACCURATE |
+ 			MT_QUIRK_SEPARATE_APP_REPORT,
+ 	},
++	{ .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER,
++		.quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT |
++			MT_QUIRK_ALWAYS_VALID |
++			MT_QUIRK_IGNORE_DUPLICATES |
++			MT_QUIRK_HOVERING |
++			MT_QUIRK_CONTACT_CNT_ACCURATE |
++			MT_QUIRK_STICKY_FINGERS |
++			MT_QUIRK_WIN8_PTP_BUTTONS,
++		.export_all_inputs = true
++	},
+ 	{ }
+ };
+ 
+@@ -1674,6 +1694,69 @@ static void mt_expired_timeout(struct timer_list *t)
+ 	clear_bit(MT_IO_FLAGS_RUNNING, &td->mt_io_flags);
+ }
+ 
++static void get_type_cover_backlight_field(struct hid_device *hdev,
++					   struct hid_field **field)
++{
++	struct hid_report_enum *rep_enum;
++	struct hid_report *rep;
++	struct hid_field *cur_field;
++	int i, j;
++
++	rep_enum = &hdev->report_enum[HID_FEATURE_REPORT];
++	list_for_each_entry(rep, &rep_enum->report_list, list) {
++		for (i = 0; i < rep->maxfield; i++) {
++			cur_field = rep->field[i];
++
++			for (j = 0; j < cur_field->maxusage; j++) {
++				if (cur_field->usage[j].hid
++				    == MS_TYPE_COVER_FEATURE_REPORT_USAGE) {
++					*field = cur_field;
++					return;
++				}
++			}
++		}
++	}
++}
++
++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled)
++{
++	struct usb_device *udev = hid_to_usb_dev(hdev);
++	struct hid_field *field = NULL;
++
++	/* Wake up the device in case it's already suspended */
++	pm_runtime_get_sync(&udev->dev);
++
++	get_type_cover_backlight_field(hdev, &field);
++	if (!field) {
++		hid_err(hdev, "couldn't find backlight field\n");
++		goto out;
++	}
++
++	field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff;
++	hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT);
++
++out:
++	pm_runtime_put_sync(&udev->dev);
++}
++
++static int mt_pm_notifier(struct notifier_block *notifier,
++			  unsigned long pm_event,
++			  void *unused)
++{
++	struct mt_device *td =
++		container_of(notifier, struct mt_device, pm_notifier);
++	struct hid_device *hdev = td->hdev;
++
++	if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) {
++		if (pm_event == PM_SUSPEND_PREPARE)
++			update_keyboard_backlight(hdev, 0);
++		else if (pm_event == PM_POST_SUSPEND)
++			update_keyboard_backlight(hdev, 1);
++	}
++
++	return NOTIFY_DONE;
++}
++
+ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
+ {
+ 	int ret, i;
+@@ -1697,6 +1780,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
+ 	td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN;
+ 	hid_set_drvdata(hdev, td);
+ 
++	td->pm_notifier.notifier_call = mt_pm_notifier;
++	register_pm_notifier(&td->pm_notifier);
++
+ 	INIT_LIST_HEAD(&td->applications);
+ 	INIT_LIST_HEAD(&td->reports);
+ 
+@@ -1726,15 +1812,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id)
+ 	timer_setup(&td->release_timer, mt_expired_timeout, 0);
+ 
+ 	ret = hid_parse(hdev);
+-	if (ret != 0)
++	if (ret != 0) {
++		unregister_pm_notifier(&td->pm_notifier);
+ 		return ret;
++	}
+ 
+ 	if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID)
+ 		mt_fix_const_fields(hdev, HID_DG_CONTACTID);
+ 
+ 	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+-	if (ret)
++	if (ret) {
++		unregister_pm_notifier(&td->pm_notifier);
+ 		return ret;
++	}
+ 
+ 	ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group);
+ 	if (ret)
+@@ -1779,6 +1869,7 @@ static void mt_remove(struct hid_device *hdev)
+ {
+ 	struct mt_device *td = hid_get_drvdata(hdev);
+ 
++	unregister_pm_notifier(&td->pm_notifier);
+ 	del_timer_sync(&td->release_timer);
+ 
+ 	sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group);
+@@ -2130,6 +2221,11 @@ static const struct hid_device_id mt_devices[] = {
+ 		MT_USB_DEVICE(USB_VENDOR_ID_XIROKU,
+ 			USB_DEVICE_ID_XIROKU_CSR2) },
+ 
++	/* Microsoft Surface type cover */
++	{ .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER,
++		HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY,
++			USB_VENDOR_ID_MICROSOFT, 0x09c0) },
++
+ 	/* Google MT devices */
+ 	{ .driver_data = MT_CLS_GOOGLE,
+ 		HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE,
+-- 
+2.31.1
+

+ 3764 - 0
patches/5.12/0009-cameras.patch

@@ -0,0 +1,3764 @@
+From c10c028151ea853e1529ad7d828a9600b4060825 Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:30 +0000
+Subject: [PATCH] ACPI: scan: Extend acpi_walk_dep_device_list()
+
+The acpi_walk_dep_device_list() is not as generalisable as its name
+implies, serving only to decrement the dependency count for each
+dependent device of the input. Extend the function to instead accept
+a callback which can be applied to all the dependencies in acpi_dep_list.
+Replace all existing calls to the function with calls to a wrapper, passing
+a callback that applies the same dependency reduction.
+
+Suggested-by: Rafael J. Wysocki <rafael@kernel.org>
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Acked-by: Wolfram Sang <wsa@kernel.org> # for changing I2C core
+Patchset: cameras
+---
+ drivers/acpi/ec.c                             |  2 +-
+ drivers/acpi/pmic/intel_pmic_chtdc_ti.c       |  2 +-
+ drivers/acpi/scan.c                           | 58 +++++++++++++------
+ drivers/gpio/gpiolib-acpi.c                   |  2 +-
+ drivers/i2c/i2c-core-acpi.c                   |  2 +-
+ drivers/platform/surface/aggregator/core.c    |  2 +-
+ drivers/platform/surface/surface3_power.c     |  2 +-
+ .../platform/surface/surface_acpi_notify.c    |  2 +-
+ include/acpi/acpi_bus.h                       |  7 +++
+ include/linux/acpi.h                          |  4 +-
+ 10 files changed, 57 insertions(+), 26 deletions(-)
+
+diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c
+index 13565629ce0a..a258db713bd2 100644
+--- a/drivers/acpi/ec.c
++++ b/drivers/acpi/ec.c
+@@ -1627,7 +1627,7 @@ static int acpi_ec_add(struct acpi_device *device)
+ 	WARN(!ret, "Could not request EC cmd io port 0x%lx", ec->command_addr);
+ 
+ 	/* Reprobe devices depending on the EC */
+-	acpi_walk_dep_device_list(ec->handle);
++	acpi_dev_flag_dependency_met(ec->handle);
+ 
+ 	acpi_handle_debug(ec->handle, "enumerated.\n");
+ 	return 0;
+diff --git a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c
+index a5101b07611a..59cca504325e 100644
+--- a/drivers/acpi/pmic/intel_pmic_chtdc_ti.c
++++ b/drivers/acpi/pmic/intel_pmic_chtdc_ti.c
+@@ -117,7 +117,7 @@ static int chtdc_ti_pmic_opregion_probe(struct platform_device *pdev)
+ 		return err;
+ 
+ 	/* Re-enumerate devices depending on PMIC */
+-	acpi_walk_dep_device_list(ACPI_HANDLE(pdev->dev.parent));
++	acpi_dev_flag_dependency_met(ACPI_HANDLE(pdev->dev.parent));
+ 	return 0;
+ }
+ 
+diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
+index 6efe7edd7b1e..44e4783d8e95 100644
+--- a/drivers/acpi/scan.c
++++ b/drivers/acpi/scan.c
+@@ -47,12 +47,6 @@ static DEFINE_MUTEX(acpi_hp_context_lock);
+  */
+ static u64 spcr_uart_addr;
+ 
+-struct acpi_dep_data {
+-	struct list_head node;
+-	acpi_handle supplier;
+-	acpi_handle consumer;
+-};
+-
+ void acpi_scan_lock_acquire(void)
+ {
+ 	mutex_lock(&acpi_scan_lock);
+@@ -2141,30 +2135,58 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass)
+ 		device->handler->hotplug.notify_online(device);
+ }
+ 
+-void acpi_walk_dep_device_list(acpi_handle handle)
++static int __acpi_dev_flag_dependency_met(struct acpi_dep_data *dep,
++					  void *data)
+ {
+-	struct acpi_dep_data *dep, *tmp;
+ 	struct acpi_device *adev;
+ 
++	acpi_bus_get_device(dep->consumer, &adev);
++
++	if (adev) {
++		adev->dep_unmet--;
++		if (!adev->dep_unmet)
++			acpi_bus_attach(adev, true);
++	}
++
++	list_del(&dep->node);
++	kfree(dep);
++	return 0;
++}
++
++void acpi_walk_dep_device_list(acpi_handle handle,
++			       int (*callback)(struct acpi_dep_data *, void *),
++			       void *data)
++{
++	struct acpi_dep_data *dep, *tmp;
++	int ret;
++
+ 	mutex_lock(&acpi_dep_list_lock);
+ 	list_for_each_entry_safe(dep, tmp, &acpi_dep_list, node) {
+ 		if (dep->supplier == handle) {
+-			acpi_bus_get_device(dep->consumer, &adev);
+-
+-			if (adev) {
+-				adev->dep_unmet--;
+-				if (!adev->dep_unmet)
+-					acpi_bus_attach(adev, true);
+-			}
+-
+-			list_del(&dep->node);
+-			kfree(dep);
++			ret = callback(dep, data);
++			if (ret)
++				break;
+ 		}
+ 	}
+ 	mutex_unlock(&acpi_dep_list_lock);
+ }
+ EXPORT_SYMBOL_GPL(acpi_walk_dep_device_list);
+ 
++/**
++ * acpi_dev_flag_dependency_met() - Inform consumers of @handle that the device
++ *				    is now active
++ * @handle: acpi_handle for the supplier device
++ *
++ * This function walks through the dependencies list and informs each consumer
++ * of @handle that their dependency upon it is now met. Devices with no more
++ * unmet dependencies will be attached to the acpi bus.
++ */
++void acpi_dev_flag_dependency_met(acpi_handle handle)
++{
++	acpi_walk_dep_device_list(handle, __acpi_dev_flag_dependency_met, NULL);
++}
++EXPORT_SYMBOL_GPL(acpi_dev_flag_dependency_met);
++
+ /**
+  * acpi_bus_scan - Add ACPI device node objects in a given namespace scope.
+  * @handle: Root of the namespace scope to scan.
+diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
+index 1aacd2a5a1fd..4a16e0d9b257 100644
+--- a/drivers/gpio/gpiolib-acpi.c
++++ b/drivers/gpio/gpiolib-acpi.c
+@@ -1263,7 +1263,7 @@ void acpi_gpiochip_add(struct gpio_chip *chip)
+ 
+ 	acpi_gpiochip_request_regions(acpi_gpio);
+ 	acpi_gpiochip_scan_gpios(acpi_gpio);
+-	acpi_walk_dep_device_list(handle);
++	acpi_dev_flag_dependency_met(handle);
+ }
+ 
+ void acpi_gpiochip_remove(struct gpio_chip *chip)
+diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c
+index deceed0d76c6..1e7ae3421de1 100644
+--- a/drivers/i2c/i2c-core-acpi.c
++++ b/drivers/i2c/i2c-core-acpi.c
+@@ -279,7 +279,7 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap)
+ 	if (!handle)
+ 		return;
+ 
+-	acpi_walk_dep_device_list(handle);
++	acpi_dev_flag_dependency_met(handle);
+ }
+ 
+ static const struct acpi_device_id i2c_acpi_force_400khz_device_ids[] = {
+diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c
+index 8dc2c267bcd6..f8c691720caf 100644
+--- a/drivers/platform/surface/aggregator/core.c
++++ b/drivers/platform/surface/aggregator/core.c
+@@ -706,7 +706,7 @@ static int ssam_serial_hub_probe(struct serdev_device *serdev)
+ 	 *       For now let's thus default power/wakeup to false.
+ 	 */
+ 	device_set_wakeup_capable(&serdev->dev, true);
+-	acpi_walk_dep_device_list(ssh);
++	acpi_dev_flag_dependency_met(ssh);
+ 
+ 	return 0;
+ 
+diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c
+index cc4f9cba6856..ad895285d3e9 100644
+--- a/drivers/platform/surface/surface3_power.c
++++ b/drivers/platform/surface/surface3_power.c
+@@ -478,7 +478,7 @@ static int mshw0011_install_space_handler(struct i2c_client *client)
+ 		return -ENOMEM;
+ 	}
+ 
+-	acpi_walk_dep_device_list(handle);
++	acpi_dev_flag_dependency_met(handle);
+ 	return 0;
+ }
+ 
+diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c
+index ef9c1f8e8336..e18dc4e30af2 100644
+--- a/drivers/platform/surface/surface_acpi_notify.c
++++ b/drivers/platform/surface/surface_acpi_notify.c
+@@ -835,7 +835,7 @@ static int san_probe(struct platform_device *pdev)
+ 	if (status)
+ 		goto err_install_dev;
+ 
+-	acpi_walk_dep_device_list(san);
++	acpi_dev_flag_dependency_met(san);
+ 	return 0;
+ 
+ err_install_dev:
+diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
+index f28b097c658f..128eec2a26f6 100644
+--- a/include/acpi/acpi_bus.h
++++ b/include/acpi/acpi_bus.h
+@@ -279,6 +279,12 @@ struct acpi_device_power {
+ 	struct acpi_device_power_state states[ACPI_D_STATE_COUNT];	/* Power states (D0-D3Cold) */
+ };
+ 
++struct acpi_dep_data {
++	struct list_head node;
++	acpi_handle supplier;
++	acpi_handle consumer;
++};
++
+ /* Performance Management */
+ 
+ struct acpi_device_perf_flags {
+@@ -684,6 +690,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev)
+ 
+ bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2);
+ 
++void acpi_dev_flag_dependency_met(acpi_handle handle);
+ struct acpi_device *
+ acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv);
+ struct acpi_device *
+diff --git a/include/linux/acpi.h b/include/linux/acpi.h
+index 3bdcfc4401b7..b560e47b700c 100644
+--- a/include/linux/acpi.h
++++ b/include/linux/acpi.h
+@@ -666,7 +666,9 @@ extern bool acpi_driver_match_device(struct device *dev,
+ 				     const struct device_driver *drv);
+ int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *);
+ int acpi_device_modalias(struct device *, char *, int);
+-void acpi_walk_dep_device_list(acpi_handle handle);
++void acpi_walk_dep_device_list(acpi_handle handle,
++			       int (*callback)(struct acpi_dep_data *, void *),
++			       void *data);
+ 
+ struct platform_device *acpi_create_platform_device(struct acpi_device *,
+ 						    struct property_entry *);
+-- 
+2.31.1
+
+From 48ca31919b84fcc1456e0f86292b95bda1a74b81 Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:31 +0000
+Subject: [PATCH] ACPI: scan: Add function to fetch dependent of acpi device
+
+In some ACPI tables we encounter, devices use the _DEP method to assert
+a dependence on other ACPI devices as opposed to the OpRegions that the
+specification intends. We need to be able to find those devices "from"
+the dependee, so add a callback and a wrapper to walk over the
+acpi_dep_list and return the dependent ACPI device.
+
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Patchset: cameras
+---
+ drivers/acpi/scan.c     | 34 ++++++++++++++++++++++++++++++++++
+ include/acpi/acpi_bus.h |  1 +
+ 2 files changed, 35 insertions(+)
+
+diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
+index 44e4783d8e95..a4735a479249 100644
+--- a/drivers/acpi/scan.c
++++ b/drivers/acpi/scan.c
+@@ -2135,6 +2135,21 @@ static void acpi_bus_attach(struct acpi_device *device, bool first_pass)
+ 		device->handler->hotplug.notify_online(device);
+ }
+ 
++static int __acpi_dev_get_dependent_dev(struct acpi_dep_data *dep, void *data)
++{
++	struct acpi_device *adev;
++	int ret;
++
++	ret = acpi_bus_get_device(dep->consumer, &adev);
++	if (ret)
++		/* If we don't find an adev then we want to continue parsing */
++		return 0;
++
++	*(struct acpi_device **)data = adev;
++
++	return 1;
++}
++
+ static int __acpi_dev_flag_dependency_met(struct acpi_dep_data *dep,
+ 					  void *data)
+ {
+@@ -2187,6 +2202,25 @@ void acpi_dev_flag_dependency_met(acpi_handle handle)
+ }
+ EXPORT_SYMBOL_GPL(acpi_dev_flag_dependency_met);
+ 
++/**
++ * acpi_dev_get_dependent_dev - Return ACPI device dependent on @adev
++ * @adev: Pointer to the dependee device
++ *
++ * Returns the first &struct acpi_device which declares itself dependent on
++ * @adev via the _DEP buffer, parsed from the acpi_dep_list.
++ */
++struct acpi_device *
++acpi_dev_get_dependent_dev(struct acpi_device *supplier)
++{
++	struct acpi_device *adev = NULL;
++
++	acpi_walk_dep_device_list(supplier->handle,
++				  __acpi_dev_get_dependent_dev, &adev);
++
++	return adev;
++}
++EXPORT_SYMBOL_GPL(acpi_dev_get_dependent_dev);
++
+ /**
+  * acpi_bus_scan - Add ACPI device node objects in a given namespace scope.
+  * @handle: Root of the namespace scope to scan.
+diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
+index 128eec2a26f6..d00dec1ad9fb 100644
+--- a/include/acpi/acpi_bus.h
++++ b/include/acpi/acpi_bus.h
+@@ -691,6 +691,7 @@ static inline bool acpi_device_can_poweroff(struct acpi_device *adev)
+ bool acpi_dev_hid_uid_match(struct acpi_device *adev, const char *hid2, const char *uid2);
+ 
+ void acpi_dev_flag_dependency_met(acpi_handle handle);
++struct acpi_device *acpi_dev_get_dependent_dev(struct acpi_device *supplier);
+ struct acpi_device *
+ acpi_dev_get_next_match_dev(struct acpi_device *adev, const char *hid, const char *uid, s64 hrv);
+ struct acpi_device *
+-- 
+2.31.1
+
+From c861065d78420ea39fec121e4cd9081fde790aa8 Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:32 +0000
+Subject: [PATCH] i2c: core: Add a format macro for I2C device names
+
+Some places in the kernel allow users to map resources to a device
+using device name (for example, in the struct gpiod_lookup_table).
+Currently this involves waiting for the I2C client to have been registered
+so we can use dev_name(&client->dev). We want to add a function to allow
+users to refer to an I2C device by name before it has been instantiated,
+so create a macro for the format that's accessible outside the I2C layer
+and use it in i2c_dev_set_name().
+
+Suggested-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Reviewed-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Acked-by: Wolfram Sang <wsa@kernel.org> # for changing I2C core
+Patchset: cameras
+---
+ drivers/i2c/i2c-core-base.c | 4 ++--
+ include/linux/i2c.h         | 3 +++
+ 2 files changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
+index f21362355973..e2cf16f27d65 100644
+--- a/drivers/i2c/i2c-core-base.c
++++ b/drivers/i2c/i2c-core-base.c
+@@ -812,12 +812,12 @@ static void i2c_dev_set_name(struct i2c_adapter *adap,
+ 	struct acpi_device *adev = ACPI_COMPANION(&client->dev);
+ 
+ 	if (info && info->dev_name) {
+-		dev_set_name(&client->dev, "i2c-%s", info->dev_name);
++		dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, info->dev_name);
+ 		return;
+ 	}
+ 
+ 	if (adev) {
+-		dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev));
++		dev_set_name(&client->dev, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev));
+ 		return;
+ 	}
+ 
+diff --git a/include/linux/i2c.h b/include/linux/i2c.h
+index 56622658b215..4d40a4b46810 100644
+--- a/include/linux/i2c.h
++++ b/include/linux/i2c.h
+@@ -39,6 +39,9 @@ enum i2c_slave_event;
+ typedef int (*i2c_slave_cb_t)(struct i2c_client *client,
+ 			      enum i2c_slave_event event, u8 *val);
+ 
++/* I2C Device Name Format - to maintain consistency outside the i2c layer */
++#define I2C_DEV_NAME_FORMAT		"i2c-%s"
++
+ /* I2C Frequency Modes */
+ #define I2C_MAX_STANDARD_MODE_FREQ	100000
+ #define I2C_MAX_FAST_MODE_FREQ		400000
+-- 
+2.31.1
+
+From e39f06db39a488efd95244f07c02672e510edc25 Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:33 +0000
+Subject: [PATCH] gpiolib: acpi: Export acpi_get_gpiod()
+
+I need to be able to translate GPIO resources in an ACPI device's _CRS
+into GPIO descriptor array. Those are represented in _CRS as a pathname
+to a GPIO device plus the pin's index number: this function is perfect
+for that purpose.
+
+As it's currently only used internally within the GPIO layer, provide and
+export a wrapper function that additionally holds a reference to the GPIO
+device.
+
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Patchset: cameras
+---
+ drivers/gpio/gpiolib-acpi.c   | 36 +++++++++++++++++++++++++++++++----
+ include/linux/gpio/consumer.h |  7 +++++++
+ 2 files changed, 39 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/gpio/gpiolib-acpi.c b/drivers/gpio/gpiolib-acpi.c
+index 4a16e0d9b257..5f99d698f60d 100644
+--- a/drivers/gpio/gpiolib-acpi.c
++++ b/drivers/gpio/gpiolib-acpi.c
+@@ -102,7 +102,8 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data)
+ }
+ 
+ /**
+- * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with GPIO API
++ * __acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with
++ *			GPIO API
+  * @path:	ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1")
+  * @pin:	ACPI GPIO pin number (0-based, controller-relative)
+  *
+@@ -111,7 +112,7 @@ static int acpi_gpiochip_find(struct gpio_chip *gc, void *data)
+  * controller does not have GPIO chip registered at the moment. This is to
+  * support probe deferral.
+  */
+-static struct gpio_desc *acpi_get_gpiod(char *path, int pin)
++static struct gpio_desc *__acpi_get_gpiod(char *path, int pin)
+ {
+ 	struct gpio_chip *chip;
+ 	acpi_handle handle;
+@@ -128,6 +129,33 @@ static struct gpio_desc *acpi_get_gpiod(char *path, int pin)
+ 	return gpiochip_get_desc(chip, pin);
+ }
+ 
++/**
++ * acpi_get_gpiod() - Translate ACPI GPIO pin to GPIO descriptor usable with
++ *		      GPIO API, and hold a refcount to the GPIO device.
++ * @path:	ACPI GPIO controller full path name, (e.g. "\\_SB.GPO1")
++ * @pin:	ACPI GPIO pin number (0-based, controller-relative)
++ * @label:	Label to pass to gpiod_request()
++ *
++ * This function is a simple pass-through to __acpi_get_gpiod(), except that as
++ * it is intended for use outside of the GPIO layer (in a similar fashion to
++ * gpiod_get_index() for example) it also holds a reference to the GPIO device.
++ */
++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label)
++{
++	struct gpio_desc *gpio = __acpi_get_gpiod(path, pin);
++	int ret;
++
++	if (IS_ERR(gpio))
++		return gpio;
++
++	ret = gpiod_request(gpio, label);
++	if (ret)
++		return ERR_PTR(ret);
++
++	return gpio;
++}
++EXPORT_SYMBOL_GPL(acpi_get_gpiod);
++
+ static irqreturn_t acpi_gpio_irq_handler(int irq, void *data)
+ {
+ 	struct acpi_gpio_event *event = data;
+@@ -693,8 +721,8 @@ static int acpi_populate_gpio_lookup(struct acpi_resource *ares, void *data)
+ 		if (lookup->info.quirks & ACPI_GPIO_QUIRK_ABSOLUTE_NUMBER)
+ 			desc = gpio_to_desc(agpio->pin_table[pin_index]);
+ 		else
+-			desc = acpi_get_gpiod(agpio->resource_source.string_ptr,
+-					      agpio->pin_table[pin_index]);
++			desc = __acpi_get_gpiod(agpio->resource_source.string_ptr,
++						agpio->pin_table[pin_index]);
+ 		lookup->desc = desc;
+ 		lookup->info.pin_config = agpio->pin_config;
+ 		lookup->info.debounce = agpio->debounce_timeout;
+diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h
+index c73b25bc9213..e26fb586b6c8 100644
+--- a/include/linux/gpio/consumer.h
++++ b/include/linux/gpio/consumer.h
+@@ -692,6 +692,8 @@ int devm_acpi_dev_add_driver_gpios(struct device *dev,
+ 				   const struct acpi_gpio_mapping *gpios);
+ void devm_acpi_dev_remove_driver_gpios(struct device *dev);
+ 
++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label);
++
+ #else  /* CONFIG_GPIOLIB && CONFIG_ACPI */
+ 
+ struct acpi_device;
+@@ -710,6 +712,11 @@ static inline int devm_acpi_dev_add_driver_gpios(struct device *dev,
+ }
+ static inline void devm_acpi_dev_remove_driver_gpios(struct device *dev) {}
+ 
++struct gpio_desc *acpi_get_gpiod(char *path, int pin, char *label)
++{
++	return NULL;
++}
++
+ #endif /* CONFIG_GPIOLIB && CONFIG_ACPI */
+ 
+ 
+-- 
+2.31.1
+
+From 478ecf8703bdcda8aab6cffc6a25b853fe00556f Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:34 +0000
+Subject: [PATCH] platform/x86: Add intel_skl_int3472 driver
+
+ACPI devices with _HID INT3472 are currently matched to the tps68470
+driver, however this does not cover all situations in which that _HID
+occurs. We've encountered three possibilities:
+
+1. On Chrome OS devices, an ACPI device with _HID INT3472 (representing
+a physical TPS68470 device) that requires a GPIO and OpRegion driver
+2. On devices designed for Windows, an ACPI device with _HID INT3472
+(again representing a physical TPS68470 device) which requires GPIO,
+Clock and Regulator drivers.
+3. On other devices designed for Windows, an ACPI device with _HID
+INT3472 which does **not** represent a physical TPS68470, and is instead
+used as a dummy device to group some system GPIO lines which are meant
+to be consumed by the sensor that is dependent on this entry.
+
+This commit adds a new module, registering a platform driver to deal
+with the 3rd scenario plus an i2c driver to deal with #1 and #2, by
+querying the CLDB buffer found against INT3472 entries to determine
+which is most appropriate.
+
+Suggested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Patchset: cameras
+---
+ MAINTAINERS                                   |   5 +
+ drivers/platform/x86/Kconfig                  |   2 +
+ drivers/platform/x86/Makefile                 |   1 +
+ drivers/platform/x86/intel-int3472/Kconfig    |  31 +
+ drivers/platform/x86/intel-int3472/Makefile   |   4 +
+ .../intel-int3472/intel_skl_int3472_common.c  | 106 ++++
+ .../intel-int3472/intel_skl_int3472_common.h  | 110 ++++
+ .../intel_skl_int3472_discrete.c              | 592 ++++++++++++++++++
+ .../intel_skl_int3472_tps68470.c              | 113 ++++
+ 9 files changed, 964 insertions(+)
+ create mode 100644 drivers/platform/x86/intel-int3472/Kconfig
+ create mode 100644 drivers/platform/x86/intel-int3472/Makefile
+ create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c
+ create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h
+ create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c
+ create mode 100644 drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 710617e26f3e..2a421d2e4b07 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -9191,6 +9191,11 @@ S:	Maintained
+ F:	arch/x86/include/asm/intel_scu_ipc.h
+ F:	drivers/platform/x86/intel_scu_*
+ 
++INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER
++M:	Daniel Scally <djrscally@gmail.com>
++S:	Maintained
++F:	drivers/platform/x86/intel-int3472/intel_skl_int3472_*
++
+ INTEL SPEED SELECT TECHNOLOGY
+ M:	Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ L:	platform-driver-x86@vger.kernel.org
+diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
+index 461ec61530eb..51b258120c50 100644
+--- a/drivers/platform/x86/Kconfig
++++ b/drivers/platform/x86/Kconfig
+@@ -674,6 +674,8 @@ config INTEL_CHT_INT33FE
+ 	  device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m
+ 	  for Type-C device.
+ 
++source "drivers/platform/x86/intel-int3472/Kconfig"
++
+ config INTEL_HID_EVENT
+ 	tristate "INTEL HID Event"
+ 	depends on ACPI
+diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
+index 60d554073749..4cbcff47a571 100644
+--- a/drivers/platform/x86/Makefile
++++ b/drivers/platform/x86/Makefile
+@@ -72,6 +72,7 @@ obj-$(CONFIG_INTEL_HID_EVENT)		+= intel-hid.o
+ obj-$(CONFIG_INTEL_INT0002_VGPIO)	+= intel_int0002_vgpio.o
+ obj-$(CONFIG_INTEL_MENLOW)		+= intel_menlow.o
+ obj-$(CONFIG_INTEL_OAKTRAIL)		+= intel_oaktrail.o
++obj-$(CONFIG_INTEL_SKL_INT3472)		+= intel-int3472/
+ obj-$(CONFIG_INTEL_VBTN)		+= intel-vbtn.o
+ 
+ # MSI
+diff --git a/drivers/platform/x86/intel-int3472/Kconfig b/drivers/platform/x86/intel-int3472/Kconfig
+new file mode 100644
+index 000000000000..b94622245c21
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/Kconfig
+@@ -0,0 +1,31 @@
++config INTEL_SKL_INT3472
++	tristate "Intel SkyLake ACPI INT3472 Driver"
++	depends on ACPI
++	depends on REGULATOR
++	depends on GPIOLIB
++	depends on COMMON_CLK && CLKDEV_LOOKUP
++	depends on I2C
++	select MFD_CORE
++	select REGMAP_I2C
++	help
++	  This driver adds support for the INT3472 ACPI devices found on some
++	  Intel SkyLake devices.
++
++	  The INT3472 is an Intel camera power controller, a logical device
++	  found on some Skylake-based systems that can map to different
++	  hardware devices depending on the platform. On machines
++	  designed for Chrome OS, it maps to a TPS68470 camera PMIC. On
++	  machines designed for Windows, it maps to either a TP68470
++	  camera PMIC, a uP6641Q sensor PMIC, or a set of discrete GPIOs
++	  and power gates.
++
++	  If your device was designed for Chrome OS, this driver will provide
++	  an ACPI OpRegion, which must be available before any of the devices
++	  using it are probed. For this reason, you should select Y if your
++	  device was designed for ChromeOS. For the same reason the
++	  I2C_DESIGNWARE_PLATFORM option must be set to Y too.
++
++	  Say Y or M here if you have a SkyLake device designed for use
++	  with Windows or ChromeOS. Say N here if you are not sure.
++
++	  The module will be named "intel-skl-int3472"
+diff --git a/drivers/platform/x86/intel-int3472/Makefile b/drivers/platform/x86/intel-int3472/Makefile
+new file mode 100644
+index 000000000000..c887ee7d52ca
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/Makefile
+@@ -0,0 +1,4 @@
++obj-$(CONFIG_INTEL_SKL_INT3472)		+= intel_skl_int3472.o
++intel_skl_int3472-objs			:= intel_skl_int3472_common.o \
++					   intel_skl_int3472_discrete.o \
++					   intel_skl_int3472_tps68470.o
+diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c
+new file mode 100644
+index 000000000000..f61166b6c497
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.c
+@@ -0,0 +1,106 @@
++// SPDX-License-Identifier: GPL-2.0
++/* Author: Dan Scally <djrscally@gmail.com> */
++
++#include <linux/acpi.h>
++#include <linux/i2c.h>
++#include <linux/platform_device.h>
++#include <linux/slab.h>
++
++#include "intel_skl_int3472_common.h"
++
++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev,
++					       char *id)
++{
++	struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
++	acpi_handle handle = adev->handle;
++	union acpi_object *obj;
++	acpi_status status;
++
++	status = acpi_evaluate_object(handle, id, NULL, &buffer);
++	if (ACPI_FAILURE(status))
++		return ERR_PTR(-ENODEV);
++
++	obj = buffer.pointer;
++	if (!obj)
++		return ERR_PTR(-ENODEV);
++
++	if (obj->type != ACPI_TYPE_BUFFER) {
++		dev_err(&adev->dev, "%s object is not an ACPI buffer\n", id);
++		kfree(obj);
++		return ERR_PTR(-EINVAL);
++	}
++
++	return obj;
++}
++
++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb)
++{
++	union acpi_object *obj;
++	int ret = 0;
++
++	obj = skl_int3472_get_acpi_buffer(adev, "CLDB");
++	if (IS_ERR(obj))
++		return PTR_ERR(obj);
++
++	if (obj->buffer.length > sizeof(*cldb)) {
++		dev_err(&adev->dev, "The CLDB buffer is too large\n");
++		ret = -EINVAL;
++		goto out_free_obj;
++	}
++
++	memcpy(cldb, obj->buffer.pointer, obj->buffer.length);
++
++out_free_obj:
++	kfree(obj);
++	return ret;
++}
++
++static const struct acpi_device_id int3472_device_id[] = {
++	{ "INT3472", 0 },
++	{ }
++};
++MODULE_DEVICE_TABLE(acpi, int3472_device_id);
++
++static struct platform_driver int3472_discrete = {
++	.driver = {
++		.name = "int3472-discrete",
++		.acpi_match_table = int3472_device_id,
++	},
++	.probe = skl_int3472_discrete_probe,
++	.remove = skl_int3472_discrete_remove,
++};
++
++static struct i2c_driver int3472_tps68470 = {
++	.driver = {
++		.name = "int3472-tps68470",
++		.acpi_match_table = int3472_device_id,
++	},
++	.probe_new = skl_int3472_tps68470_probe,
++};
++
++static int skl_int3472_init(void)
++{
++	int ret = 0;
++
++	ret = platform_driver_register(&int3472_discrete);
++	if (ret)
++		return ret;
++
++	ret = i2c_register_driver(THIS_MODULE, &int3472_tps68470);
++	if (ret)
++		platform_driver_unregister(&int3472_discrete);
++
++	return ret;
++}
++module_init(skl_int3472_init);
++
++static void skl_int3472_exit(void)
++{
++	platform_driver_unregister(&int3472_discrete);
++	i2c_del_driver(&int3472_tps68470);
++}
++module_exit(skl_int3472_exit);
++
++MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver");
++MODULE_AUTHOR("Daniel Scally <djrscally@gmail.com>");
++MODULE_LICENSE("GPL v2");
+diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h
+new file mode 100644
+index 000000000000..9169356cd522
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_common.h
+@@ -0,0 +1,110 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++/* Author: Dan Scally <djrscally@gmail.com> */
++
++#ifndef _INTEL_SKL_INT3472_H
++#define _INTEL_SKL_INT3472_H
++
++#include <linux/clk-provider.h>
++#include <linux/gpio/machine.h>
++#include <linux/regulator/driver.h>
++#include <linux/regulator/machine.h>
++#include <linux/types.h>
++
++/* PMIC GPIO Types */
++#define INT3472_GPIO_TYPE_RESET					0x00
++#define INT3472_GPIO_TYPE_POWERDOWN				0x01
++#define INT3472_GPIO_TYPE_POWER_ENABLE				0x0b
++#define INT3472_GPIO_TYPE_CLK_ENABLE				0x0c
++#define INT3472_GPIO_TYPE_PRIVACY_LED				0x0d
++
++#define INT3472_PDEV_MAX_NAME_LEN				23
++#define INT3472_MAX_SENSOR_GPIOS				3
++
++#define GPIO_REGULATOR_NAME_LENGTH				21
++#define GPIO_REGULATOR_SUPPLY_NAME_LENGTH			9
++
++#define CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET			86
++
++#define INT3472_REGULATOR(_name, _supply, _ops)			\
++	(const struct regulator_desc) {				\
++		.name = _name,					\
++		.supply_name = _supply,				\
++		.type = REGULATOR_VOLTAGE,			\
++		.ops = _ops,					\
++		.owner = THIS_MODULE,				\
++	}
++
++#define to_int3472_clk(hw)					\
++	container_of(hw, struct int3472_gpio_clock, clk_hw)
++
++#define to_int3472_device(clk)					\
++	container_of(clk, struct int3472_discrete_device, clock)
++
++struct platform_device;
++struct i2c_client;
++struct acpi_device;
++
++struct int3472_cldb {
++	u8 version;
++	/*
++	 * control logic type
++	 * 0: UNKNOWN
++	 * 1: DISCRETE(CRD-D)
++	 * 2: PMIC TPS68470
++	 * 3: PMIC uP6641
++	 */
++	u8 control_logic_type;
++	u8 control_logic_id;
++	u8 sensor_card_sku;
++	u8 reserved[28];
++};
++
++struct int3472_gpio_function_remap {
++	char *documented;
++	char *actual;
++};
++
++struct int3472_sensor_config {
++	const char *sensor_module_name;
++	struct regulator_consumer_supply supply_map;
++	const struct int3472_gpio_function_remap *function_maps;
++};
++
++struct int3472_discrete_device {
++	struct acpi_device *adev;
++	struct device *dev;
++	struct acpi_device *sensor;
++	const char *sensor_name;
++
++	struct int3472_sensor_config *sensor_config;
++
++	struct int3472_gpio_regulator {
++		char regulator_name[GPIO_REGULATOR_NAME_LENGTH];
++		char supply_name[GPIO_REGULATOR_SUPPLY_NAME_LENGTH];
++		struct gpio_desc *gpio;
++		struct regulator_dev *rdev;
++		struct regulator_desc rdesc;
++	} regulator;
++
++	struct int3472_gpio_clock {
++		struct clk *clk;
++		struct clk_hw clk_hw;
++		struct clk_lookup *cl;
++		struct gpio_desc *ena_gpio;
++		struct gpio_desc *led_gpio;
++		u32 frequency;
++	} clock;
++
++	unsigned int n_gpios; /* how many GPIOs have we seen */
++	unsigned int n_sensor_gpios; /* how many have we mapped to sensor */
++	struct gpiod_lookup_table gpios;
++};
++
++int skl_int3472_discrete_probe(struct platform_device *pdev);
++int skl_int3472_discrete_remove(struct platform_device *pdev);
++int skl_int3472_tps68470_probe(struct i2c_client *client);
++union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev,
++					       char *id);
++int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb);
++
++#endif
+diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c
+new file mode 100644
+index 000000000000..40652161bbbf
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_discrete.c
+@@ -0,0 +1,592 @@
++// SPDX-License-Identifier: GPL-2.0
++/* Author: Dan Scally <djrscally@gmail.com> */
++
++#include <linux/acpi.h>
++#include <linux/clkdev.h>
++#include <linux/gpio/consumer.h>
++#include <linux/i2c.h>
++#include <linux/kernel.h>
++#include <linux/module.h>
++#include <linux/overflow.h>
++#include <linux/platform_device.h>
++#include <linux/regulator/driver.h>
++#include <linux/slab.h>
++
++#include "intel_skl_int3472_common.h"
++
++/*
++ * 79234640-9e10-4fea-a5c1-b5aa8b19756f
++ * This _DSM GUID returns information about the GPIO lines mapped to a
++ * discrete INT3472 device. Function number 1 returns a count of the GPIO
++ * lines that are mapped. Subsequent functions return 32 bit ints encoding
++ * information about the GPIO line, including its purpose.
++ */
++static const guid_t int3472_gpio_guid =
++	GUID_INIT(0x79234640, 0x9e10, 0x4fea,
++		  0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f);
++
++/*
++ * 822ace8f-2814-4174-a56b-5f029fe079ee
++ * This _DSM GUID returns a string from the sensor device, which acts as a
++ * module identifier.
++ */
++static const guid_t cio2_sensor_module_guid =
++	GUID_INIT(0x822ace8f, 0x2814, 0x4174,
++		  0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee);
++
++/*
++ * Here follows platform specific mapping information that we can pass to
++ * the functions mapping resources to the sensors. Where the sensors have
++ * a power enable pin defined in DSDT we need to provide a supply name so
++ * the sensor drivers can find the regulator. The device name will be derived
++ * from the sensor's ACPI device within the code. Optionally, we can provide a
++ * NULL terminated array of function name mappings to deal with any platform
++ * specific deviations from the documented behaviour of GPIOs.
++ *
++ * Map a GPIO function name to NULL to prevent the driver from mapping that
++ * GPIO at all.
++ */
++
++static const struct int3472_gpio_function_remap ov2680_gpio_function_remaps[] = {
++	{ "reset", NULL },
++	{ "powerdown", "reset" },
++	{ }
++};
++
++static struct int3472_sensor_config int3472_sensor_configs[] = {
++	/* Lenovo Miix 510-12ISK - OV2680, Front */
++	{ "GNDF140809R", { 0 }, ov2680_gpio_function_remaps},
++	/* Lenovo Miix 510-12ISK - OV5648, Rear */
++	{ "GEFF150023R", REGULATOR_SUPPLY("avdd", NULL), NULL},
++	/* Surface Go 1&2 - OV5693, Front */
++	{ "YHCU", REGULATOR_SUPPLY("avdd", NULL), NULL},
++};
++
++/*
++ * The regulators have to have .ops to be valid, but the only ops we actually
++ * support are .enable and .disable which are handled via .ena_gpiod. Pass an
++ * empty struct to clear the check without lying about capabilities.
++ */
++static const struct regulator_ops int3472_gpio_regulator_ops = { 0 };
++
++static int skl_int3472_clk_prepare(struct clk_hw *hw)
++{
++	struct int3472_gpio_clock *clk = to_int3472_clk(hw);
++
++	gpiod_set_value(clk->ena_gpio, 1);
++	if (clk->led_gpio)
++		gpiod_set_value(clk->led_gpio, 1);
++
++	return 0;
++}
++
++static void skl_int3472_clk_unprepare(struct clk_hw *hw)
++{
++	struct int3472_gpio_clock *clk = to_int3472_clk(hw);
++
++	gpiod_set_value(clk->ena_gpio, 0);
++	if (clk->led_gpio)
++		gpiod_set_value(clk->led_gpio, 0);
++}
++
++static int skl_int3472_clk_enable(struct clk_hw *hw)
++{
++	/*
++	 * We're just turning a GPIO on to enable, which operation has the
++	 * potential to sleep. Given enable cannot sleep, but prepare can,
++	 * we toggle the GPIO in prepare instead. Thus, nothing to do here.
++	 */
++	return 0;
++}
++
++static void skl_int3472_clk_disable(struct clk_hw *hw)
++{
++	/* Likewise, nothing to do here... */
++}
++
++static unsigned int skl_int3472_get_clk_frequency(struct int3472_discrete_device *int3472)
++{
++	union acpi_object *obj;
++	unsigned int ret = 0;
++
++	obj = skl_int3472_get_acpi_buffer(int3472->sensor, "SSDB");
++	if (IS_ERR(obj))
++		return 0; /* report rate as 0 on error */
++
++	if (obj->buffer.length < CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET + sizeof(u32)) {
++		dev_err(int3472->dev, "The buffer is too small\n");
++		goto out_free_buff;
++	}
++
++	ret = *(u32 *)(obj->buffer.pointer + CIO2_SENSOR_SSDB_MCLKSPEED_OFFSET);
++
++out_free_buff:
++	kfree(obj);
++	return ret;
++}
++
++static unsigned long skl_int3472_clk_recalc_rate(struct clk_hw *hw,
++						 unsigned long parent_rate)
++{
++	struct int3472_gpio_clock *clk = to_int3472_clk(hw);
++	struct int3472_discrete_device *int3472 = to_int3472_device(clk);
++
++	return int3472->clock.frequency;
++}
++
++static const struct clk_ops skl_int3472_clock_ops = {
++	.prepare = skl_int3472_clk_prepare,
++	.unprepare = skl_int3472_clk_unprepare,
++	.enable = skl_int3472_clk_enable,
++	.disable = skl_int3472_clk_disable,
++	.recalc_rate = skl_int3472_clk_recalc_rate,
++};
++
++static struct int3472_sensor_config *
++skl_int3472_get_sensor_module_config(struct int3472_discrete_device *int3472)
++{
++	struct int3472_sensor_config *ret;
++	union acpi_object *obj;
++	unsigned int i;
++
++	obj = acpi_evaluate_dsm_typed(int3472->sensor->handle,
++				      &cio2_sensor_module_guid, 0x00,
++				      0x01, NULL, ACPI_TYPE_STRING);
++
++	if (!obj) {
++		dev_err(int3472->dev,
++			"Failed to get sensor module string from _DSM\n");
++		return ERR_PTR(-ENODEV);
++	}
++
++	if (obj->string.type != ACPI_TYPE_STRING) {
++		dev_err(int3472->dev,
++			"Sensor _DSM returned a non-string value\n");
++		ret = ERR_PTR(-EINVAL);
++		goto out_free_obj;
++	}
++
++	ret = NULL;
++	for (i = 0; i < ARRAY_SIZE(int3472_sensor_configs); i++) {
++		if (!strcmp(int3472_sensor_configs[i].sensor_module_name,
++			    obj->string.pointer)) {
++			ret = &int3472_sensor_configs[i];
++			break;
++		}
++	}
++
++out_free_obj:
++	ACPI_FREE(obj);
++	return ret;
++}
++
++static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int3472,
++					  struct acpi_resource *ares,
++					  char *func, u32 polarity)
++{
++	char *path = ares->data.gpio.resource_source.string_ptr;
++	const struct int3472_sensor_config *sensor_config;
++	struct gpiod_lookup *table_entry;
++	struct acpi_device *adev;
++	acpi_handle handle;
++	acpi_status status;
++	int ret;
++
++	if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) {
++		dev_warn(int3472->dev, "Too many GPIOs mapped\n");
++		return -EINVAL;
++	}
++
++	sensor_config = int3472->sensor_config;
++	if (!IS_ERR_OR_NULL(sensor_config) && sensor_config->function_maps) {
++		const struct int3472_gpio_function_remap *remap =
++			sensor_config->function_maps;
++
++		for (; remap->documented; ++remap)
++			if (!strcmp(func, remap->documented)) {
++				func = remap->actual;
++				break;
++			}
++	}
++
++	/* Functions mapped to NULL should not be mapped to the sensor */
++	if (!func)
++		return 0;
++
++	status = acpi_get_handle(NULL, path, &handle);
++	if (ACPI_FAILURE(status))
++		return -EINVAL;
++
++	ret = acpi_bus_get_device(handle, &adev);
++	if (ret)
++		return -ENODEV;
++
++	table_entry = &int3472->gpios.table[int3472->n_sensor_gpios];
++	table_entry->key = acpi_dev_name(adev);
++	table_entry->chip_hwnum = ares->data.gpio.pin_table[0];
++	table_entry->con_id = func;
++	table_entry->idx = 0;
++	table_entry->flags = polarity;
++
++	int3472->n_sensor_gpios++;
++
++	return 0;
++}
++
++static int skl_int3472_map_gpio_to_clk(struct int3472_discrete_device *int3472,
++				       struct acpi_resource *ares, u8 type)
++{
++	char *path = ares->data.gpio.resource_source.string_ptr;
++	struct gpio_desc *gpio;
++
++	switch (type) {
++	case INT3472_GPIO_TYPE_CLK_ENABLE:
++		gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0],
++				      "int3472,clk-enable");
++		if (IS_ERR(gpio))
++			return (PTR_ERR(gpio));
++
++		int3472->clock.ena_gpio = gpio;
++		break;
++	case INT3472_GPIO_TYPE_PRIVACY_LED:
++		gpio = acpi_get_gpiod(path, ares->data.gpio.pin_table[0],
++				      "int3472,privacy-led");
++		if (IS_ERR(gpio))
++			return (PTR_ERR(gpio));
++
++		int3472->clock.led_gpio = gpio;
++		break;
++	default:
++		dev_err(int3472->dev, "Invalid GPIO type 0x%hx for clock\n",
++			type);
++		break;
++	}
++
++	return 0;
++}
++
++static int skl_int3472_register_clock(struct int3472_discrete_device *int3472)
++{
++	struct clk_init_data init = {
++		.ops = &skl_int3472_clock_ops,
++		.flags = CLK_GET_RATE_NOCACHE,
++	};
++	int ret = 0;
++
++	init.name = kasprintf(GFP_KERNEL, "%s-clk",
++			      acpi_dev_name(int3472->adev));
++	if (!init.name)
++		return -ENOMEM;
++
++	int3472->clock.frequency = skl_int3472_get_clk_frequency(int3472);
++
++	int3472->clock.clk_hw.init = &init;
++	int3472->clock.clk = clk_register(&int3472->adev->dev,
++					  &int3472->clock.clk_hw);
++	if (IS_ERR(int3472->clock.clk)) {
++		ret = PTR_ERR(int3472->clock.clk);
++		goto out_free_init_name;
++	}
++
++	int3472->clock.cl = clkdev_create(int3472->clock.clk, NULL,
++					  int3472->sensor_name);
++	if (!int3472->clock.cl) {
++		ret = -ENOMEM;
++		goto err_unregister_clk;
++	}
++
++	goto out_free_init_name;
++
++err_unregister_clk:
++	clk_unregister(int3472->clock.clk);
++out_free_init_name:
++	kfree(init.name);
++
++	return ret;
++}
++
++static int skl_int3472_register_regulator(struct int3472_discrete_device *int3472,
++					  struct acpi_resource *ares)
++{
++	char *path = ares->data.gpio.resource_source.string_ptr;
++	struct int3472_sensor_config *sensor_config;
++	struct regulator_init_data init_data = { };
++	struct regulator_config cfg = { };
++	int ret;
++
++	sensor_config = int3472->sensor_config;
++	if (IS_ERR_OR_NULL(sensor_config)) {
++		dev_err(int3472->dev, "No sensor module config\n");
++		return PTR_ERR(sensor_config);
++	}
++
++	if (!sensor_config->supply_map.supply) {
++		dev_err(int3472->dev, "No supply name defined\n");
++		return -ENODEV;
++	}
++
++	init_data.constraints.valid_ops_mask = REGULATOR_CHANGE_STATUS;
++	init_data.num_consumer_supplies = 1;
++	sensor_config->supply_map.dev_name = int3472->sensor_name;
++	init_data.consumer_supplies = &sensor_config->supply_map;
++
++	snprintf(int3472->regulator.regulator_name,
++		 sizeof(int3472->regulator.regulator_name), "%s-regulator",
++		 acpi_dev_name(int3472->adev));
++	snprintf(int3472->regulator.supply_name,
++		 GPIO_REGULATOR_SUPPLY_NAME_LENGTH, "supply-0");
++
++	int3472->regulator.rdesc = INT3472_REGULATOR(
++						int3472->regulator.regulator_name,
++						int3472->regulator.supply_name,
++						&int3472_gpio_regulator_ops);
++
++	int3472->regulator.gpio = acpi_get_gpiod(path,
++						 ares->data.gpio.pin_table[0],
++						 "int3472,regulator");
++	if (IS_ERR(int3472->regulator.gpio)) {
++		dev_err(int3472->dev, "Failed to get regulator GPIO lines\n");
++		return PTR_ERR(int3472->regulator.gpio);
++	}
++
++	cfg.dev = &int3472->adev->dev;
++	cfg.init_data = &init_data;
++	cfg.ena_gpiod = int3472->regulator.gpio;
++
++	int3472->regulator.rdev = regulator_register(&int3472->regulator.rdesc,
++						     &cfg);
++	if (IS_ERR(int3472->regulator.rdev)) {
++		ret = PTR_ERR(int3472->regulator.rdev);
++		goto err_free_gpio;
++	}
++
++	return 0;
++
++err_free_gpio:
++	gpiod_put(int3472->regulator.gpio);
++
++	return ret;
++}
++
++/**
++ * skl_int3472_handle_gpio_resources: Map PMIC resources to consuming sensor
++ * @ares: A pointer to a &struct acpi_resource
++ * @data: A pointer to a &struct int3472_discrete_device
++ *
++ * This function handles GPIO resources that are against an INT3472
++ * ACPI device, by checking the value of the corresponding _DSM entry.
++ * This will return a 32bit int, where the lowest byte represents the
++ * function of the GPIO pin:
++ *
++ * 0x00 Reset
++ * 0x01 Power down
++ * 0x0b Power enable
++ * 0x0c Clock enable
++ * 0x0d Privacy LED
++ *
++ * There are some known platform specific quirks where that does not quite
++ * hold up; for example where a pin with type 0x01 (Power down) is mapped to
++ * a sensor pin that performs a reset function or entries in _CRS and _DSM that
++ * do not actually correspond to a physical connection. These will be handled
++ * by the mapping sub-functions.
++ *
++ * GPIOs will either be mapped directly to the sensor device or else used
++ * to create clocks and regulators via the usual frameworks.
++ *
++ * Return:
++ * * 0		- When all resources found are handled properly.
++ * * -EINVAL	- If the resource is not a GPIO IO resource
++ * * -ENODEV	- If the resource has no corresponding _DSM entry
++ * * -Other	- Errors propagated from one of the sub-functions.
++ */
++static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares,
++					     void *data)
++{
++	struct int3472_discrete_device *int3472 = data;
++	u16 pin = ares->data.gpio.pin_table[0];
++	union acpi_object *obj;
++	char *err_msg;
++	int ret = 0;
++	u8 type;
++
++	if (ares->type != ACPI_RESOURCE_TYPE_GPIO ||
++	    ares->data.gpio.connection_type != ACPI_RESOURCE_GPIO_TYPE_IO)
++		return 1; /* Deliberately positive so parsing continues */
++
++	/*
++	 * n_gpios + 2 because the index of this _DSM function is 1-based and
++	 * the first function is just a count.
++	 */
++	obj = acpi_evaluate_dsm_typed(int3472->adev->handle,
++				      &int3472_gpio_guid, 0x00,
++				      int3472->n_gpios + 2,
++				      NULL, ACPI_TYPE_INTEGER);
++
++	if (!obj) {
++		dev_warn(int3472->dev, "No _DSM entry for GPIO pin %u\n", pin);
++		return 1;
++	}
++
++	type = obj->integer.value & 0xff;
++
++	switch (type) {
++	case INT3472_GPIO_TYPE_RESET:
++		ret = skl_int3472_map_gpio_to_sensor(int3472, ares, "reset",
++						     GPIO_ACTIVE_LOW);
++		if (ret)
++			err_msg = "Failed to map reset pin to sensor\n";
++
++		break;
++	case INT3472_GPIO_TYPE_POWERDOWN:
++		ret = skl_int3472_map_gpio_to_sensor(int3472, ares,
++						     "powerdown",
++						     GPIO_ACTIVE_LOW);
++		if (ret)
++			err_msg = "Failed to map powerdown pin to sensor\n";
++
++		break;
++	case INT3472_GPIO_TYPE_CLK_ENABLE:
++	case INT3472_GPIO_TYPE_PRIVACY_LED:
++		ret = skl_int3472_map_gpio_to_clk(int3472, ares, type);
++		if (ret)
++			err_msg = "Failed to map GPIO to clock\n";
++
++		break;
++	case INT3472_GPIO_TYPE_POWER_ENABLE:
++		ret = skl_int3472_register_regulator(int3472, ares);
++		if (ret)
++			err_msg = "Failed to map regulator to sensor\n";
++
++		break;
++	default:
++		dev_warn(int3472->dev,
++			 "GPIO type 0x%02x unknown; the sensor may not work\n",
++			 type);
++		ret = 1;
++		break;
++	}
++
++	if (ret < 0 && ret != -EPROBE_DEFER)
++		dev_err(int3472->dev, err_msg);
++
++	int3472->n_gpios++;
++	ACPI_FREE(obj);
++
++	return ret;
++}
++
++static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472)
++{
++	struct list_head resource_list;
++	int ret;
++
++	INIT_LIST_HEAD(&resource_list);
++
++	int3472->sensor_config = skl_int3472_get_sensor_module_config(int3472);
++
++	ret = acpi_dev_get_resources(int3472->adev, &resource_list,
++				     skl_int3472_handle_gpio_resources,
++				     int3472);
++	if (ret)
++		goto out_free_res_list;
++
++	if (int3472->clock.ena_gpio) {
++		ret = skl_int3472_register_clock(int3472);
++		if (ret)
++			goto out_free_res_list;
++	} else {
++		if (int3472->clock.led_gpio)
++			dev_warn(int3472->dev,
++				 "No clk GPIO. The privacy LED won't work\n");
++	}
++
++	int3472->gpios.dev_id = int3472->sensor_name;
++	gpiod_add_lookup_table(&int3472->gpios);
++
++out_free_res_list:
++	acpi_dev_free_resource_list(&resource_list);
++
++	return ret;
++}
++
++int skl_int3472_discrete_probe(struct platform_device *pdev)
++{
++	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
++	struct int3472_discrete_device *int3472;
++	struct int3472_cldb cldb;
++	int ret;
++
++	ret = skl_int3472_fill_cldb(adev, &cldb);
++	if (ret) {
++		dev_err(&pdev->dev, "Couldn't fill CLDB structure\n");
++		return ret;
++	}
++
++	if (cldb.control_logic_type != 1) {
++		dev_err(&pdev->dev, "Unsupported control logic type %u\n",
++			cldb.control_logic_type);
++		return -EINVAL;
++	}
++
++	/* Max num GPIOs we've seen plus a terminator */
++	int3472 = kzalloc(struct_size(int3472, gpios.table,
++			  INT3472_MAX_SENSOR_GPIOS + 1), GFP_KERNEL);
++	if (!int3472)
++		return -ENOMEM;
++
++	int3472->adev = adev;
++	int3472->dev = &pdev->dev;
++	platform_set_drvdata(pdev, int3472);
++
++	int3472->sensor = acpi_dev_get_dependent_dev(adev);
++	if (IS_ERR_OR_NULL(int3472->sensor)) {
++		dev_err(&pdev->dev,
++			"INT3472 seems to have no dependents.\n");
++		ret = -ENODEV;
++		goto err_free_int3472;
++	}
++	get_device(&int3472->sensor->dev);
++
++	int3472->sensor_name = kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT,
++					 acpi_dev_name(int3472->sensor));
++
++	ret = skl_int3472_parse_crs(int3472);
++	if (ret) {
++		skl_int3472_discrete_remove(pdev);
++		return ret;
++	}
++
++	return 0;
++
++err_free_int3472:
++	kfree(int3472);
++	return ret;
++}
++
++int skl_int3472_discrete_remove(struct platform_device *pdev)
++{
++	struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev);
++
++	if (int3472->gpios.dev_id)
++		gpiod_remove_lookup_table(&int3472->gpios);
++
++	if (!IS_ERR(int3472->regulator.rdev))
++		regulator_unregister(int3472->regulator.rdev);
++
++	if (!IS_ERR(int3472->clock.clk))
++		clk_unregister(int3472->clock.clk);
++
++	if (int3472->clock.cl)
++		clkdev_drop(int3472->clock.cl);
++
++	gpiod_put(int3472->regulator.gpio);
++	gpiod_put(int3472->clock.ena_gpio);
++	gpiod_put(int3472->clock.led_gpio);
++
++	acpi_dev_put(int3472->sensor);
++
++	kfree(int3472->sensor_name);
++	kfree(int3472);
++
++	return 0;
++}
+diff --git a/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c
+new file mode 100644
+index 000000000000..d0d2391e263f
+--- /dev/null
++++ b/drivers/platform/x86/intel-int3472/intel_skl_int3472_tps68470.c
+@@ -0,0 +1,113 @@
++// SPDX-License-Identifier: GPL-2.0
++/* Author: Dan Scally <djrscally@gmail.com> */
++
++#include <linux/i2c.h>
++#include <linux/mfd/core.h>
++#include <linux/mfd/tps68470.h>
++#include <linux/platform_device.h>
++#include <linux/regmap.h>
++
++#include "intel_skl_int3472_common.h"
++
++static const struct mfd_cell tps68470_c[] = {
++	{ .name = "tps68470-gpio" },
++	{ .name = "tps68470_pmic_opregion" },
++};
++
++static const struct mfd_cell tps68470_w[] = {
++	{ .name = "tps68470-gpio" },
++	{ .name = "tps68470-clk" },
++	{ .name = "tps68470-regulator"},
++};
++
++static const struct regmap_config tps68470_regmap_config = {
++	.reg_bits = 8,
++	.val_bits = 8,
++	.max_register = TPS68470_REG_MAX,
++};
++
++static int tps68470_chip_init(struct device *dev, struct regmap *regmap)
++{
++	unsigned int version;
++	int ret;
++
++	/* Force software reset */
++	ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK);
++	if (ret)
++		return ret;
++
++	ret = regmap_read(regmap, TPS68470_REG_REVID, &version);
++	if (ret) {
++		dev_err(dev, "Failed to read revision register: %d\n", ret);
++		return ret;
++	}
++
++	dev_info(dev, "TPS68470 REVID: 0x%02x\n", version);
++
++	return 0;
++}
++
++int skl_int3472_tps68470_probe(struct i2c_client *client)
++{
++	struct acpi_device *adev = ACPI_COMPANION(&client->dev);
++	struct int3472_cldb cldb = { 0 };
++	bool cldb_present = true;
++	struct regmap *regmap;
++	int ret;
++
++	regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config);
++	if (IS_ERR(regmap)) {
++		dev_err(&client->dev, "Failed to create regmap: %ld\n",
++			PTR_ERR(regmap));
++		return PTR_ERR(regmap);
++	}
++
++	i2c_set_clientdata(client, regmap);
++
++	ret = tps68470_chip_init(&client->dev, regmap);
++	if (ret < 0) {
++		dev_err(&client->dev, "TPS68470 init error %d\n", ret);
++		return ret;
++	}
++
++	/*
++	 * Check CLDB buffer against the PMIC's adev. If present, then we check
++	 * the value of control_logic_type field and follow one of the
++	 * following scenarios:
++	 *
++	 *	1. No CLDB - likely ACPI tables designed for ChromeOS. We
++	 *	create platform devices for the GPIOs and OpRegion drivers.
++	 *
++	 *	2. CLDB, with control_logic_type = 2 - probably ACPI tables
++	 *	made for Windows 2-in-1 platforms. Register pdevs for GPIO,
++	 *	Clock and Regulator drivers to bind to.
++	 *
++	 *	3. Any other value in control_logic_type, we should never have
++	 *	gotten to this point; fail probe and return.
++	 */
++	ret = skl_int3472_fill_cldb(adev, &cldb);
++	if (!ret && cldb.control_logic_type != 2) {
++		dev_err(&client->dev, "Unsupported control logic type %u\n",
++			cldb.control_logic_type);
++		return -EINVAL;
++	}
++
++	if (ret)
++		cldb_present = false;
++
++	if (cldb_present)
++		ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
++					   tps68470_w, ARRAY_SIZE(tps68470_w),
++					   NULL, 0, NULL);
++	else
++		ret = devm_mfd_add_devices(&client->dev, PLATFORM_DEVID_NONE,
++					   tps68470_c, ARRAY_SIZE(tps68470_c),
++					   NULL, 0, NULL);
++
++	if (ret) {
++		dev_err(&client->dev, "Failed to add MFD devices\n");
++		return ret;
++	}
++
++	return 0;
++}
+-- 
+2.31.1
+
+From 6c66649aaf0b322f3c0041bd3204d7d485a7d91f Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 22 Feb 2021 13:07:35 +0000
+Subject: [PATCH] mfd: tps68470: Remove tps68470 MFD driver
+
+This driver only covered one scenario in which ACPI devices with _HID
+INT3472 are found, and its functionality has been taken over by the
+intel-skl-int3472 module, so remove it.
+
+Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Acked-by: Andy Shevchenko <andy.shevchenko@gmail.com>
+Acked-by: Lee Jones <lee.jones@linaro.org>
+Patchset: cameras
+---
+ drivers/acpi/pmic/Kconfig |  2 +-
+ drivers/gpio/Kconfig      |  2 +-
+ drivers/mfd/Kconfig       | 18 --------
+ drivers/mfd/Makefile      |  1 -
+ drivers/mfd/tps68470.c    | 97 ---------------------------------------
+ 5 files changed, 2 insertions(+), 118 deletions(-)
+ delete mode 100644 drivers/mfd/tps68470.c
+
+diff --git a/drivers/acpi/pmic/Kconfig b/drivers/acpi/pmic/Kconfig
+index 56bbcb2ce61b..f84b8f6038dc 100644
+--- a/drivers/acpi/pmic/Kconfig
++++ b/drivers/acpi/pmic/Kconfig
+@@ -52,7 +52,7 @@ endif	# PMIC_OPREGION
+ 
+ config TPS68470_PMIC_OPREGION
+ 	bool "ACPI operation region support for TPS68470 PMIC"
+-	depends on MFD_TPS68470
++	depends on INTEL_SKL_INT3472
+ 	help
+ 	  This config adds ACPI operation region support for TI TPS68470 PMIC.
+ 	  TPS68470 device is an advanced power management unit that powers
+diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
+index e3607ec4c2e8..269d88c7d5a5 100644
+--- a/drivers/gpio/Kconfig
++++ b/drivers/gpio/Kconfig
+@@ -1345,7 +1345,7 @@ config GPIO_TPS65912
+ 
+ config GPIO_TPS68470
+ 	bool "TPS68470 GPIO"
+-	depends on MFD_TPS68470
++	depends on INTEL_SKL_INT3472
+ 	help
+ 	  Select this option to enable GPIO driver for the TPS68470
+ 	  chip family.
+diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
+index b74efa469e90..8a112a194c3b 100644
+--- a/drivers/mfd/Kconfig
++++ b/drivers/mfd/Kconfig
+@@ -1511,24 +1511,6 @@ config MFD_TPS65217
+ 	  This driver can also be built as a module.  If so, the module
+ 	  will be called tps65217.
+ 
+-config MFD_TPS68470
+-	bool "TI TPS68470 Power Management / LED chips"
+-	depends on ACPI && PCI && I2C=y
+-	depends on I2C_DESIGNWARE_PLATFORM=y
+-	select MFD_CORE
+-	select REGMAP_I2C
+-	help
+-	  If you say yes here you get support for the TPS68470 series of
+-	  Power Management / LED chips.
+-
+-	  These include voltage regulators, LEDs and other features
+-	  that are often used in portable devices.
+-
+-	  This option is a bool as it provides an ACPI operation
+-	  region, which must be available before any of the devices
+-	  using this are probed. This option also configures the
+-	  designware-i2c driver to be built-in, for the same reason.
+-
+ config MFD_TI_LP873X
+ 	tristate "TI LP873X Power Management IC"
+ 	depends on I2C
+diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
+index 834f5463af28..daccc3475de5 100644
+--- a/drivers/mfd/Makefile
++++ b/drivers/mfd/Makefile
+@@ -105,7 +105,6 @@ obj-$(CONFIG_MFD_TPS65910)	+= tps65910.o
+ obj-$(CONFIG_MFD_TPS65912)	+= tps65912-core.o
+ obj-$(CONFIG_MFD_TPS65912_I2C)	+= tps65912-i2c.o
+ obj-$(CONFIG_MFD_TPS65912_SPI)  += tps65912-spi.o
+-obj-$(CONFIG_MFD_TPS68470)	+= tps68470.o
+ obj-$(CONFIG_MFD_TPS80031)	+= tps80031.o
+ obj-$(CONFIG_MENELAUS)		+= menelaus.o
+ 
+diff --git a/drivers/mfd/tps68470.c b/drivers/mfd/tps68470.c
+deleted file mode 100644
+index 4a4df4ffd18c..000000000000
+--- a/drivers/mfd/tps68470.c
++++ /dev/null
+@@ -1,97 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-/*
+- * TPS68470 chip Parent driver
+- *
+- * Copyright (C) 2017 Intel Corporation
+- *
+- * Authors:
+- *	Rajmohan Mani <rajmohan.mani@intel.com>
+- *	Tianshu Qiu <tian.shu.qiu@intel.com>
+- *	Jian Xu Zheng <jian.xu.zheng@intel.com>
+- *	Yuning Pu <yuning.pu@intel.com>
+- */
+-
+-#include <linux/acpi.h>
+-#include <linux/delay.h>
+-#include <linux/i2c.h>
+-#include <linux/init.h>
+-#include <linux/mfd/core.h>
+-#include <linux/mfd/tps68470.h>
+-#include <linux/regmap.h>
+-
+-static const struct mfd_cell tps68470s[] = {
+-	{ .name = "tps68470-gpio" },
+-	{ .name = "tps68470_pmic_opregion" },
+-};
+-
+-static const struct regmap_config tps68470_regmap_config = {
+-	.reg_bits = 8,
+-	.val_bits = 8,
+-	.max_register = TPS68470_REG_MAX,
+-};
+-
+-static int tps68470_chip_init(struct device *dev, struct regmap *regmap)
+-{
+-	unsigned int version;
+-	int ret;
+-
+-	/* Force software reset */
+-	ret = regmap_write(regmap, TPS68470_REG_RESET, TPS68470_REG_RESET_MASK);
+-	if (ret)
+-		return ret;
+-
+-	ret = regmap_read(regmap, TPS68470_REG_REVID, &version);
+-	if (ret) {
+-		dev_err(dev, "Failed to read revision register: %d\n", ret);
+-		return ret;
+-	}
+-
+-	dev_info(dev, "TPS68470 REVID: 0x%x\n", version);
+-
+-	return 0;
+-}
+-
+-static int tps68470_probe(struct i2c_client *client)
+-{
+-	struct device *dev = &client->dev;
+-	struct regmap *regmap;
+-	int ret;
+-
+-	regmap = devm_regmap_init_i2c(client, &tps68470_regmap_config);
+-	if (IS_ERR(regmap)) {
+-		dev_err(dev, "devm_regmap_init_i2c Error %ld\n",
+-			PTR_ERR(regmap));
+-		return PTR_ERR(regmap);
+-	}
+-
+-	i2c_set_clientdata(client, regmap);
+-
+-	ret = tps68470_chip_init(dev, regmap);
+-	if (ret < 0) {
+-		dev_err(dev, "TPS68470 Init Error %d\n", ret);
+-		return ret;
+-	}
+-
+-	ret = devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, tps68470s,
+-			      ARRAY_SIZE(tps68470s), NULL, 0, NULL);
+-	if (ret < 0) {
+-		dev_err(dev, "devm_mfd_add_devices failed: %d\n", ret);
+-		return ret;
+-	}
+-
+-	return 0;
+-}
+-
+-static const struct acpi_device_id tps68470_acpi_ids[] = {
+-	{"INT3472"},
+-	{},
+-};
+-
+-static struct i2c_driver tps68470_driver = {
+-	.driver = {
+-		   .name = "tps68470",
+-		   .acpi_match_table = tps68470_acpi_ids,
+-	},
+-	.probe_new = tps68470_probe,
+-};
+-builtin_i2c_driver(tps68470_driver);
+-- 
+2.31.1
+
+From c71b895f160e1a133b5648ba0efe2aceb1c0a3a8 Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 5 Apr 2021 23:56:53 +0100
+Subject: [PATCH] media: ipu3-cio2: Toggle sensor streaming in pm runtime ops
+
+The .suspend() and .resume() runtime_pm operations for the ipu3-cio2
+driver currently do not handle the sensor's stream. Setting .s_stream() on
+or off for the sensor subdev means that sensors will pause and resume the
+stream at the appropriate time even if their drivers don't implement those
+operations.
+
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Patchset: cameras
+---
+ drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 15 ++++++++++++++-
+ 1 file changed, 14 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c
+index 6e8c0c230e11..21e4d0358cdc 100644
+--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c
++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c
+@@ -1973,12 +1973,19 @@ static int __maybe_unused cio2_suspend(struct device *dev)
+ 	struct pci_dev *pci_dev = to_pci_dev(dev);
+ 	struct cio2_device *cio2 = pci_get_drvdata(pci_dev);
+ 	struct cio2_queue *q = cio2->cur_queue;
++	int r;
+ 
+ 	dev_dbg(dev, "cio2 suspend\n");
+ 	if (!cio2->streaming)
+ 		return 0;
+ 
+ 	/* Stop stream */
++	r = v4l2_subdev_call(q->sensor, video, s_stream, 0);
++	if (r) {
++		dev_err(dev, "failed to stop sensor streaming\n");
++		return r;
++	}
++
+ 	cio2_hw_exit(cio2, q);
+ 	synchronize_irq(pci_dev->irq);
+ 
+@@ -2013,8 +2020,14 @@ static int __maybe_unused cio2_resume(struct device *dev)
+ 	}
+ 
+ 	r = cio2_hw_init(cio2, q);
+-	if (r)
++	if (r) {
+ 		dev_err(dev, "fail to init cio2 hw\n");
++		return r;
++	}
++
++	r = v4l2_subdev_call(q->sensor, video, s_stream, 1);
++	if (r)
++		dev_err(dev, "fail to start sensor streaming\n");
+ 
+ 	return r;
+ }
+-- 
+2.31.1
+
+From 8432e3e455f946d974d66de60b0b8a397c02ef3d Mon Sep 17 00:00:00 2001
+From: Daniel Scally <djrscally@gmail.com>
+Date: Mon, 5 Apr 2021 23:56:54 +0100
+Subject: [PATCH] media: i2c: Add support for ov5693 sensor
+
+The OV5693 is a 5 Mpx CMOS image sensor, connected via MIPI CSI-2. The
+chip is capable of a single lane configuration, but currently only two
+lanes are supported.
+
+Most of the sensor's features are supported, with the main exception
+being the lens correction algorithm.
+
+The driver provides all mandatory, optional and recommended V4L2 controls
+for maximum compatibility with libcamera.
+
+Signed-off-by: Daniel Scally <djrscally@gmail.com>
+Patchset: cameras
+---
+ MAINTAINERS                |    7 +
+ drivers/media/i2c/Kconfig  |   11 +
+ drivers/media/i2c/Makefile |    1 +
+ drivers/media/i2c/ov5693.c | 1557 ++++++++++++++++++++++++++++++++++++
+ 4 files changed, 1576 insertions(+)
+ create mode 100644 drivers/media/i2c/ov5693.c
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 2a421d2e4b07..b270caa6f766 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -13276,6 +13276,13 @@ S:	Maintained
+ T:	git git://linuxtv.org/media_tree.git
+ F:	drivers/media/i2c/ov5675.c
+ 
++OMNIVISION OV5693 SENSOR DRIVER
++M:	Daniel Scally <djrscally@gmail.com>
++L:	linux-media@vger.kernel.org
++S:	Maintained
++T:	git git://linuxtv.org/media_tree.git
++F:	drivers/media/i2c/ov5693.c
++
+ OMNIVISION OV5695 SENSOR DRIVER
+ M:	Shunqian Zheng <zhengsq@rock-chips.com>
+ L:	linux-media@vger.kernel.org
+diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
+index 462c0e059754..2893e74af99a 100644
+--- a/drivers/media/i2c/Kconfig
++++ b/drivers/media/i2c/Kconfig
+@@ -999,6 +999,17 @@ config VIDEO_OV5675
+ 	  To compile this driver as a module, choose M here: the
+ 	  module will be called ov5675.
+ 
++config VIDEO_OV5693
++	tristate "OmniVision OV5693 sensor support"
++	depends on I2C && VIDEO_V4L2
++	select V4L2_FWNODE
++	help
++	  This is a Video4Linux2 sensor driver for the OmniVision
++	  OV5693 camera.
++
++	  To compile this driver as a module, choose M here: the
++	  module will be called ov5693.
++
+ config VIDEO_OV5695
+ 	tristate "OmniVision OV5695 sensor support"
+ 	depends on I2C && VIDEO_V4L2
+diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
+index 0c067beca066..7d0884bc89f1 100644
+--- a/drivers/media/i2c/Makefile
++++ b/drivers/media/i2c/Makefile
+@@ -75,6 +75,7 @@ obj-$(CONFIG_VIDEO_OV5647) += ov5647.o
+ obj-$(CONFIG_VIDEO_OV5648) += ov5648.o
+ obj-$(CONFIG_VIDEO_OV5670) += ov5670.o
+ obj-$(CONFIG_VIDEO_OV5675) += ov5675.o
++obj-$(CONFIG_VIDEO_OV5693) += ov5693.o
+ obj-$(CONFIG_VIDEO_OV5695) += ov5695.o
+ obj-$(CONFIG_VIDEO_OV6650) += ov6650.o
+ obj-$(CONFIG_VIDEO_OV7251) += ov7251.o
+diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c
+new file mode 100644
+index 000000000000..da2ca99a7ad3
+--- /dev/null
++++ b/drivers/media/i2c/ov5693.c
+@@ -0,0 +1,1557 @@
++// SPDX-License-Identifier: GPL-2.0
++/*
++ * Copyright (c) 2013 Intel Corporation. All Rights Reserved.
++ *
++ * Adapted from the atomisp-ov5693 driver, with contributions from:
++ *
++ * Daniel Scally
++ * Jean-Michel Hautbois
++ * Fabian Wuthrich
++ * Tsuchiya Yuto
++ * Jordan Hand
++ * Jake Day
++ */
++
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/clk.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/pm_runtime.h>
++#include <linux/regulator/consumer.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-fwnode.h>
++
++/* System Control */
++#define OV5693_SW_RESET_REG			0x0103
++#define OV5693_SW_STREAM_REG			0x0100
++#define OV5693_START_STREAMING			0x01
++#define OV5693_STOP_STREAMING			0x00
++#define OV5693_SW_RESET				0x01
++
++#define OV5693_REG_CHIP_ID_H			0x300a
++#define OV5693_REG_CHIP_ID_L			0x300b
++/* Yes, this is right. The datasheet for the OV5693 gives its ID as 0x5690 */
++#define OV5693_CHIP_ID				0x5690
++
++/* Exposure */
++#define OV5693_EXPOSURE_L_CTRL_HH_REG		0x3500
++#define OV5693_EXPOSURE_L_CTRL_H_REG		0x3501
++#define OV5693_EXPOSURE_L_CTRL_L_REG		0x3502
++#define OV5693_EXPOSURE_CTRL_HH(v)		(((v) & GENMASK(14, 12)) >> 12)
++#define OV5693_EXPOSURE_CTRL_H(v)		(((v) & GENMASK(11, 4)) >> 4)
++#define OV5693_EXPOSURE_CTRL_L(v)		(((v) & GENMASK(3, 0)) << 4)
++#define OV5693_INTEGRATION_TIME_MARGIN		8
++#define OV5693_EXPOSURE_MIN			1
++#define OV5693_EXPOSURE_STEP			1
++
++/* Analogue Gain */
++#define OV5693_GAIN_CTRL_H_REG			0x350a
++#define OV5693_GAIN_CTRL_H(v)			(((v) >> 4) & GENMASK(2, 0))
++#define OV5693_GAIN_CTRL_L_REG			0x350b
++#define OV5693_GAIN_CTRL_L(v)			(((v) << 4) & GENMASK(7, 4))
++#define OV5693_GAIN_MIN				1
++#define OV5693_GAIN_MAX				127
++#define OV5693_GAIN_DEF				8
++#define OV5693_GAIN_STEP			1
++
++/* Digital Gain */
++#define OV5693_MWB_RED_GAIN_H_REG		0x3400
++#define OV5693_MWB_RED_GAIN_L_REG		0x3401
++#define OV5693_MWB_GREEN_GAIN_H_REG		0x3402
++#define OV5693_MWB_GREEN_GAIN_L_REG		0x3403
++#define OV5693_MWB_BLUE_GAIN_H_REG		0x3404
++#define OV5693_MWB_BLUE_GAIN_L_REG		0x3405
++#define OV5693_MWB_GAIN_H_CTRL(v)		(((v) >> 8) & GENMASK(3, 0))
++#define OV5693_MWB_GAIN_L_CTRL(v)		((v) & GENMASK(7, 0))
++#define OV5693_MWB_GAIN_MAX			0x0fff
++#define OV5693_DIGITAL_GAIN_MIN			1
++#define OV5693_DIGITAL_GAIN_MAX			4095
++#define OV5693_DIGITAL_GAIN_DEF			1024
++#define OV5693_DIGITAL_GAIN_STEP		1
++
++/* Timing and Format */
++#define OV5693_CROP_START_X_H_REG		0x3800
++#define OV5693_CROP_START_X_H(v)		(((v) & GENMASK(12, 8)) >> 8)
++#define OV5693_CROP_START_X_L_REG		0x3801
++#define OV5693_CROP_START_X_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_CROP_START_Y_H_REG		0x3802
++#define OV5693_CROP_START_Y_H(v)		(((v) & GENMASK(11, 8)) >> 8)
++#define OV5693_CROP_START_Y_L_REG		0x3803
++#define OV5693_CROP_START_Y_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_CROP_END_X_H_REG			0x3804
++#define OV5693_CROP_END_X_H(v)			(((v) & GENMASK(12, 8)) >> 8)
++#define OV5693_CROP_END_X_L_REG			0x3805
++#define OV5693_CROP_END_X_L(v)			((v) & GENMASK(7, 0))
++
++#define OV5693_CROP_END_Y_H_REG			0x3806
++#define OV5693_CROP_END_Y_H(v)			(((v) & GENMASK(11, 8)) >> 8)
++#define OV5693_CROP_END_Y_L_REG			0x3807
++#define OV5693_CROP_END_Y_L(v)			((v) & GENMASK(7, 0))
++
++#define OV5693_OUTPUT_SIZE_X_H_REG		0x3808
++#define OV5693_OUTPUT_SIZE_X_H(v)		(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_OUTPUT_SIZE_X_L_REG		0x3809
++#define OV5693_OUTPUT_SIZE_X_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_OUTPUT_SIZE_Y_H_REG		0x380a
++#define OV5693_OUTPUT_SIZE_Y_H(v)		(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_OUTPUT_SIZE_Y_L_REG		0x380b
++#define OV5693_OUTPUT_SIZE_Y_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_TIMING_HTS_H_REG			0x380c
++#define OV5693_TIMING_HTS_H(v)			(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_TIMING_HTS_L_REG			0x380d
++#define OV5693_TIMING_HTS_L(v)			((v) & GENMASK(7, 0))
++#define OV5693_FIXED_PPL			2688U
++
++#define OV5693_TIMING_VTS_H_REG			0x380e
++#define OV5693_TIMING_VTS_H(v)			(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_TIMING_VTS_L_REG			0x380f
++#define OV5693_TIMING_VTS_L(v)			((v) & GENMASK(7, 0))
++#define OV5693_TIMING_MAX_VTS			0xffff
++#define OV5693_TIMING_MIN_VTS			0x04
++
++#define OV5693_OFFSET_START_X_H_REG		0x3810
++#define OV5693_OFFSET_START_X_H(v)		(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_OFFSET_START_X_L_REG		0x3811
++#define OV5693_OFFSET_START_X_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_OFFSET_START_Y_H_REG		0x3812
++#define OV5693_OFFSET_START_Y_H(v)		(((v) & GENMASK(15, 8)) >> 8)
++#define OV5693_OFFSET_START_Y_L_REG		0x3813
++#define OV5693_OFFSET_START_Y_L(v)		((v) & GENMASK(7, 0))
++
++#define OV5693_SUB_INC_X_REG			0x3814
++#define OV5693_SUB_INC_Y_REG			0x3815
++
++#define OV5693_FORMAT1_REG			0x3820
++#define OV5693_FORMAT1_FLIP_VERT_ISP_EN		BIT(2)
++#define OV5693_FORMAT1_FLIP_VERT_SENSOR_EN	BIT(1)
++#define OV5693_FORMAT1_VBIN_EN			BIT(0)
++#define OV5693_FORMAT2_REG			0x3821
++#define OV5693_FORMAT2_HDR_EN			BIT(7)
++#define OV5693_FORMAT2_FLIP_HORZ_ISP_EN		BIT(2)
++#define OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN	BIT(1)
++#define OV5693_FORMAT2_HBIN_EN			BIT(0)
++
++#define OV5693_ISP_CTRL2_REG			0x5002
++#define OV5693_ISP_SCALE_ENABLE			BIT(7)
++
++/* Pixel Array */
++#define OV5693_NATIVE_WIDTH			2624
++#define OV5693_NATIVE_HEIGHT			1956
++#define OV5693_NATIVE_START_LEFT		0
++#define OV5693_NATIVE_START_TOP			0
++#define OV5693_ACTIVE_WIDTH			2592
++#define OV5693_ACTIVE_HEIGHT			1944
++#define OV5693_ACTIVE_START_LEFT		16
++#define OV5693_ACTIVE_START_TOP			6
++#define OV5693_MIN_CROP_WIDTH			2
++#define OV5693_MIN_CROP_HEIGHT			2
++
++/* Test Pattern */
++#define OV5693_TEST_PATTERN_REG			0x5e00
++#define OV5693_TEST_PATTERN_ENABLE		BIT(7)
++#define OV5693_TEST_PATTERN_ROLLING		BIT(6)
++#define OV5693_TEST_PATTERN_RANDOM		0x01
++#define OV5693_TEST_PATTERN_BARS		0x00
++
++/* System Frequencies */
++#define OV5693_XVCLK_FREQ			19200000
++#define OV5693_LINK_FREQ_400MHZ			400000000
++#define OV5693_PIXEL_RATE			160000000
++
++/* Miscellaneous */
++#define OV5693_NUM_SUPPLIES			2
++
++#define to_ov5693_sensor(x) container_of(x, struct ov5693_device, sd)
++
++struct ov5693_reg {
++	u16 reg;
++	u8 val;
++};
++
++struct ov5693_reg_list {
++	u32 num_regs;
++	const struct ov5693_reg *regs;
++};
++
++struct ov5693_device {
++	struct i2c_client *client;
++	struct device *dev;
++
++	/* Protect against concurrent changes to controls */
++	struct mutex lock;
++
++	struct gpio_desc *reset;
++	struct gpio_desc *powerdown;
++	struct regulator_bulk_data supplies[OV5693_NUM_SUPPLIES];
++	struct clk *clk;
++
++	struct ov5693_mode {
++		struct v4l2_rect crop;
++		struct v4l2_mbus_framefmt format;
++		bool binning_x;
++		bool binning_y;
++		unsigned int inc_x_odd;
++		unsigned int inc_y_odd;
++		unsigned int vts;
++	} mode;
++	bool streaming;
++
++	struct v4l2_subdev sd;
++	struct media_pad pad;
++
++	struct ov5693_v4l2_ctrls {
++		struct v4l2_ctrl_handler handler;
++		struct v4l2_ctrl *link_freq;
++		struct v4l2_ctrl *pixel_rate;
++		struct v4l2_ctrl *exposure;
++		struct v4l2_ctrl *analogue_gain;
++		struct v4l2_ctrl *digital_gain;
++		struct v4l2_ctrl *hflip;
++		struct v4l2_ctrl *vflip;
++		struct v4l2_ctrl *hblank;
++		struct v4l2_ctrl *vblank;
++		struct v4l2_ctrl *test_pattern;
++	} ctrls;
++};
++
++static const struct ov5693_reg ov5693_global_regs[] = {
++	{0x3016, 0xf0},
++	{0x3017, 0xf0},
++	{0x3018, 0xf0},
++	{0x3022, 0x01},
++	{0x3028, 0x44},
++	{0x3098, 0x02},
++	{0x3099, 0x19},
++	{0x309a, 0x02},
++	{0x309b, 0x01},
++	{0x309c, 0x00},
++	{0x30a0, 0xd2},
++	{0x30a2, 0x01},
++	{0x30b2, 0x00},
++	{0x30b3, 0x7d},
++	{0x30b4, 0x03},
++	{0x30b5, 0x04},
++	{0x30b6, 0x01},
++	{0x3104, 0x21},
++	{0x3106, 0x00},
++	{0x3406, 0x01},
++	{0x3503, 0x07},
++	{0x350b, 0x40},
++	{0x3601, 0x0a},
++	{0x3602, 0x38},
++	{0x3612, 0x80},
++	{0x3620, 0x54},
++	{0x3621, 0xc7},
++	{0x3622, 0x0f},
++	{0x3625, 0x10},
++	{0x3630, 0x55},
++	{0x3631, 0xf4},
++	{0x3632, 0x00},
++	{0x3633, 0x34},
++	{0x3634, 0x02},
++	{0x364d, 0x0d},
++	{0x364f, 0xdd},
++	{0x3660, 0x04},
++	{0x3662, 0x10},
++	{0x3663, 0xf1},
++	{0x3665, 0x00},
++	{0x3666, 0x20},
++	{0x3667, 0x00},
++	{0x366a, 0x80},
++	{0x3680, 0xe0},
++	{0x3681, 0x00},
++	{0x3700, 0x42},
++	{0x3701, 0x14},
++	{0x3702, 0xa0},
++	{0x3703, 0xd8},
++	{0x3704, 0x78},
++	{0x3705, 0x02},
++	{0x370a, 0x00},
++	{0x370b, 0x20},
++	{0x370c, 0x0c},
++	{0x370d, 0x11},
++	{0x370e, 0x00},
++	{0x370f, 0x40},
++	{0x3710, 0x00},
++	{0x371a, 0x1c},
++	{0x371b, 0x05},
++	{0x371c, 0x01},
++	{0x371e, 0xa1},
++	{0x371f, 0x0c},
++	{0x3721, 0x00},
++	{0x3724, 0x10},
++	{0x3726, 0x00},
++	{0x372a, 0x01},
++	{0x3730, 0x10},
++	{0x3738, 0x22},
++	{0x3739, 0xe5},
++	{0x373a, 0x50},
++	{0x373b, 0x02},
++	{0x373c, 0x41},
++	{0x373f, 0x02},
++	{0x3740, 0x42},
++	{0x3741, 0x02},
++	{0x3742, 0x18},
++	{0x3743, 0x01},
++	{0x3744, 0x02},
++	{0x3747, 0x10},
++	{0x374c, 0x04},
++	{0x3751, 0xf0},
++	{0x3752, 0x00},
++	{0x3753, 0x00},
++	{0x3754, 0xc0},
++	{0x3755, 0x00},
++	{0x3756, 0x1a},
++	{0x3758, 0x00},
++	{0x3759, 0x0f},
++	{0x376b, 0x44},
++	{0x375c, 0x04},
++	{0x3774, 0x10},
++	{0x3776, 0x00},
++	{0x377f, 0x08},
++	{0x3780, 0x22},
++	{0x3781, 0x0c},
++	{0x3784, 0x2c},
++	{0x3785, 0x1e},
++	{0x378f, 0xf5},
++	{0x3791, 0xb0},
++	{0x3795, 0x00},
++	{0x3796, 0x64},
++	{0x3797, 0x11},
++	{0x3798, 0x30},
++	{0x3799, 0x41},
++	{0x379a, 0x07},
++	{0x379b, 0xb0},
++	{0x379c, 0x0c},
++	{0x3a04, 0x06},
++	{0x3a05, 0x14},
++	{0x3e07, 0x20},
++	{0x4000, 0x08},
++	{0x4001, 0x04},
++	{0x4004, 0x08},
++	{0x4006, 0x20},
++	{0x4008, 0x24},
++	{0x4009, 0x10},
++	{0x4058, 0x00},
++	{0x4101, 0xb2},
++	{0x4307, 0x31},
++	{0x4511, 0x05},
++	{0x4512, 0x01},
++	{0x481f, 0x30},
++	{0x4826, 0x2c},
++	{0x4d02, 0xfd},
++	{0x4d03, 0xf5},
++	{0x4d04, 0x0c},
++	{0x4d05, 0xcc},
++	{0x4837, 0x0a},
++	{0x5003, 0x20},
++	{0x5013, 0x00},
++	{0x5842, 0x01},
++	{0x5843, 0x2b},
++	{0x5844, 0x01},
++	{0x5845, 0x92},
++	{0x5846, 0x01},
++	{0x5847, 0x8f},
++	{0x5848, 0x01},
++	{0x5849, 0x0c},
++	{0x5e10, 0x0c},
++	{0x3820, 0x00},
++	{0x3821, 0x1e},
++	{0x5041, 0x14}
++};
++
++static const struct ov5693_reg_list ov5693_global_setting = {
++	.num_regs = ARRAY_SIZE(ov5693_global_regs),
++	.regs = ov5693_global_regs,
++};
++
++static const struct v4l2_rect ov5693_default_crop = {
++	.left = OV5693_ACTIVE_START_LEFT,
++	.top = OV5693_ACTIVE_START_TOP,
++	.width = OV5693_ACTIVE_WIDTH,
++	.height = OV5693_ACTIVE_HEIGHT,
++};
++
++static const struct v4l2_mbus_framefmt ov5693_default_fmt = {
++	.width = OV5693_ACTIVE_WIDTH,
++	.height = OV5693_ACTIVE_HEIGHT,
++	.code = MEDIA_BUS_FMT_SBGGR10_1X10,
++};
++
++static const s64 link_freq_menu_items[] = {
++	OV5693_LINK_FREQ_400MHZ
++};
++
++static const char * const ov5693_supply_names[] = {
++	"avdd",
++	"dovdd",
++};
++
++static const char * const ov5693_test_pattern_menu[] = {
++	"Disabled",
++	"Random Data",
++	"Colour Bars",
++	"Colour Bars with Rolling Bar"
++};
++
++static const u8 ov5693_test_pattern_bits[] = {
++	0,
++	OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_RANDOM,
++	OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS,
++	OV5693_TEST_PATTERN_ENABLE | OV5693_TEST_PATTERN_BARS |
++	OV5693_TEST_PATTERN_ROLLING,
++};
++
++/* I2C I/O Operations */
++
++static int ov5693_read_reg(struct ov5693_device *ov5693, u16 addr, u8 *value)
++{
++	struct i2c_client *client = ov5693->client;
++	struct i2c_msg msgs[2];
++	u8 addr_buf[2];
++	u8 data_buf;
++	int ret;
++
++	put_unaligned_be16(addr, addr_buf);
++
++	/* Write register address */
++	msgs[0].addr = client->addr;
++	msgs[0].flags = 0;
++	msgs[0].len = ARRAY_SIZE(addr_buf);
++	msgs[0].buf = addr_buf;
++
++	/* Read register value */
++	msgs[1].addr = client->addr;
++	msgs[1].flags = I2C_M_RD;
++	msgs[1].len = 1;
++	msgs[1].buf = &data_buf;
++
++	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
++	if (ret != ARRAY_SIZE(msgs))
++		return -EIO;
++
++	*value = data_buf;
++
++	return 0;
++}
++
++static void ov5693_write_reg(struct ov5693_device *ov5693, u16 addr, u8 value,
++			     int *error)
++{
++	unsigned char data[3] = { addr >> 8, addr & 0xff, value };
++	int ret;
++
++	if (*error < 0)
++		return;
++
++	ret = i2c_master_send(ov5693->client, data, sizeof(data));
++	if (ret < 0) {
++		dev_dbg(ov5693->dev, "i2c send error at address 0x%04x: %d\n",
++			addr, ret);
++		*error = ret;
++	}
++}
++
++static int ov5693_write_reg_array(struct ov5693_device *ov5693,
++				  const struct ov5693_reg_list *reglist)
++{
++	unsigned int i;
++	int ret = 0;
++
++	for (i = 0; i < reglist->num_regs; i++)
++		ov5693_write_reg(ov5693, reglist->regs[i].reg,
++				 reglist->regs[i].val, &ret);
++
++	return ret;
++}
++
++static int ov5693_update_bits(struct ov5693_device *ov5693, u16 address,
++			      u16 mask, u16 bits)
++{
++	u8 value = 0;
++	int ret;
++
++	ret = ov5693_read_reg(ov5693, address, &value);
++	if (ret)
++		return ret;
++
++	value &= ~mask;
++	value |= bits;
++
++	ov5693_write_reg(ov5693, address, value, &ret);
++
++	return ret;
++}
++
++/* V4L2 Controls Functions */
++
++static int ov5693_flip_vert_configure(struct ov5693_device *ov5693, bool enable)
++{
++	u8 bits = OV5693_FORMAT1_FLIP_VERT_ISP_EN |
++		  OV5693_FORMAT1_FLIP_VERT_SENSOR_EN;
++	int ret;
++
++	ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG, bits,
++				 enable ? bits : 0);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int ov5693_flip_horz_configure(struct ov5693_device *ov5693, bool enable)
++{
++	u8 bits = OV5693_FORMAT2_FLIP_HORZ_ISP_EN |
++		  OV5693_FORMAT2_FLIP_HORZ_SENSOR_EN;
++	int ret;
++
++	ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG, bits,
++				 enable ? bits : 0);
++	if (ret)
++		return ret;
++
++	return 0;
++}
++
++static int ov5693_get_exposure(struct ov5693_device *ov5693, s32 *value)
++{
++	u8 exposure_hh = 0, exposure_h = 0, exposure_l = 0;
++	int ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG, &exposure_hh);
++	if (ret)
++		return ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG, &exposure_h);
++	if (ret)
++		return ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG, &exposure_l);
++	if (ret)
++		return ret;
++
++	/* The lowest 4 bits are unsupported fractional bits */
++	*value = ((exposure_hh << 16) | (exposure_h << 8) | exposure_l) >> 4;
++
++	return 0;
++}
++
++static int ov5693_exposure_configure(struct ov5693_device *ov5693, u32 exposure)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_HH_REG,
++			 OV5693_EXPOSURE_CTRL_HH(exposure), &ret);
++	ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_H_REG,
++			 OV5693_EXPOSURE_CTRL_H(exposure), &ret);
++	ov5693_write_reg(ov5693, OV5693_EXPOSURE_L_CTRL_L_REG,
++			 OV5693_EXPOSURE_CTRL_L(exposure), &ret);
++
++	return ret;
++}
++
++static int ov5693_get_gain(struct ov5693_device *ov5693, u32 *gain)
++{
++	u8 gain_l = 0, gain_h = 0;
++	int ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_H_REG, &gain_h);
++	if (ret)
++		return ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_GAIN_CTRL_L_REG, &gain_l);
++	if (ret)
++		return ret;
++
++	/* As with exposure, the lowest 4 bits are fractional bits. */
++	*gain = ((gain_h << 8) | gain_l) >> 4;
++
++	return ret;
++}
++
++static int ov5693_digital_gain_configure(struct ov5693_device *ov5693, u32 gain)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_H_REG,
++			 OV5693_MWB_GAIN_H_CTRL(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_MWB_RED_GAIN_L_REG,
++			 OV5693_MWB_GAIN_L_CTRL(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_H_REG,
++			 OV5693_MWB_GAIN_H_CTRL(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_MWB_GREEN_GAIN_L_REG,
++			 OV5693_MWB_GAIN_L_CTRL(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_H_REG,
++			 OV5693_MWB_GAIN_H_CTRL(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_MWB_BLUE_GAIN_L_REG,
++			 OV5693_MWB_GAIN_L_CTRL(gain), &ret);
++
++	return ret;
++}
++
++static int ov5693_analog_gain_configure(struct ov5693_device *ov5693, u32 gain)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_L_REG,
++			 OV5693_GAIN_CTRL_L(gain), &ret);
++	ov5693_write_reg(ov5693, OV5693_GAIN_CTRL_H_REG,
++			 OV5693_GAIN_CTRL_H(gain), &ret);
++
++	return ret;
++}
++
++static int ov5693_vts_configure(struct ov5693_device *ov5693, u32 vblank)
++{
++	u16 vts = ov5693->mode.format.height + vblank;
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG,
++			 OV5693_TIMING_VTS_H(vts), &ret);
++	ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG,
++			 OV5693_TIMING_VTS_L(vts), &ret);
++
++	return ret;
++}
++
++static int ov5693_test_pattern_configure(struct ov5693_device *ov5693, u32 idx)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_TEST_PATTERN_REG,
++			 ov5693_test_pattern_bits[idx], &ret);
++
++	return ret;
++}
++
++static int ov5693_s_ctrl(struct v4l2_ctrl *ctrl)
++{
++	struct ov5693_device *ov5693 =
++	    container_of(ctrl->handler, struct ov5693_device, ctrls.handler);
++	int ret = 0;
++
++	/* If VBLANK is altered we need to update exposure to compensate */
++	if (ctrl->id == V4L2_CID_VBLANK) {
++		int exposure_max;
++
++		exposure_max = ov5693->mode.format.height + ctrl->val -
++			       OV5693_INTEGRATION_TIME_MARGIN;
++		__v4l2_ctrl_modify_range(ov5693->ctrls.exposure,
++					 ov5693->ctrls.exposure->minimum,
++					 exposure_max,
++					 ov5693->ctrls.exposure->step,
++					 min(ov5693->ctrls.exposure->val, exposure_max));
++	}
++
++	/* Only apply changes to the controls if the device is powered up */
++	if (!pm_runtime_get_if_in_use(ov5693->dev))
++		return 0;
++
++	switch (ctrl->id) {
++	case V4L2_CID_EXPOSURE:
++		ret = ov5693_exposure_configure(ov5693, ctrl->val);
++		break;
++	case V4L2_CID_ANALOGUE_GAIN:
++		ret = ov5693_analog_gain_configure(ov5693, ctrl->val);
++		break;
++	case V4L2_CID_DIGITAL_GAIN:
++		ret = ov5693_digital_gain_configure(ov5693, ctrl->val);
++		break;
++	case V4L2_CID_HFLIP:
++		ret = ov5693_flip_horz_configure(ov5693, !!ctrl->val);
++		break;
++	case V4L2_CID_VFLIP:
++		ret = ov5693_flip_vert_configure(ov5693, !!ctrl->val);
++		break;
++	case V4L2_CID_VBLANK:
++		ret = ov5693_vts_configure(ov5693, ctrl->val);
++		break;
++	case V4L2_CID_TEST_PATTERN:
++		ret = ov5693_test_pattern_configure(ov5693, ctrl->val);
++		break;
++	default:
++		ret = -EINVAL;
++	}
++
++	pm_runtime_put(ov5693->dev);
++
++	return ret;
++}
++
++static int ov5693_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
++{
++	struct ov5693_device *ov5693 =
++	    container_of(ctrl->handler, struct ov5693_device, ctrls.handler);
++
++	switch (ctrl->id) {
++	case V4L2_CID_EXPOSURE_ABSOLUTE:
++		return ov5693_get_exposure(ov5693, &ctrl->val);
++	case V4L2_CID_AUTOGAIN:
++		return ov5693_get_gain(ov5693, &ctrl->val);
++	default:
++		return -EINVAL;
++	}
++}
++
++static const struct v4l2_ctrl_ops ov5693_ctrl_ops = {
++	.s_ctrl = ov5693_s_ctrl,
++	.g_volatile_ctrl = ov5693_g_volatile_ctrl
++};
++
++/* System Control Functions */
++
++static int ov5693_mode_configure(struct ov5693_device *ov5693)
++{
++	const struct ov5693_mode *mode = &ov5693->mode;
++	int ret = 0;
++
++	/* Crop Start X */
++	ov5693_write_reg(ov5693, OV5693_CROP_START_X_H_REG,
++			 OV5693_CROP_START_X_H(mode->crop.left), &ret);
++	ov5693_write_reg(ov5693, OV5693_CROP_START_X_L_REG,
++			 OV5693_CROP_START_X_L(mode->crop.left), &ret);
++
++	/* Offset X */
++	ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_H_REG,
++			 OV5693_OFFSET_START_X_H(0), &ret);
++	ov5693_write_reg(ov5693, OV5693_OFFSET_START_X_L_REG,
++			 OV5693_OFFSET_START_X_L(0), &ret);
++
++	/* Output Size X */
++	ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_H_REG,
++			 OV5693_OUTPUT_SIZE_X_H(mode->format.width), &ret);
++	ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_X_L_REG,
++			 OV5693_OUTPUT_SIZE_X_L(mode->format.width), &ret);
++
++	/* Crop End X */
++	ov5693_write_reg(ov5693, OV5693_CROP_END_X_H_REG,
++			 OV5693_CROP_END_X_H(mode->crop.left + mode->crop.width),
++			 &ret);
++	ov5693_write_reg(ov5693, OV5693_CROP_END_X_L_REG,
++			 OV5693_CROP_END_X_L(mode->crop.left + mode->crop.width),
++			 &ret);
++
++	/* Horizontal Total Size */
++	ov5693_write_reg(ov5693, OV5693_TIMING_HTS_H_REG,
++			 OV5693_TIMING_HTS_H(OV5693_FIXED_PPL), &ret);
++	ov5693_write_reg(ov5693, OV5693_TIMING_HTS_L_REG,
++			 OV5693_TIMING_HTS_L(OV5693_FIXED_PPL), &ret);
++
++	/* Crop Start Y */
++	ov5693_write_reg(ov5693, OV5693_CROP_START_Y_H_REG,
++			 OV5693_CROP_START_Y_H(mode->crop.top), &ret);
++	ov5693_write_reg(ov5693, OV5693_CROP_START_Y_L_REG,
++			 OV5693_CROP_START_Y_L(mode->crop.top), &ret);
++
++	/* Offset Y */
++	ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_H_REG,
++			 OV5693_OFFSET_START_Y_H(0), &ret);
++	ov5693_write_reg(ov5693, OV5693_OFFSET_START_Y_L_REG,
++			 OV5693_OFFSET_START_Y_L(0), &ret);
++
++	/* Output Size Y */
++	ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_H_REG,
++			 OV5693_OUTPUT_SIZE_Y_H(mode->format.height), &ret);
++	ov5693_write_reg(ov5693, OV5693_OUTPUT_SIZE_Y_L_REG,
++			 OV5693_OUTPUT_SIZE_Y_L(mode->format.height), &ret);
++
++	/* Crop End Y */
++	ov5693_write_reg(ov5693, OV5693_CROP_END_Y_H_REG,
++			 OV5693_CROP_END_Y_H(mode->crop.top + mode->crop.height),
++			 &ret);
++	ov5693_write_reg(ov5693, OV5693_CROP_END_Y_L_REG,
++			 OV5693_CROP_END_Y_L(mode->crop.top + mode->crop.height),
++			 &ret);
++
++	/* Vertical Total Size */
++	ov5693_write_reg(ov5693, OV5693_TIMING_VTS_H_REG,
++			 OV5693_TIMING_VTS_H(mode->vts), &ret);
++	ov5693_write_reg(ov5693, OV5693_TIMING_VTS_L_REG,
++			 OV5693_TIMING_VTS_L(mode->vts), &ret);
++
++	/* Subsample X increase */
++	ov5693_write_reg(ov5693, OV5693_SUB_INC_X_REG,
++			 ((mode->inc_x_odd << 4) & 0xf0) | 0x01, &ret);
++	/* Subsample Y increase */
++	ov5693_write_reg(ov5693, OV5693_SUB_INC_Y_REG,
++			 ((mode->inc_y_odd << 4) & 0xf0) | 0x01, &ret);
++
++	/* Binning */
++	ret = ov5693_update_bits(ov5693, OV5693_FORMAT1_REG,
++				 OV5693_FORMAT1_VBIN_EN,
++				 mode->binning_y ? OV5693_FORMAT1_VBIN_EN : 0);
++	if (ret)
++		return ret;
++
++	ret = ov5693_update_bits(ov5693, OV5693_FORMAT2_REG,
++				 OV5693_FORMAT2_HBIN_EN,
++				 mode->binning_x ? OV5693_FORMAT2_HBIN_EN : 0);
++
++	return ret;
++}
++
++static int ov5693_sw_standby(struct ov5693_device *ov5693, bool standby)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_SW_STREAM_REG,
++			 standby ? OV5693_STOP_STREAMING : OV5693_START_STREAMING,
++			 &ret);
++
++	return ret;
++}
++
++static int ov5693_sw_reset(struct ov5693_device *ov5693)
++{
++	int ret = 0;
++
++	ov5693_write_reg(ov5693, OV5693_SW_RESET_REG, OV5693_SW_RESET, &ret);
++
++	return ret;
++}
++
++static int ov5693_sensor_init(struct ov5693_device *ov5693)
++{
++	int ret = 0;
++
++	ret = ov5693_sw_reset(ov5693);
++	if (ret) {
++		dev_err(ov5693->dev, "%s software reset error\n", __func__);
++		return ret;
++	}
++
++	ret = ov5693_write_reg_array(ov5693, &ov5693_global_setting);
++	if (ret) {
++		dev_err(ov5693->dev, "%s global settings error\n", __func__);
++		return ret;
++	}
++
++	ret = ov5693_mode_configure(ov5693);
++	if (ret) {
++		dev_err(ov5693->dev, "%s mode configure error\n", __func__);
++		return ret;
++	}
++
++	ret = ov5693_sw_standby(ov5693, true);
++	if (ret)
++		dev_err(ov5693->dev, "%s software standby error\n", __func__);
++
++	return ret;
++}
++
++static void ov5693_sensor_powerdown(struct ov5693_device *ov5693)
++{
++	gpiod_set_value_cansleep(ov5693->reset, 1);
++	gpiod_set_value_cansleep(ov5693->powerdown, 1);
++
++	regulator_bulk_disable(OV5693_NUM_SUPPLIES, ov5693->supplies);
++
++	clk_disable_unprepare(ov5693->clk);
++}
++
++static int ov5693_sensor_powerup(struct ov5693_device *ov5693)
++{
++	int ret;
++
++	gpiod_set_value_cansleep(ov5693->reset, 1);
++	gpiod_set_value_cansleep(ov5693->powerdown, 1);
++
++	ret = clk_prepare_enable(ov5693->clk);
++	if (ret) {
++		dev_err(ov5693->dev, "Failed to enable clk\n");
++		goto fail_power;
++	}
++
++	ret = regulator_bulk_enable(OV5693_NUM_SUPPLIES, ov5693->supplies);
++	if (ret) {
++		dev_err(ov5693->dev, "Failed to enable regulators\n");
++		goto fail_power;
++	}
++
++	gpiod_set_value_cansleep(ov5693->powerdown, 0);
++	gpiod_set_value_cansleep(ov5693->reset, 0);
++
++	usleep_range(5000, 7500);
++
++	return 0;
++
++fail_power:
++	ov5693_sensor_powerdown(ov5693);
++	return ret;
++}
++
++static int __maybe_unused ov5693_sensor_suspend(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++
++	ov5693_sensor_powerdown(ov5693);
++
++	return 0;
++}
++
++static int __maybe_unused ov5693_sensor_resume(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	int ret;
++
++	mutex_lock(&ov5693->lock);
++
++	ret = ov5693_sensor_powerup(ov5693);
++	if (ret)
++		goto out_unlock;
++
++	ret = ov5693_sensor_init(ov5693);
++	if (ret) {
++		dev_err(dev, "ov5693 sensor init failure\n");
++		goto err_power;
++	}
++
++	goto out_unlock;
++
++err_power:
++	ov5693_sensor_powerdown(ov5693);
++out_unlock:
++	mutex_unlock(&ov5693->lock);
++	return ret;
++}
++
++static int ov5693_detect(struct ov5693_device *ov5693)
++{
++	u8 id_l = 0, id_h = 0;
++	u16 id = 0;
++	int ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_H, &id_h);
++	if (ret)
++		return ret;
++
++	ret = ov5693_read_reg(ov5693, OV5693_REG_CHIP_ID_L, &id_l);
++	if (ret)
++		return ret;
++
++	id = (id_h << 8) | id_l;
++
++	if (id != OV5693_CHIP_ID) {
++		dev_err(ov5693->dev, "sensor ID mismatch. Found 0x%04x\n", id);
++		return -ENODEV;
++	}
++
++	return 0;
++}
++
++/* V4L2 Framework callbacks */
++
++static unsigned int __ov5693_calc_vts(u32 height)
++{
++	/*
++	 * We need to set a sensible default VTS for whatever format height we
++	 * happen to be given from set_fmt(). This function just targets
++	 * an even multiple of 30fps.
++	 */
++
++	unsigned int tgt_fps;
++
++	tgt_fps = rounddown(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / height, 30);
++
++	return ALIGN_DOWN(OV5693_PIXEL_RATE / OV5693_FIXED_PPL / tgt_fps, 2);
++}
++
++static struct v4l2_mbus_framefmt *
++__ov5693_get_pad_format(struct ov5693_device *ov5693,
++			struct v4l2_subdev_pad_config *cfg,
++			unsigned int pad, enum v4l2_subdev_format_whence which)
++{
++	switch (which) {
++	case V4L2_SUBDEV_FORMAT_TRY:
++		return v4l2_subdev_get_try_format(&ov5693->sd, cfg, pad);
++	case V4L2_SUBDEV_FORMAT_ACTIVE:
++		return &ov5693->mode.format;
++	default:
++		return NULL;
++	}
++}
++
++static struct v4l2_rect *
++__ov5693_get_pad_crop(struct ov5693_device *ov5693,
++		      struct v4l2_subdev_pad_config *cfg,
++		      unsigned int pad, enum v4l2_subdev_format_whence which)
++{
++	switch (which) {
++	case V4L2_SUBDEV_FORMAT_TRY:
++		return v4l2_subdev_get_try_crop(&ov5693->sd, cfg, pad);
++	case V4L2_SUBDEV_FORMAT_ACTIVE:
++		return &ov5693->mode.crop;
++	}
++
++	return NULL;
++}
++
++static int ov5693_get_fmt(struct v4l2_subdev *sd,
++			  struct v4l2_subdev_pad_config *cfg,
++			  struct v4l2_subdev_format *format)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++
++	format->format = ov5693->mode.format;
++
++	return 0;
++}
++
++static int ov5693_set_fmt(struct v4l2_subdev *sd,
++			  struct v4l2_subdev_pad_config *cfg,
++			  struct v4l2_subdev_format *format)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	const struct v4l2_rect *crop;
++	struct v4l2_mbus_framefmt *fmt;
++	unsigned int hratio, vratio;
++	unsigned int width, height;
++	unsigned int hblank;
++	int exposure_max;
++	int ret = 0;
++
++	crop = __ov5693_get_pad_crop(ov5693, cfg, format->pad, format->which);
++
++	/*
++	 * Align to two to simplify the binning calculations below, and clamp
++	 * the requested format at the crop rectangle
++	 */
++	width = clamp_t(unsigned int, ALIGN(format->format.width, 2),
++			OV5693_MIN_CROP_WIDTH, crop->width);
++	height = clamp_t(unsigned int, ALIGN(format->format.height, 2),
++			 OV5693_MIN_CROP_HEIGHT, crop->height);
++
++	/*
++	 * We can only support setting either the dimensions of the crop rect
++	 * or those dimensions binned (separately) by a factor of two.
++	 */
++	hratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->width, width), 1, 2);
++	vratio = clamp_t(unsigned int, DIV_ROUND_CLOSEST(crop->height, height), 1, 2);
++
++	fmt = __ov5693_get_pad_format(ov5693, cfg, format->pad, format->which);
++
++	fmt->width = crop->width / hratio;
++	fmt->height = crop->height / vratio;
++	fmt->code = MEDIA_BUS_FMT_SBGGR10_1X10;
++
++	format->format = *fmt;
++
++	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
++		return ret;
++
++	mutex_lock(&ov5693->lock);
++
++	ov5693->mode.binning_x = hratio > 1 ? true : false;
++	ov5693->mode.inc_x_odd = hratio > 1 ? 3 : 1;
++	ov5693->mode.binning_y = vratio > 1 ? true : false;
++	ov5693->mode.inc_y_odd = vratio > 1 ? 3 : 1;
++
++	ov5693->mode.vts = __ov5693_calc_vts(fmt->height);
++
++	__v4l2_ctrl_modify_range(ov5693->ctrls.vblank,
++				 OV5693_TIMING_MIN_VTS,
++				 OV5693_TIMING_MAX_VTS - fmt->height,
++				 1, ov5693->mode.vts - fmt->height);
++	__v4l2_ctrl_s_ctrl(ov5693->ctrls.vblank,
++			   ov5693->mode.vts - fmt->height);
++
++	hblank = OV5693_FIXED_PPL - fmt->width;
++	__v4l2_ctrl_modify_range(ov5693->ctrls.hblank, hblank, hblank, 1,
++				 hblank);
++
++	exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN;
++	__v4l2_ctrl_modify_range(ov5693->ctrls.exposure,
++				 ov5693->ctrls.exposure->minimum, exposure_max,
++				 ov5693->ctrls.exposure->step,
++				 min(ov5693->ctrls.exposure->val, exposure_max));
++
++	mutex_unlock(&ov5693->lock);
++	return ret;
++}
++
++static int ov5693_get_selection(struct v4l2_subdev *sd,
++				struct v4l2_subdev_pad_config *cfg,
++				struct v4l2_subdev_selection *sel)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++
++	switch (sel->target) {
++	case V4L2_SEL_TGT_CROP:
++		mutex_lock(&ov5693->lock);
++		sel->r = *__ov5693_get_pad_crop(ov5693, cfg, sel->pad,
++						sel->which);
++		mutex_unlock(&ov5693->lock);
++		break;
++	case V4L2_SEL_TGT_NATIVE_SIZE:
++		sel->r.top = 0;
++		sel->r.left = 0;
++		sel->r.width = OV5693_NATIVE_WIDTH;
++		sel->r.height = OV5693_NATIVE_HEIGHT;
++		break;
++	case V4L2_SEL_TGT_CROP_BOUNDS:
++	case V4L2_SEL_TGT_CROP_DEFAULT:
++		sel->r.top = OV5693_ACTIVE_START_TOP;
++		sel->r.left = OV5693_ACTIVE_START_LEFT;
++		sel->r.width = OV5693_ACTIVE_WIDTH;
++		sel->r.height = OV5693_ACTIVE_HEIGHT;
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static int ov5693_set_selection(struct v4l2_subdev *sd,
++				struct v4l2_subdev_pad_config *cfg,
++				struct v4l2_subdev_selection *sel)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	struct v4l2_mbus_framefmt *format;
++	struct v4l2_rect *__crop;
++	struct v4l2_rect rect;
++
++	if (sel->target != V4L2_SEL_TGT_CROP)
++		return -EINVAL;
++
++	/*
++	 * Clamp the boundaries of the crop rectangle to the size of the sensor
++	 * pixel array. Align to multiples of 2 to ensure Bayer pattern isn't
++	 * disrupted.
++	 */
++	rect.left = clamp(ALIGN(sel->r.left, 2), OV5693_NATIVE_START_LEFT,
++			  OV5693_NATIVE_WIDTH);
++	rect.top = clamp(ALIGN(sel->r.top, 2), OV5693_NATIVE_START_TOP,
++			 OV5693_NATIVE_HEIGHT);
++	rect.width = clamp_t(unsigned int, ALIGN(sel->r.width, 2),
++			     OV5693_MIN_CROP_WIDTH, OV5693_NATIVE_WIDTH);
++	rect.height = clamp_t(unsigned int, ALIGN(sel->r.height, 2),
++			      OV5693_MIN_CROP_HEIGHT, OV5693_NATIVE_HEIGHT);
++
++	/* Make sure the crop rectangle isn't outside the bounds of the array */
++	rect.width = min_t(unsigned int, rect.width,
++			   OV5693_NATIVE_WIDTH - rect.left);
++	rect.height = min_t(unsigned int, rect.height,
++			    OV5693_NATIVE_HEIGHT - rect.top);
++
++	__crop = __ov5693_get_pad_crop(ov5693, cfg, sel->pad, sel->which);
++
++	if (rect.width != __crop->width || rect.height != __crop->height) {
++		/*
++		 * Reset the output image size if the crop rectangle size has
++		 * been modified.
++		 */
++		format = __ov5693_get_pad_format(ov5693, cfg, sel->pad, sel->which);
++		format->width = rect.width;
++		format->height = rect.height;
++	}
++
++	*__crop = rect;
++	sel->r = rect;
++
++	return 0;
++}
++
++static int ov5693_s_stream(struct v4l2_subdev *sd, int enable)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	int ret;
++
++	if (enable) {
++		ret = pm_runtime_get_sync(ov5693->dev);
++		if (ret < 0)
++			goto err_power_down;
++
++		ret = __v4l2_ctrl_handler_setup(&ov5693->ctrls.handler);
++		if (ret)
++			goto err_power_down;
++	}
++
++	mutex_lock(&ov5693->lock);
++	ret = ov5693_sw_standby(ov5693, !enable);
++	mutex_unlock(&ov5693->lock);
++
++	if (ret)
++		goto err_power_down;
++	ov5693->streaming = !!enable;
++
++	if (!enable)
++		pm_runtime_put(ov5693->dev);
++
++	return 0;
++err_power_down:
++	pm_runtime_put_noidle(ov5693->dev);
++	return ret;
++}
++
++static int ov5693_g_frame_interval(struct v4l2_subdev *sd,
++				   struct v4l2_subdev_frame_interval *interval)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	unsigned int framesize = OV5693_FIXED_PPL * (ov5693->mode.format.height +
++				 ov5693->ctrls.vblank->val);
++	unsigned int fps = DIV_ROUND_CLOSEST(OV5693_PIXEL_RATE, framesize);
++
++	interval->interval.numerator = 1;
++	interval->interval.denominator = fps;
++
++	return 0;
++}
++
++static int ov5693_enum_mbus_code(struct v4l2_subdev *sd,
++				 struct v4l2_subdev_pad_config *cfg,
++				 struct v4l2_subdev_mbus_code_enum *code)
++{
++	/* Only a single mbus format is supported */
++	if (code->index > 0)
++		return -EINVAL;
++
++	code->code = MEDIA_BUS_FMT_SBGGR10_1X10;
++	return 0;
++}
++
++static int ov5693_enum_frame_size(struct v4l2_subdev *sd,
++				  struct v4l2_subdev_pad_config *cfg,
++				  struct v4l2_subdev_frame_size_enum *fse)
++{
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++	struct v4l2_rect *__crop;
++
++	if (fse->index > 1 || fse->code != MEDIA_BUS_FMT_SBGGR10_1X10)
++		return -EINVAL;
++
++	__crop = __ov5693_get_pad_crop(ov5693, cfg, fse->pad, fse->which);
++	if (!__crop)
++		return -EINVAL;
++
++	fse->min_width = __crop->width / (fse->index + 1);
++	fse->min_height = __crop->height / (fse->index + 1);
++	fse->max_width = fse->min_width;
++	fse->max_height = fse->min_height;
++
++	return 0;
++}
++
++static const struct v4l2_subdev_video_ops ov5693_video_ops = {
++	.s_stream = ov5693_s_stream,
++	.g_frame_interval = ov5693_g_frame_interval,
++};
++
++static const struct v4l2_subdev_pad_ops ov5693_pad_ops = {
++	.enum_mbus_code = ov5693_enum_mbus_code,
++	.enum_frame_size = ov5693_enum_frame_size,
++	.get_fmt = ov5693_get_fmt,
++	.set_fmt = ov5693_set_fmt,
++	.get_selection = ov5693_get_selection,
++	.set_selection = ov5693_set_selection,
++};
++
++static const struct v4l2_subdev_ops ov5693_ops = {
++	.video = &ov5693_video_ops,
++	.pad = &ov5693_pad_ops,
++};
++
++/* Sensor and Driver Configuration Functions */
++
++static int ov5693_init_controls(struct ov5693_device *ov5693)
++{
++	const struct v4l2_ctrl_ops *ops = &ov5693_ctrl_ops;
++	struct v4l2_fwnode_device_properties props;
++	int vblank_max, vblank_def;
++	int exposure_max;
++	int hblank;
++	int ret;
++
++	ret = v4l2_ctrl_handler_init(&ov5693->ctrls.handler, 12);
++	if (ret)
++		return ret;
++
++	/* link freq */
++	ov5693->ctrls.link_freq = v4l2_ctrl_new_int_menu(&ov5693->ctrls.handler,
++							 NULL, V4L2_CID_LINK_FREQ,
++							 0, 0, link_freq_menu_items);
++	if (ov5693->ctrls.link_freq)
++		ov5693->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
++
++	/* pixel rate */
++	ov5693->ctrls.pixel_rate = v4l2_ctrl_new_std(&ov5693->ctrls.handler, NULL,
++						     V4L2_CID_PIXEL_RATE, 0,
++						     OV5693_PIXEL_RATE, 1,
++						     OV5693_PIXEL_RATE);
++
++	/* Exposure */
++	exposure_max = ov5693->mode.vts - OV5693_INTEGRATION_TIME_MARGIN;
++	ov5693->ctrls.exposure = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						   V4L2_CID_EXPOSURE,
++						   OV5693_EXPOSURE_MIN,
++						   exposure_max,
++						   OV5693_EXPOSURE_STEP,
++						   exposure_max);
++
++	/* Gain */
++	ov5693->ctrls.analogue_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler,
++							ops, V4L2_CID_ANALOGUE_GAIN,
++							OV5693_GAIN_MIN,
++							OV5693_GAIN_MAX,
++							OV5693_GAIN_STEP,
++							OV5693_GAIN_DEF);
++
++	ov5693->ctrls.digital_gain = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						       V4L2_CID_DIGITAL_GAIN,
++						       OV5693_DIGITAL_GAIN_MIN,
++						       OV5693_DIGITAL_GAIN_MAX,
++						       OV5693_DIGITAL_GAIN_STEP,
++						       OV5693_DIGITAL_GAIN_DEF);
++
++	/* Flip */
++	ov5693->ctrls.hflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						V4L2_CID_HFLIP, 0, 1, 1, 0);
++
++	ov5693->ctrls.vflip = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						V4L2_CID_VFLIP, 0, 1, 1, 0);
++
++	hblank = OV5693_FIXED_PPL - ov5693->mode.format.width;
++	ov5693->ctrls.hblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						 V4L2_CID_HBLANK, hblank,
++						 hblank, 1, hblank);
++
++	if (ov5693->ctrls.hblank)
++		ov5693->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
++
++	vblank_max = OV5693_TIMING_MAX_VTS - ov5693->mode.format.height;
++	vblank_def = ov5693->mode.vts - ov5693->mode.format.height;
++	ov5693->ctrls.vblank = v4l2_ctrl_new_std(&ov5693->ctrls.handler, ops,
++						 V4L2_CID_VBLANK,
++						 OV5693_TIMING_MIN_VTS,
++						 vblank_max, 1, vblank_def);
++
++	ov5693->ctrls.test_pattern = v4l2_ctrl_new_std_menu_items(
++					&ov5693->ctrls.handler, ops,
++					V4L2_CID_TEST_PATTERN,
++					ARRAY_SIZE(ov5693_test_pattern_menu) - 1,
++					0, 0, ov5693_test_pattern_menu);
++
++	if (ov5693->ctrls.handler.error) {
++		dev_err(ov5693->dev, "Error initialising v4l2 ctrls\n");
++		ret = ov5693->ctrls.handler.error;
++		goto err_free_handler;
++	}
++
++	/* set properties from fwnode (e.g. rotation, orientation) */
++	ret = v4l2_fwnode_device_parse(ov5693->dev, &props);
++	if (ret)
++		goto err_free_handler;
++
++	ret = v4l2_ctrl_new_fwnode_properties(&ov5693->ctrls.handler, ops,
++					      &props);
++	if (ret)
++		goto err_free_handler;
++
++	/* Use same lock for controls as for everything else. */
++	ov5693->ctrls.handler.lock = &ov5693->lock;
++	ov5693->sd.ctrl_handler = &ov5693->ctrls.handler;
++
++	return 0;
++
++err_free_handler:
++	v4l2_ctrl_handler_free(&ov5693->ctrls.handler);
++	return ret;
++}
++
++static int ov5693_configure_gpios(struct ov5693_device *ov5693)
++{
++	ov5693->reset = devm_gpiod_get_optional(ov5693->dev, "reset",
++						GPIOD_OUT_HIGH);
++	if (IS_ERR(ov5693->reset)) {
++		dev_err(ov5693->dev, "Error fetching reset GPIO\n");
++		return PTR_ERR(ov5693->reset);
++	}
++
++	ov5693->powerdown = devm_gpiod_get_optional(ov5693->dev, "powerdown",
++						    GPIOD_OUT_HIGH);
++	if (IS_ERR(ov5693->powerdown)) {
++		dev_err(ov5693->dev, "Error fetching powerdown GPIO\n");
++		return PTR_ERR(ov5693->powerdown);
++	}
++
++	return 0;
++}
++
++static int ov5693_get_regulators(struct ov5693_device *ov5693)
++{
++	unsigned int i;
++
++	for (i = 0; i < OV5693_NUM_SUPPLIES; i++)
++		ov5693->supplies[i].supply = ov5693_supply_names[i];
++
++	return devm_regulator_bulk_get(ov5693->dev, OV5693_NUM_SUPPLIES,
++				       ov5693->supplies);
++}
++
++static int ov5693_probe(struct i2c_client *client)
++{
++	struct fwnode_handle *fwnode = dev_fwnode(&client->dev);
++	struct fwnode_handle *endpoint;
++	struct ov5693_device *ov5693;
++	u32 clk_rate;
++	int ret = 0;
++
++	endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
++	if (!endpoint && !IS_ERR_OR_NULL(fwnode->secondary))
++		endpoint = fwnode_graph_get_next_endpoint(fwnode->secondary, NULL);
++	if (!endpoint)
++		return -EPROBE_DEFER;
++
++	ov5693 = devm_kzalloc(&client->dev, sizeof(*ov5693), GFP_KERNEL);
++	if (!ov5693)
++		return -ENOMEM;
++
++	ov5693->client = client;
++	ov5693->dev = &client->dev;
++
++	mutex_init(&ov5693->lock);
++
++	v4l2_i2c_subdev_init(&ov5693->sd, client, &ov5693_ops);
++
++	ov5693->clk = devm_clk_get(&client->dev, "xvclk");
++	if (IS_ERR(ov5693->clk)) {
++		dev_err(&client->dev, "Error getting clock\n");
++		return PTR_ERR(ov5693->clk);
++	}
++
++	clk_rate = clk_get_rate(ov5693->clk);
++	if (clk_rate != OV5693_XVCLK_FREQ) {
++		dev_err(&client->dev, "Unsupported clk freq %u, expected %u\n",
++			clk_rate, OV5693_XVCLK_FREQ);
++		return -EINVAL;
++	}
++
++	ret = ov5693_configure_gpios(ov5693);
++	if (ret)
++		return ret;
++
++	ret = ov5693_get_regulators(ov5693);
++	if (ret) {
++		dev_err(&client->dev, "Error fetching regulators\n");
++		return ret;
++	}
++
++	ov5693->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
++	ov5693->pad.flags = MEDIA_PAD_FL_SOURCE;
++	ov5693->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
++
++	ov5693->mode.crop = ov5693_default_crop;
++	ov5693->mode.format = ov5693_default_fmt;
++	ov5693->mode.vts = __ov5693_calc_vts(ov5693->mode.format.height);
++
++	ret = ov5693_init_controls(ov5693);
++	if (ret)
++		return ret;
++
++	ret = media_entity_pads_init(&ov5693->sd.entity, 1, &ov5693->pad);
++	if (ret)
++		goto err_ctrl_handler_free;
++
++	/*
++	 * We need the driver to work in the event that pm runtime is disable in
++	 * the kernel, so power up and verify the chip now. In the event that
++	 * runtime pm is disabled this will leave the chip on, so that streaming
++	 * will work.
++	 */
++
++	ret = ov5693_sensor_powerup(ov5693);
++	if (ret)
++		goto err_media_entity_cleanup;
++
++	ret = ov5693_detect(ov5693);
++	if (ret)
++		goto err_powerdown;
++
++	pm_runtime_set_active(&client->dev);
++	pm_runtime_get_noresume(&client->dev);
++	pm_runtime_enable(&client->dev);
++
++	ret = v4l2_async_register_subdev_sensor_common(&ov5693->sd);
++	if (ret) {
++		dev_err(&client->dev, "failed to register V4L2 subdev: %d",
++			ret);
++		goto err_pm_runtime;
++	}
++
++	pm_runtime_set_autosuspend_delay(&client->dev, 1000);
++	pm_runtime_use_autosuspend(&client->dev);
++	pm_runtime_put_autosuspend(&client->dev);
++
++	return ret;
++
++err_pm_runtime:
++	pm_runtime_disable(&client->dev);
++	pm_runtime_put_noidle(&client->dev);
++err_powerdown:
++	ov5693_sensor_powerdown(ov5693);
++err_media_entity_cleanup:
++	media_entity_cleanup(&ov5693->sd.entity);
++err_ctrl_handler_free:
++	v4l2_ctrl_handler_free(&ov5693->ctrls.handler);
++
++	return ret;
++}
++
++static int ov5693_remove(struct i2c_client *client)
++{
++	struct v4l2_subdev *sd = i2c_get_clientdata(client);
++	struct ov5693_device *ov5693 = to_ov5693_sensor(sd);
++
++	v4l2_async_unregister_subdev(sd);
++	media_entity_cleanup(&ov5693->sd.entity);
++	v4l2_ctrl_handler_free(&ov5693->ctrls.handler);
++	mutex_destroy(&ov5693->lock);
++
++	/*
++	 * Disable runtime PM. In case runtime PM is disabled in the kernel,
++	 * make sure to turn power off manually.
++	 */
++	pm_runtime_disable(&client->dev);
++	if (!pm_runtime_status_suspended(&client->dev))
++		ov5693_sensor_powerdown(ov5693);
++	pm_runtime_set_suspended(&client->dev);
++
++	return 0;
++}
++
++static const struct dev_pm_ops ov5693_pm_ops = {
++	SET_RUNTIME_PM_OPS(ov5693_sensor_suspend, ov5693_sensor_resume, NULL)
++};
++
++static const struct acpi_device_id ov5693_acpi_match[] = {
++	{"INT33BE"},
++	{},
++};
++MODULE_DEVICE_TABLE(acpi, ov5693_acpi_match);
++
++static struct i2c_driver ov5693_driver = {
++	.driver = {
++		.name = "ov5693",
++		.acpi_match_table = ov5693_acpi_match,
++		.pm = &ov5693_pm_ops,
++	},
++	.probe_new = ov5693_probe,
++	.remove = ov5693_remove,
++};
++module_i2c_driver(ov5693_driver);
++
++MODULE_DESCRIPTION("A low-level driver for OmniVision 5693 sensors");
++MODULE_LICENSE("GPL");
+-- 
+2.31.1
+
+From 50a9c72845f5a7f0ce6411b59a49a724b04cb92d Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= <me@fabwu.ch>
+Date: Fri, 22 Jan 2021 20:58:13 +0100
+Subject: [PATCH] cio2-bridge: Parse sensor orientation and rotation
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+The sensor orientation is read from the _PLC ACPI buffer and converted
+to a v4l2 format.
+
+See https://uefi.org/sites/default/files/resources/ACPI_6_3_final_Jan30.pdf
+page 351 for a definition of the Panel property.
+
+The sensor rotation is read from the SSDB ACPI buffer and converted into
+degrees.
+
+Signed-off-by: Fabian Wüthrich <me@fabwu.ch>
+Patchset: cameras
+---
+ drivers/media/pci/intel/ipu3/cio2-bridge.c | 45 ++++++++++++++++++++--
+ drivers/media/pci/intel/ipu3/cio2-bridge.h |  3 ++
+ 2 files changed, 44 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c
+index c2199042d3db..9a8a4a55d6a7 100644
+--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c
++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c
+@@ -29,6 +29,7 @@ static const struct cio2_sensor_config cio2_supported_sensors[] = {
+ static const struct cio2_property_names prop_names = {
+ 	.clock_frequency = "clock-frequency",
+ 	.rotation = "rotation",
++	.orientation = "orientation",
+ 	.bus_type = "bus-type",
+ 	.data_lanes = "data-lanes",
+ 	.remote_endpoint = "remote-endpoint",
+@@ -72,11 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id,
+ 	return ret;
+ }
+ 
++static u32 cio2_bridge_parse_rotation(u8 rotation)
++{
++	if (rotation == 1)
++		return 180;
++	return 0;
++}
++
++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel)
++{
++	switch (panel) {
++	case 4:
++		return V4L2_FWNODE_ORIENTATION_FRONT;
++	case 5:
++		return V4L2_FWNODE_ORIENTATION_BACK;
++	default:
++		return V4L2_FWNODE_ORIENTATION_EXTERNAL;
++	}
++}
++
+ static void cio2_bridge_create_fwnode_properties(
+ 	struct cio2_sensor *sensor,
+ 	struct cio2_bridge *bridge,
+ 	const struct cio2_sensor_config *cfg)
+ {
++	u32 rotation;
++	enum v4l2_fwnode_orientation orientation;
++
++	rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree);
++	orientation = cio2_bridge_parse_orientation(sensor->pld->panel);
++
+ 	sensor->prop_names = prop_names;
+ 
+ 	sensor->local_ref[0].node = &sensor->swnodes[SWNODE_CIO2_ENDPOINT];
+@@ -85,9 +111,12 @@ static void cio2_bridge_create_fwnode_properties(
+ 	sensor->dev_properties[0] = PROPERTY_ENTRY_U32(
+ 					sensor->prop_names.clock_frequency,
+ 					sensor->ssdb.mclkspeed);
+-	sensor->dev_properties[1] = PROPERTY_ENTRY_U8(
++	sensor->dev_properties[1] = PROPERTY_ENTRY_U32(
+ 					sensor->prop_names.rotation,
+-					sensor->ssdb.degree);
++					rotation);
++	sensor->dev_properties[2] = PROPERTY_ENTRY_U32(
++					sensor->prop_names.orientation,
++					orientation);
+ 
+ 	sensor->ep_properties[0] = PROPERTY_ENTRY_U32(
+ 					sensor->prop_names.bus_type,
+@@ -159,6 +188,7 @@ static void cio2_bridge_unregister_sensors(struct cio2_bridge *bridge)
+ 	for (i = 0; i < bridge->n_sensors; i++) {
+ 		sensor = &bridge->sensors[i];
+ 		software_node_unregister_nodes(sensor->swnodes);
++		ACPI_FREE(sensor->pld);
+ 		acpi_dev_put(sensor->adev);
+ 	}
+ }
+@@ -170,6 +200,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg,
+ 	struct fwnode_handle *fwnode;
+ 	struct cio2_sensor *sensor;
+ 	struct acpi_device *adev;
++	acpi_status status;
+ 	int ret;
+ 
+ 	for_each_acpi_dev_match(adev, cfg->hid, NULL, -1) {
+@@ -193,11 +224,15 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg,
+ 		if (ret)
+ 			goto err_put_adev;
+ 
++		status = acpi_get_physical_device_location(adev->handle, &sensor->pld);
++		if (ACPI_FAILURE(status))
++			goto err_put_adev;
++
+ 		if (sensor->ssdb.lanes > CIO2_MAX_LANES) {
+ 			dev_err(&adev->dev,
+ 				"Number of lanes in SSDB is invalid\n");
+ 			ret = -EINVAL;
+-			goto err_put_adev;
++			goto err_free_pld;
+ 		}
+ 
+ 		cio2_bridge_create_fwnode_properties(sensor, bridge, cfg);
+@@ -205,7 +240,7 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg,
+ 
+ 		ret = software_node_register_nodes(sensor->swnodes);
+ 		if (ret)
+-			goto err_put_adev;
++			goto err_free_pld;
+ 
+ 		fwnode = software_node_fwnode(&sensor->swnodes[
+ 						      SWNODE_SENSOR_HID]);
+@@ -226,6 +261,8 @@ static int cio2_bridge_connect_sensor(const struct cio2_sensor_config *cfg,
+ 
+ err_free_swnodes:
+ 	software_node_unregister_nodes(sensor->swnodes);
++err_free_pld:
++	ACPI_FREE(sensor->pld);
+ err_put_adev:
+ 	acpi_dev_put(sensor->adev);
+ err_out:
+diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h
+index dd0ffcafa489..924d99d20328 100644
+--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h
++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h
+@@ -80,6 +80,7 @@ struct cio2_sensor_ssdb {
+ struct cio2_property_names {
+ 	char clock_frequency[16];
+ 	char rotation[9];
++	char orientation[12];
+ 	char bus_type[9];
+ 	char data_lanes[11];
+ 	char remote_endpoint[16];
+@@ -106,6 +107,8 @@ struct cio2_sensor {
+ 	struct cio2_node_names node_names;
+ 
+ 	struct cio2_sensor_ssdb ssdb;
++	struct acpi_pld_info *pld;
++
+ 	struct cio2_property_names prop_names;
+ 	struct property_entry ep_properties[5];
+ 	struct property_entry dev_properties[3];
+-- 
+2.31.1
+
+From b8227ed525b8a609ae311c7c7d310498c29e94fd Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Fabian=20W=C3=BCthrich?= <me@fabwu.ch>
+Date: Sun, 24 Jan 2021 11:07:42 +0100
+Subject: [PATCH] cio2-bridge: Use macros and add warnings
+MIME-Version: 1.0
+Content-Type: text/plain; charset=UTF-8
+Content-Transfer-Encoding: 8bit
+
+Use macros for the _PLD panel as defined in the ACPI spec 6.3 and emit
+a warning if we see an unknown value.
+
+Signed-off-by: Fabian Wüthrich <me@fabwu.ch>
+Patchset: cameras
+---
+ drivers/media/pci/intel/ipu3/cio2-bridge.c | 33 ++++++++++++++++------
+ drivers/media/pci/intel/ipu3/cio2-bridge.h | 13 +++++++++
+ 2 files changed, 37 insertions(+), 9 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.c b/drivers/media/pci/intel/ipu3/cio2-bridge.c
+index 9a8a4a55d6a7..503809907b92 100644
+--- a/drivers/media/pci/intel/ipu3/cio2-bridge.c
++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.c
+@@ -73,21 +73,36 @@ static int cio2_bridge_read_acpi_buffer(struct acpi_device *adev, char *id,
+ 	return ret;
+ }
+ 
+-static u32 cio2_bridge_parse_rotation(u8 rotation)
++static u32 cio2_bridge_parse_rotation(struct cio2_sensor *sensor)
+ {
+-	if (rotation == 1)
++	switch (sensor->ssdb.degree) {
++	case CIO2_SENSOR_ROTATION_NORMAL:
++		return 0;
++	case CIO2_SENSOR_ROTATION_INVERTED:
+ 		return 180;
+-	return 0;
++	default:
++		dev_warn(&sensor->adev->dev,
++			 "Unknown rotation %d. Assume 0 degree rotation\n",
++			 sensor->ssdb.degree);
++		return 0;
++	}
+ }
+ 
+-static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(u8 panel)
++static enum v4l2_fwnode_orientation cio2_bridge_parse_orientation(struct cio2_sensor *sensor)
+ {
+-	switch (panel) {
+-	case 4:
++	switch (sensor->pld->panel) {
++	case CIO2_PLD_PANEL_FRONT:
+ 		return V4L2_FWNODE_ORIENTATION_FRONT;
+-	case 5:
++	case CIO2_PLD_PANEL_BACK:
+ 		return V4L2_FWNODE_ORIENTATION_BACK;
++	case CIO2_PLD_PANEL_TOP:
++	case CIO2_PLD_PANEL_LEFT:
++	case CIO2_PLD_PANEL_RIGHT:
++	case CIO2_PLD_PANEL_UNKNOWN:
++		return V4L2_FWNODE_ORIENTATION_EXTERNAL;
+ 	default:
++		dev_warn(&sensor->adev->dev, "Unknown _PLD panel value %d\n",
++			 sensor->pld->panel);
+ 		return V4L2_FWNODE_ORIENTATION_EXTERNAL;
+ 	}
+ }
+@@ -100,8 +115,8 @@ static void cio2_bridge_create_fwnode_properties(
+ 	u32 rotation;
+ 	enum v4l2_fwnode_orientation orientation;
+ 
+-	rotation = cio2_bridge_parse_rotation(sensor->ssdb.degree);
+-	orientation = cio2_bridge_parse_orientation(sensor->pld->panel);
++	rotation = cio2_bridge_parse_rotation(sensor);
++	orientation = cio2_bridge_parse_orientation(sensor);
+ 
+ 	sensor->prop_names = prop_names;
+ 
+diff --git a/drivers/media/pci/intel/ipu3/cio2-bridge.h b/drivers/media/pci/intel/ipu3/cio2-bridge.h
+index 924d99d20328..e1e388cc9f45 100644
+--- a/drivers/media/pci/intel/ipu3/cio2-bridge.h
++++ b/drivers/media/pci/intel/ipu3/cio2-bridge.h
+@@ -12,6 +12,19 @@
+ #define CIO2_MAX_LANES				4
+ #define MAX_NUM_LINK_FREQS			3
+ 
++/* Values are estimated guesses as we don't have a spec */
++#define CIO2_SENSOR_ROTATION_NORMAL		0
++#define CIO2_SENSOR_ROTATION_INVERTED		1
++
++/* Panel position defined in _PLD section of ACPI Specification 6.3 */
++#define CIO2_PLD_PANEL_TOP			0
++#define CIO2_PLD_PANEL_BOTTOM			1
++#define CIO2_PLD_PANEL_LEFT			2
++#define CIO2_PLD_PANEL_RIGHT			3
++#define CIO2_PLD_PANEL_FRONT			4
++#define CIO2_PLD_PANEL_BACK			5
++#define CIO2_PLD_PANEL_UNKNOWN			6
++
+ #define CIO2_SENSOR_CONFIG(_HID, _NR, ...)	\
+ 	(const struct cio2_sensor_config) {	\
+ 		.hid = _HID,			\
+-- 
+2.31.1
+