From 42d9466dc7b6c433fbc80f88f0f68451fab017d9 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Wed, 10 Aug 2022 17:07:50 +0200 Subject: [PATCH] Add patches for v5.19 No functional changes from latest v5.18 patches. Links: - kernel: https://github.com/linux-surface/kernel/commit/8e33cd037d4a68a5feabf796ea0b61e4ec018eeb - tree-devel: https://github.com/linux-surface/kernel/commits/v5.19-surface-devel - tree-patches: https://github.com/linux-surface/kernel/commits/v5.19-surface --- configs/surface-5.19.config | 64 + patches/5.19/0001-surface3-oemb.patch | 101 + patches/5.19/0002-mwifiex.patch | 704 +++ patches/5.19/0003-ath10k.patch | 121 + patches/5.19/0004-ipts.patch | 1603 ++++++ patches/5.19/0005-surface-sam.patch | 4735 ++++++++++++++++++ patches/5.19/0006-surface-sam-over-hid.patch | 335 ++ patches/5.19/0007-surface-button.patch | 149 + patches/5.19/0008-surface-typecover.patch | 533 ++ patches/5.19/0009-surface-battery.patch | 48 + patches/5.19/0010-surface-gpe.patch | 43 + patches/5.19/0011-cameras.patch | 923 ++++ patches/5.19/0012-amd-gpio.patch | 109 + 13 files changed, 9468 insertions(+) create mode 100644 configs/surface-5.19.config create mode 100644 patches/5.19/0001-surface3-oemb.patch create mode 100644 patches/5.19/0002-mwifiex.patch create mode 100644 patches/5.19/0003-ath10k.patch create mode 100644 patches/5.19/0004-ipts.patch create mode 100644 patches/5.19/0005-surface-sam.patch create mode 100644 patches/5.19/0006-surface-sam-over-hid.patch create mode 100644 patches/5.19/0007-surface-button.patch create mode 100644 patches/5.19/0008-surface-typecover.patch create mode 100644 patches/5.19/0009-surface-battery.patch create mode 100644 patches/5.19/0010-surface-gpe.patch create mode 100644 patches/5.19/0011-cameras.patch create mode 100644 patches/5.19/0012-amd-gpio.patch diff --git a/configs/surface-5.19.config b/configs/surface-5.19.config new file mode 100644 index 000000000..0bb50d4a6 --- /dev/null +++ b/configs/surface-5.19.config @@ -0,0 +1,64 @@ +# +# 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_HUB=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH=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_DW9719=m +CONFIG_VIDEO_IPU3_IMGU=m +CONFIG_VIDEO_IPU3_CIO2=m +CONFIG_CIO2_BRIDGE=y +CONFIG_INTEL_SKL_INT3472=m +CONFIG_REGULATOR_TPS68470=m +CONFIG_COMMON_CLK_TPS68470=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_POWER_OPREGION=m +CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m +CONFIG_SURFACE_BOOK1_DGPU_SWITCH=m diff --git a/patches/5.19/0001-surface3-oemb.patch b/patches/5.19/0001-surface3-oemb.patch new file mode 100644 index 000000000..cd404d88b --- /dev/null +++ b/patches/5.19/0001-surface3-oemb.patch @@ -0,0 +1,101 @@ +From 078652a47159f114689add6f373abe0a02300e7c Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 +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 ca4602bcc7de..490b9731068a 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 507aba8de3cc..1f8570e04083 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3718,6 +3718,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 6beb00858c33..d82d77387a0a 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.37.1 + diff --git a/patches/5.19/0002-mwifiex.patch b/patches/5.19/0002-mwifiex.patch new file mode 100644 index 000000000..682e40c05 --- /dev/null +++ b/patches/5.19/0002-mwifiex.patch @@ -0,0 +1,704 @@ +From a087da4bad9b7086225e6fff700eae152bdb2c46 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 10 +++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 83 +++++++++++++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.h | 6 ++ + 3 files changed, 99 insertions(+) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index d5fb29400bad..033648526f16 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -2993,6 +2993,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 0234cf3c2974..563dd0d5ac79 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -15,10 +15,21 @@ + * this warranty disclaimer. + */ + ++#include + #include + + #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,6 +98,14 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + }, + .driver_data = (void *)QUIRK_FW_RST_D3COLD, + }, ++ { ++ .ident = "Surface 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface 3"), ++ }, ++ .driver_data = (void *)QUIRK_FW_RST_WSID_S3, ++ }, + {} + }; + +@@ -103,6 +122,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) +@@ -159,3 +181,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 8ec4176d698f..25370c5a4f59 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -19,5 +19,11 @@ + + #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.37.1 + +From 6622e6f29f2b917800149cd0e985bb4cc6b770ae Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 +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 563dd0d5ac79..32e2f000e57b 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -106,6 +106,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, ++ }, + {} + }; + +-- +2.37.1 + +From b2dc31fd5137b8dab8ded67498d6f2b80f1f9414 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +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 +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 033648526f16..ca6bcbe4794c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -380,6 +380,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", +@@ -421,6 +422,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 32e2f000e57b..356401bab59c 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -38,7 +38,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", +@@ -47,7 +48,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)", +@@ -56,7 +58,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", +@@ -64,7 +67,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", +@@ -72,7 +76,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", +@@ -80,7 +85,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", +@@ -88,7 +94,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", +@@ -96,7 +103,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", +@@ -134,6 +142,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 25370c5a4f59..a1de111ad1db 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -23,6 +23,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.37.1 + +From 35c1eccdf85145abda992d57ae3c234baeaa9044 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +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 ca6bcbe4794c..24bcd22a2618 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1781,9 +1781,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 356401bab59c..6437f067d07a 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -39,7 +39,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", +@@ -49,7 +50,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)", +@@ -59,7 +61,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", +@@ -68,7 +71,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", +@@ -77,7 +81,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", +@@ -86,7 +91,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", +@@ -95,7 +101,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", +@@ -104,7 +111,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", +@@ -145,6 +153,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 a1de111ad1db..0e429779bb04 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -24,6 +24,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.37.1 + +From 4968cdbd71a2ec9c99134afd1dbba3d9ee74509e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +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 e25fcd49db70..a3cca97825d4 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -63,6 +63,7 @@ static struct usb_driver btusb_driver; + #define BTUSB_INTEL_BROKEN_SHUTDOWN_LED BIT(24) + #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) + #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) ++#define BTUSB_LOWER_LESCAN_INTERVAL BIT(27) + + static const struct usb_device_id btusb_table[] = { + /* Generic Bluetooth USB device */ +@@ -382,6 +383,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_COMBINED }, +@@ -3788,6 +3790,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.37.1 + +From 0d94f1f3c699a1fbdb41061a36c66fb9a1de2b57 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +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 24bcd22a2618..b4ad0113a035 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -238,6 +238,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.37.1 + diff --git a/patches/5.19/0003-ath10k.patch b/patches/5.19/0003-ath10k.patch new file mode 100644 index 000000000..e868d0107 --- /dev/null +++ b/patches/5.19/0003-ath10k.patch @@ -0,0 +1,121 @@ +From 112363a0024d309f1a42e5c701916b86a71d3efb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +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 +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 688177453b07..e400a0318838 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -36,6 +36,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); + +@@ -48,6 +51,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"); +@@ -56,6 +62,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, +@@ -876,6 +885,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) +@@ -890,6 +935,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.37.1 + diff --git a/patches/5.19/0004-ipts.patch b/patches/5.19/0004-ipts.patch new file mode 100644 index 000000000..6f690dad1 --- /dev/null +++ b/patches/5.19/0004-ipts.patch @@ -0,0 +1,1603 @@ +From b2398457116838b9da2c1f280bafa6defb8ece9a Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +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 15e8e2b322b1..91587b808323 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_ICP_N 0x38E0 /* Ice Lake Point N */ + + #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 5435604327a7..1165ee4f5928 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,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_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.37.1 + +From 095b1ba9131f883f3583800fe39d940f17ae1bac Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +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 +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 41d2bb0ae23a..effb258d4848 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -500,4 +500,5 @@ source "drivers/misc/cardreader/Kconfig" + source "drivers/misc/habanalabs/Kconfig" + source "drivers/misc/uacce/Kconfig" + source "drivers/misc/pvpanic/Kconfig" ++source "drivers/misc/ipts/Kconfig" + endmenu +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index 70e800e9127f..a8d1e9447025 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -60,3 +60,4 @@ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o + obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o + obj-$(CONFIG_OPEN_DICE) += open-dice.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 ++#include ++#include ++#include ++ ++#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 ++ ++#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 ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++ ++#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 "); ++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 ++ ++/* ++ * 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 ++#include ++#include ++ ++#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 ++ ++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 ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++#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.37.1 + +From aa4bc1e5db020eca7a889b3328d593573cb06928 Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 5c0dce78586a..825b524e81f3 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -37,6 +37,8 @@ + #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) + #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) ++#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9d3e)) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + + #define IOAPIC_RANGE_START (0xfee00000) +@@ -307,12 +309,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_gfx = 1; ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + + #define IDENTMAP_GFX 2 + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + int intel_iommu_gfx_mapped; + EXPORT_SYMBOL_GPL(intel_iommu_gfx_mapped); +@@ -2659,6 +2663,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -3095,6 +3102,9 @@ static int __init init_dmars(void) + if (!dmar_map_gfx) + iommu_identity_mapping |= IDENTMAP_GFX; + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + ret = si_domain_init(hw_pass_through); +@@ -4923,6 +4933,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + dmar_map_gfx = 0; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4958,6 +4979,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.37.1 + diff --git a/patches/5.19/0005-surface-sam.patch b/patches/5.19/0005-surface-sam.patch new file mode 100644 index 000000000..dab4f4530 --- /dev/null +++ b/patches/5.19/0005-surface-sam.patch @@ -0,0 +1,4735 @@ +From 0a7622f7cd89a2a850caab7794f6a640dee4a5e8 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:36 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow is_ssam_device() to be + used when CONFIG_SURFACE_AGGREGATOR_BUS is disabled + +In SSAM subsystem drivers that handle both ACPI and SSAM-native client +devices, we may want to check whether we have a SSAM (native) client +device. Further, we may want to do this even when instantiation thereof +cannot happen due to CONFIG_SURFACE_AGGREGATOR_BUS=n. Currently, doing +so causes an error due to an undefined reference error due to +ssam_device_type being placed in the bus source unit. + +Therefore, if CONFIG_SURFACE_AGGREGATOR_BUS is not defined, simply let +is_ssam_device() return false to prevent this error. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-2-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + include/linux/surface_aggregator/device.h | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index cc257097eb05..62b38b4487eb 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -177,6 +177,8 @@ struct ssam_device_driver { + void (*remove)(struct ssam_device *sdev); + }; + ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++ + extern struct bus_type ssam_bus_type; + extern const struct device_type ssam_device_type; + +@@ -193,6 +195,15 @@ static inline bool is_ssam_device(struct device *d) + return d->type == &ssam_device_type; + } + ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static inline bool is_ssam_device(struct device *d) ++{ ++ return false; ++} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ + /** + * to_ssam_device() - Casts the given device to a SSAM client device. + * @d: The device to cast. +-- +2.37.1 + +From 59ccf6b69fdd0e03572a3e72bf5d16bb93279ab8 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:37 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow devices to be marked as + hot-removed + +Some SSAM devices, notably the keyboard cover (keyboard and touchpad) on +the Surface Pro 8, can be hot-removed. When this occurs, communication +with the device may fail and time out. This timeout can unnecessarily +block and slow down device removal and even cause issues when the +devices are detached and re-attached quickly. Thus, communication should +generally be avoided once hot-removal is detected. + +While we already remove a device as soon as we detect its (hot-)removal, +the corresponding device driver may still attempt to communicate with +the device during teardown. This is especially critical as communication +failure may also extend to disabling of events, which is typically done +at that stage. + +Add a flag to allow marking devices as hot-removed. This can then be +used during client driver teardown to check if any communication +attempts should be avoided. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-3-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + include/linux/surface_aggregator/device.h | 48 +++++++++++++++++++++-- + 1 file changed, 45 insertions(+), 3 deletions(-) + +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 62b38b4487eb..6df7c8d4e50e 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -148,17 +148,30 @@ struct ssam_device_uid { + #define SSAM_SDEV(cat, tid, iid, fun) \ + SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + ++/* ++ * enum ssam_device_flags - Flags for SSAM client devices. ++ * @SSAM_DEVICE_HOT_REMOVED_BIT: ++ * The device has been hot-removed. Further communication with it may time ++ * out and should be avoided. ++ */ ++enum ssam_device_flags { ++ SSAM_DEVICE_HOT_REMOVED_BIT = 0, ++}; ++ + /** + * struct ssam_device - SSAM client device. +- * @dev: Driver model representation of the device. +- * @ctrl: SSAM controller managing this device. +- * @uid: UID identifying the device. ++ * @dev: Driver model representation of the device. ++ * @ctrl: SSAM controller managing this device. ++ * @uid: UID identifying the device. ++ * @flags: Device state flags, see &enum ssam_device_flags. + */ + struct ssam_device { + struct device dev; + struct ssam_controller *ctrl; + + struct ssam_device_uid uid; ++ ++ unsigned long flags; + }; + + /** +@@ -251,6 +264,35 @@ struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, + int ssam_device_add(struct ssam_device *sdev); + void ssam_device_remove(struct ssam_device *sdev); + ++/** ++ * ssam_device_mark_hot_removed() - Mark the given device as hot-removed. ++ * @sdev: The device to mark as hot-removed. ++ * ++ * Mark the device as having been hot-removed. This signals drivers using the ++ * device that communication with the device should be avoided and may lead to ++ * timeouts. ++ */ ++static inline void ssam_device_mark_hot_removed(struct ssam_device *sdev) ++{ ++ dev_dbg(&sdev->dev, "marking device as hot-removed\n"); ++ set_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); ++} ++ ++/** ++ * ssam_device_is_hot_removed() - Check if the given device has been ++ * hot-removed. ++ * @sdev: The device to check. ++ * ++ * Checks if the given device has been marked as hot-removed. See ++ * ssam_device_mark_hot_removed() for more details. ++ * ++ * Return: Returns ``true`` if the device has been marked as hot-removed. ++ */ ++static inline bool ssam_device_is_hot_removed(struct ssam_device *sdev) ++{ ++ return test_bit(SSAM_DEVICE_HOT_REMOVED_BIT, &sdev->flags); ++} ++ + /** + * ssam_device_get() - Increment reference count of SSAM client device. + * @sdev: The device to increment the reference count of. +-- +2.37.1 + +From f245465a0ede2e4b52cf058caaf043e14065370b Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:38 +0200 +Subject: [PATCH] platform/surface: aggregator: Allow notifiers to avoid + communication on unregistering + +When SSAM client devices have been (physically) hot-removed, +communication attempts with those devices may fail and time out. This +can even extend to event notifiers, due to which timeouts may occur +during device removal, slowing down that process. + +Add a parameter to the notifier unregister function that allows skipping +communication with the EC to prevent this. Furthermore, add wrappers for +registering and unregistering notifiers belonging to SSAM client devices +that automatically check if the device has been marked as hot-removed +and communication should be avoided. + +Note that non-SSAM client devices can generally not be hot-removed, so +also add a convenience wrapper for those, defaulting to allow +communication. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-4-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../driver-api/surface_aggregator/client.rst | 6 +- + .../platform/surface/aggregator/controller.c | 53 ++++++++++----- + include/linux/surface_aggregator/controller.h | 24 ++++++- + include/linux/surface_aggregator/device.h | 66 +++++++++++++++++++ + 4 files changed, 128 insertions(+), 21 deletions(-) + +diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst +index e519d374c378..27f95abdbe99 100644 +--- a/Documentation/driver-api/surface_aggregator/client.rst ++++ b/Documentation/driver-api/surface_aggregator/client.rst +@@ -17,6 +17,8 @@ + .. |SSAM_DEVICE| replace:: :c:func:`SSAM_DEVICE` + .. |ssam_notifier_register| replace:: :c:func:`ssam_notifier_register` + .. |ssam_notifier_unregister| replace:: :c:func:`ssam_notifier_unregister` ++.. |ssam_device_notifier_register| replace:: :c:func:`ssam_device_notifier_register` ++.. |ssam_device_notifier_unregister| replace:: :c:func:`ssam_device_notifier_unregister` + .. |ssam_request_sync| replace:: :c:func:`ssam_request_sync` + .. |ssam_event_mask| replace:: :c:type:`enum ssam_event_mask ` + +@@ -312,7 +314,9 @@ Handling Events + To receive events from the SAM EC, an event notifier must be registered for + the desired event via |ssam_notifier_register|. The notifier must be + unregistered via |ssam_notifier_unregister| once it is not required any +-more. ++more. For |ssam_device| type clients, the |ssam_device_notifier_register| and ++|ssam_device_notifier_unregister| wrappers should be preferred as they properly ++handle hot-removal of client devices. + + Event notifiers are registered by providing (at minimum) a callback to call + in case an event has been received, the registry specifying how the event +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index b8c377b3f932..6de834b52b63 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2199,16 +2199,26 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, + } + + /** +- * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is +- * no longer in use and free the corresponding entry. ++ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if ++ * it is no longer in use and free the corresponding entry. + * @ctrl: The controller to disable the event on. + * @entry: The reference count entry for the event to be disabled. + * @flags: The flags used for enabling the event on the EC. ++ * @ec: Flag specifying if the event should actually be disabled on the EC. + * +- * If the reference count equals zero, i.e. the event is no longer requested by +- * any client, the event will be disabled and the corresponding reference count +- * entry freed. The reference count entry must not be used any more after a +- * call to this function. ++ * If ``ec`` equals ``true`` and the reference count equals zero (i.e. the ++ * event is no longer requested by any client), the specified event will be ++ * disabled on the EC via the corresponding request. ++ * ++ * If ``ec`` equals ``false``, no request will be sent to the EC and the event ++ * can be considered in a detached state (i.e. no longer used but still ++ * enabled). Disabling an event via this method may be required for ++ * hot-removable devices, where event disable requests may time out after the ++ * device has been physically removed. ++ * ++ * In both cases, if the reference count equals zero, the corresponding ++ * reference count entry will be freed. The reference count entry must not be ++ * used any more after a call to this function. + * + * Also checks if the flags used for disabling the event match the flags used + * for enabling the event and warns if they do not (regardless of reference +@@ -2223,7 +2233,7 @@ static int ssam_nf_refcount_enable(struct ssam_controller *ctrl, + * returns the status of the event-enable EC command. + */ + static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, +- struct ssam_nf_refcount_entry *entry, u8 flags) ++ struct ssam_nf_refcount_entry *entry, u8 flags, bool ec) + { + const struct ssam_event_registry reg = entry->key.reg; + const struct ssam_event_id id = entry->key.id; +@@ -2232,8 +2242,9 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + + lockdep_assert_held(&nf->lock); + +- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", +- reg.target_category, id.target_category, id.instance, entry->refcount); ++ ssam_dbg(ctrl, "%s event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n", ++ ec ? "disabling" : "detaching", reg.target_category, id.target_category, ++ id.instance, entry->refcount); + + if (entry->flags != flags) { + ssam_warn(ctrl, +@@ -2242,7 +2253,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl, + id.instance); + } + +- if (entry->refcount == 0) { ++ if (ec && entry->refcount == 0) { + status = ssam_ssh_event_disable(ctrl, reg, id, flags); + kfree(entry); + } +@@ -2322,20 +2333,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif + EXPORT_SYMBOL_GPL(ssam_notifier_register); + + /** +- * ssam_notifier_unregister() - Unregister an event notifier. +- * @ctrl: The controller the notifier has been registered on. +- * @n: The event notifier to unregister. ++ * __ssam_notifier_unregister() - Unregister an event notifier. ++ * @ctrl: The controller the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * @disable: Whether to disable the corresponding event on the EC. + * + * Unregister an event notifier. Decrement the usage counter of the associated + * SAM event if the notifier is not marked as an observer. If the usage counter +- * reaches zero, the event will be disabled. ++ * reaches zero and ``disable`` equals ``true``, the event will be disabled. ++ * ++ * Useful for hot-removable devices, where communication may fail once the ++ * device has been physically removed. In that case, specifying ``disable`` as ++ * ``false`` avoids communication with the EC. + * + * Return: Returns zero on success, %-ENOENT if the given notifier block has + * not been registered on the controller. If the given notifier block was the + * last one associated with its specific event, returns the status of the + * event-disable EC-command. + */ +-int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n) ++int __ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n, ++ bool disable) + { + u16 rqid = ssh_tc_to_rqid(n->event.id.target_category); + struct ssam_nf_refcount_entry *entry; +@@ -2373,7 +2390,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + goto remove; + } + +- status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags); ++ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags, disable); + } + + remove: +@@ -2383,7 +2400,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not + + return status; + } +-EXPORT_SYMBOL_GPL(ssam_notifier_unregister); ++EXPORT_SYMBOL_GPL(__ssam_notifier_unregister); + + /** + * ssam_controller_event_enable() - Enable the specified event. +@@ -2477,7 +2494,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl, + return -ENOENT; + } + +- status = ssam_nf_refcount_disable_free(ctrl, entry, flags); ++ status = ssam_nf_refcount_disable_free(ctrl, entry, flags, true); + + mutex_unlock(&nf->lock); + return status; +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index 74bfdffaf7b0..50a2b4926c06 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -835,8 +835,28 @@ struct ssam_event_notifier { + int ssam_notifier_register(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + +-int ssam_notifier_unregister(struct ssam_controller *ctrl, +- struct ssam_event_notifier *n); ++int __ssam_notifier_unregister(struct ssam_controller *ctrl, ++ struct ssam_event_notifier *n, bool disable); ++ ++/** ++ * ssam_notifier_unregister() - Unregister an event notifier. ++ * @ctrl: The controller the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * ++ * Unregister an event notifier. Decrement the usage counter of the associated ++ * SAM event if the notifier is not marked as an observer. If the usage counter ++ * reaches zero, the event will be disabled. ++ * ++ * Return: Returns zero on success, %-ENOENT if the given notifier block has ++ * not been registered on the controller. If the given notifier block was the ++ * last one associated with its specific event, returns the status of the ++ * event-disable EC-command. ++ */ ++static inline int ssam_notifier_unregister(struct ssam_controller *ctrl, ++ struct ssam_event_notifier *n) ++{ ++ return __ssam_notifier_unregister(ctrl, n, true); ++} + + int ssam_controller_event_enable(struct ssam_controller *ctrl, + struct ssam_event_registry reg, +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 6df7c8d4e50e..c418f7f2732d 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -483,4 +483,70 @@ static inline void ssam_remove_clients(struct device *dev) {} + sdev->uid.instance, ret); \ + } + ++ ++/* -- Helpers for client-device notifiers. ---------------------------------- */ ++ ++/** ++ * ssam_device_notifier_register() - Register an event notifier for the ++ * specified client device. ++ * @sdev: The device the notifier should be registered on. ++ * @n: The event notifier to register. ++ * ++ * Register an event notifier. Increment the usage counter of the associated ++ * SAM event if the notifier is not marked as an observer. If the event is not ++ * marked as an observer and is currently not enabled, it will be enabled ++ * during this call. If the notifier is marked as an observer, no attempt will ++ * be made at enabling any event and no reference count will be modified. ++ * ++ * Notifiers marked as observers do not need to be associated with one specific ++ * event, i.e. as long as no event matching is performed, only the event target ++ * category needs to be set. ++ * ++ * Return: Returns zero on success, %-ENOSPC if there have already been ++ * %INT_MAX notifiers for the event ID/type associated with the notifier block ++ * registered, %-ENOMEM if the corresponding event entry could not be ++ * allocated, %-ENODEV if the device is marked as hot-removed. If this is the ++ * first time that a notifier block is registered for the specific associated ++ * event, returns the status of the event-enable EC-command. ++ */ ++static inline int ssam_device_notifier_register(struct ssam_device *sdev, ++ struct ssam_event_notifier *n) ++{ ++ /* ++ * Note that this check does not provide any guarantees whatsoever as ++ * hot-removal could happen at any point and we can't protect against ++ * it. Nevertheless, if we can detect hot-removal, bail early to avoid ++ * communication timeouts. ++ */ ++ if (ssam_device_is_hot_removed(sdev)) ++ return -ENODEV; ++ ++ return ssam_notifier_register(sdev->ctrl, n); ++} ++ ++/** ++ * ssam_device_notifier_unregister() - Unregister an event notifier for the ++ * specified client device. ++ * @sdev: The device the notifier has been registered on. ++ * @n: The event notifier to unregister. ++ * ++ * Unregister an event notifier. Decrement the usage counter of the associated ++ * SAM event if the notifier is not marked as an observer. If the usage counter ++ * reaches zero, the event will be disabled. ++ * ++ * In case the device has been marked as hot-removed, the event will not be ++ * disabled on the EC, as in those cases any attempt at doing so may time out. ++ * ++ * Return: Returns zero on success, %-ENOENT if the given notifier block has ++ * not been registered on the controller. If the given notifier block was the ++ * last one associated with its specific event, returns the status of the ++ * event-disable EC-command. ++ */ ++static inline int ssam_device_notifier_unregister(struct ssam_device *sdev, ++ struct ssam_event_notifier *n) ++{ ++ return __ssam_notifier_unregister(sdev->ctrl, n, ++ !ssam_device_is_hot_removed(sdev)); ++} ++ + #endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ +-- +2.37.1 + +From 75e02994d3a8b2cd245918f9e5087b17d287f17e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:39 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Use client device + wrappers for notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-5-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index ce2bd88feeaa..9f630e890ff7 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -468,7 +468,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + + ssam_device_set_drvdata(sdev, hub); + +- status = ssam_notifier_register(sdev->ctrl, &hub->notif); ++ status = ssam_device_notifier_register(sdev, &hub->notif); + if (status) + return status; + +@@ -480,7 +480,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + return 0; + + err: +- ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); + return status; +@@ -492,7 +492,7 @@ 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); ++ ssam_device_notifier_unregister(sdev, &hub->notif); + cancel_delayed_work_sync(&hub->update_work); + ssam_remove_clients(&sdev->dev); + } +-- +2.37.1 + +From 47c31518e1c63bfa6d1c8dfec5db080f1ceb8749 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:40 +0200 +Subject: [PATCH] power/supply: surface_charger: Use client device wrappers for + notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Acked-by: Sebastian Reichel +Link: https://lore.kernel.org/r/20220527023447.2460025-6-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/power/supply/surface_charger.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c +index a060c36c7766..59182d55742d 100644 +--- a/drivers/power/supply/surface_charger.c ++++ b/drivers/power/supply/surface_charger.c +@@ -216,7 +216,7 @@ static int spwr_ac_register(struct spwr_ac_device *ac) + if (IS_ERR(ac->psy)) + return PTR_ERR(ac->psy); + +- return ssam_notifier_register(ac->sdev->ctrl, &ac->notif); ++ return ssam_device_notifier_register(ac->sdev, &ac->notif); + } + + +@@ -251,7 +251,7 @@ 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); ++ ssam_device_notifier_unregister(sdev, &ac->notif); + } + + static const struct spwr_psy_properties spwr_psy_props_adp1 = { +-- +2.37.1 + +From c588c883700fd3dd5cb03de059f15746651327fa Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:41 +0200 +Subject: [PATCH] power/supply: surface_battery: Use client device wrappers for + notifier registration + +Use newly introduced client device wrapper functions for notifier +registration and unregistration. + +Signed-off-by: Maximilian Luz +Acked-by: Sebastian Reichel +Link: https://lore.kernel.org/r/20220527023447.2460025-7-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +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 5ec2e6bb2465..540707882bb0 100644 +--- a/drivers/power/supply/surface_battery.c ++++ b/drivers/power/supply/surface_battery.c +@@ -802,7 +802,7 @@ static int spwr_battery_register(struct spwr_battery_device *bat) + if (IS_ERR(bat->psy)) + return PTR_ERR(bat->psy); + +- return ssam_notifier_register(bat->sdev->ctrl, &bat->notif); ++ return ssam_device_notifier_register(bat->sdev, &bat->notif); + } + + +@@ -837,7 +837,7 @@ 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); ++ ssam_device_notifier_unregister(sdev, &bat->notif); + cancel_delayed_work_sync(&bat->update_work); + } + +-- +2.37.1 + +From 678945c668057fb32298231e4c6ce14a1cbacb6b Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:42 +0200 +Subject: [PATCH] HID: surface-hid: Add support for hot-removal + +Add support for hot-removal of SSAM HID client devices. + +Once a device has been hot-removed, further communication with it should +be avoided as it may fail and time out. While the device will be removed +as soon as we detect hot-removal, communication may still occur during +teardown, especially when unregistering notifiers. + +While hot-removal is a surprise event that can happen at any time, try +to avoid communication as much as possible once it has been detected to +prevent timeouts that can slow down device removal and cause issues, +e.g. when quickly re-attaching the device. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-8-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/hid/surface-hid/surface_hid_core.c | 38 +++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c +index e46330b2e561..87637f813de2 100644 +--- a/drivers/hid/surface-hid/surface_hid_core.c ++++ b/drivers/hid/surface-hid/surface_hid_core.c +@@ -19,12 +19,30 @@ + #include "surface_hid_core.h" + + ++/* -- Utility functions. ---------------------------------------------------- */ ++ ++static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) ++{ ++ /* ++ * Non-ssam client devices, i.e. platform client devices, cannot be ++ * hot-removed. ++ */ ++ if (!is_ssam_device(shid->dev)) ++ return false; ++ ++ return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); ++} ++ ++ + /* -- Device descriptor access. --------------------------------------------- */ + + static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) + { + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, + (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); + if (status) +@@ -61,6 +79,9 @@ static int surface_hid_load_device_attributes(struct surface_hid_device *shid) + { + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, + (u8 *)&shid->attrs, sizeof(shid->attrs)); + if (status) +@@ -88,9 +109,18 @@ static int surface_hid_start(struct hid_device *hid) + static void surface_hid_stop(struct hid_device *hid) + { + struct surface_hid_device *shid = hid->driver_data; ++ bool hot_removed; ++ ++ /* ++ * Communication may fail for devices that have been hot-removed. This ++ * also includes unregistration of HID events, so we need to check this ++ * here. Only if the device has not been marked as hot-removed, we can ++ * safely disable events. ++ */ ++ hot_removed = surface_hid_is_hot_removed(shid); + + /* Note: This call will log errors for us, so ignore them here. */ +- ssam_notifier_unregister(shid->ctrl, &shid->notif); ++ __ssam_notifier_unregister(shid->ctrl, &shid->notif, !hot_removed); + } + + static int surface_hid_open(struct hid_device *hid) +@@ -109,6 +139,9 @@ static int surface_hid_parse(struct hid_device *hid) + u8 *buf; + int status; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + buf = kzalloc(len, GFP_KERNEL); + if (!buf) + return -ENOMEM; +@@ -126,6 +159,9 @@ static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportn + { + struct surface_hid_device *shid = hid->driver_data; + ++ if (surface_hid_is_hot_removed(shid)) ++ return -ENODEV; ++ + if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) + return shid->ops.output_report(shid, reportnum, buf, len); + +-- +2.37.1 + +From 9797970d6472dec59e7cdeb112ef406e6a14a201 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:43 +0200 +Subject: [PATCH] platform/surface: aggregator: Add comment for KIP subsystem + category + +The KIP subsystem (full name unknown, abbreviation has been obtained +through reverse engineering) handles detachable peripherals such as the +keyboard cover on the Surface Pro X and Surface Pro 8. + +It is currently not entirely clear what this subsystem entails, but at +the very least it provides event notifications for when the keyboard +cover on the Surface Pro X and Surface Pro 8 have been detached or +re-attached, as well as the state that the keyboard cover is currently +in (e.g. folded-back, folded laptop-like, closed, etc.). + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-9-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + include/linux/surface_aggregator/serial_hub.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h +index c3de43edcffa..26b95ec12733 100644 +--- a/include/linux/surface_aggregator/serial_hub.h ++++ b/include/linux/surface_aggregator/serial_hub.h +@@ -306,7 +306,7 @@ enum ssam_ssh_tc { + SSAM_SSH_TC_LPC = 0x0b, + SSAM_SSH_TC_TCL = 0x0c, + SSAM_SSH_TC_SFL = 0x0d, +- SSAM_SSH_TC_KIP = 0x0e, ++ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ + SSAM_SSH_TC_EXT = 0x0f, + SSAM_SSH_TC_BLD = 0x10, + SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ +-- +2.37.1 + +From 50b9b6d1d3d557a736d113724e4689af80510948 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:44 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Generify subsystem hub + functionality + +The Surface System Aggregator Module (SSAM) has multiple subsystems that +can manage detachable devices. At the moment, we only support the "base" +(BAS/0x11) subsystem, which is used on the Surface Book 3 to manage +devices (including keyboard, touchpad, and secondary battery) connected +to the base of the device. + +The Surface Pro 8 has a new type-cover with keyboard and touchpad, which +is managed via the KIP/0x0e subsystem. The general procedure is the +same, but with slightly different events and setup. To make +implementation of the KIP hub easier and prevent duplication, generify +the parts of the base hub that we can use for the KIP hub (or any +potential future subsystem hubs). + +This also switches over to use the newly introduced "hot-remove" +functionality, which should prevent communication issues when devices +have been detached. + +Lastly, also drop the undocumented and unused sysfs "state" attribute of +the base hub. It has at best been useful for debugging. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-10-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 269 ++++++++++-------- + 1 file changed, 153 insertions(+), 116 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 9f630e890ff7..09cbeee2428b 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -308,30 +308,159 @@ static int ssam_hub_register_clients(struct device *parent, struct ssam_controll + } + + +-/* -- SSAM base-hub driver. ------------------------------------------------- */ ++/* -- SSAM generic subsystem hub driver framework. -------------------------- */ + +-/* +- * 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_hub_state { ++ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ ++ SSAM_HUB_CONNECTED, ++ SSAM_HUB_DISCONNECTED, ++}; + +-enum ssam_base_hub_state { +- SSAM_BASE_HUB_UNINITIALIZED, +- SSAM_BASE_HUB_CONNECTED, +- SSAM_BASE_HUB_DISCONNECTED, ++enum ssam_hub_flags { ++ SSAM_HUB_HOT_REMOVED, + }; + +-struct ssam_base_hub { ++struct ssam_hub { + struct ssam_device *sdev; + +- enum ssam_base_hub_state state; ++ enum ssam_hub_state state; ++ unsigned long flags; ++ + struct delayed_work update_work; ++ unsigned long connect_delay; + + struct ssam_event_notifier notif; ++ ++ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); + }; + ++static void ssam_hub_update_workfn(struct work_struct *work) ++{ ++ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); ++ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); ++ enum ssam_hub_state state; ++ int status = 0; ++ ++ status = hub->get_state(hub, &state); ++ if (status) ++ return; ++ ++ /* ++ * There is a small possibility that hub devices were hot-removed and ++ * re-added before we were able to remove them here. In that case, both ++ * the state returned by get_state() and the state of the hub will ++ * equal SSAM_HUB_CONNECTED and we would bail early below, which would ++ * leave child devices without proper (re-)initialization and the ++ * hot-remove flag set. ++ * ++ * Therefore, we check whether devices have been hot-removed via an ++ * additional flag on the hub and, in this case, override the returned ++ * hub state. In case of a missed disconnect (i.e. get_state returned ++ * "connected"), we further need to re-schedule this work (with the ++ * appropriate delay) as the actual connect work submission might have ++ * been merged with this one. ++ * ++ * This then leads to one of two cases: Either we submit an unnecessary ++ * work item (which will get ignored via either the queue or the state ++ * checks) or, in the unlikely case that the work is actually required, ++ * double the normal connect delay. ++ */ ++ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { ++ if (state == SSAM_HUB_CONNECTED) ++ schedule_delayed_work(&hub->update_work, hub->connect_delay); ++ ++ state = SSAM_HUB_DISCONNECTED; ++ } ++ ++ if (hub->state == state) ++ return; ++ hub->state = state; ++ ++ if (hub->state == SSAM_HUB_CONNECTED) ++ status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); ++ else ++ ssam_remove_clients(&hub->sdev->dev); ++ ++ if (status) ++ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); ++} ++ ++static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (is_ssam_device(dev)) ++ ssam_device_mark_hot_removed(sdev); ++ ++ return 0; ++} ++ ++static void ssam_hub_update(struct ssam_hub *hub, bool connected) ++{ ++ unsigned long delay; ++ ++ /* Mark devices as hot-removed before we remove any. */ ++ if (!connected) { ++ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); ++ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); ++ } ++ ++ /* ++ * Delay update when the base/keyboard cover is being connected to give ++ * devices/EC some time to set up. ++ */ ++ delay = connected ? hub->connect_delay : 0; ++ ++ schedule_delayed_work(&hub->update_work, delay); ++} ++ ++static int __maybe_unused ssam_hub_resume(struct device *dev) ++{ ++ struct ssam_hub *hub = dev_get_drvdata(dev); ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++} ++static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); ++ ++static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) ++{ ++ int status; ++ ++ hub->sdev = sdev; ++ hub->state = SSAM_HUB_UNINITIALIZED; ++ ++ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); ++ ++ ssam_device_set_drvdata(sdev, hub); ++ ++ status = ssam_device_notifier_register(sdev, &hub->notif); ++ if (status) ++ return status; ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++} ++ ++static void ssam_hub_remove(struct ssam_device *sdev) ++{ ++ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); ++ ++ ssam_device_notifier_unregister(sdev, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); ++ ssam_remove_clients(&sdev->dev); ++} ++ ++ ++/* -- 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) ++ + SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = 0x01, +@@ -342,7 +471,7 @@ SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { + #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) ++static int ssam_base_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) + { + u8 opmode; + int status; +@@ -354,62 +483,16 @@ static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_h + } + + if (opmode != SSAM_BAS_OPMODE_TABLET) +- *state = SSAM_BASE_HUB_CONNECTED; ++ *state = SSAM_HUB_CONNECTED; + else +- *state = SSAM_BASE_HUB_DISCONNECTED; ++ *state = SSAM_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 = hub->state == SSAM_BASE_HUB_CONNECTED; +- +- 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, +-}; +- +-static const struct attribute_group ssam_base_hub_group = { +- .attrs = ssam_base_hub_attrs, +-}; +- +-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; +- +- status = ssam_base_hub_query_state(hub, &state); +- if (status) +- return; +- +- if (hub->state == state) +- return; +- hub->state = state; +- +- if (hub->state == SSAM_BASE_HUB_CONNECTED) +- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); +- else +- ssam_remove_clients(&hub->sdev->dev); +- +- if (status) +- dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status); +-} +- + static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) + { +- struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif); +- unsigned long delay; ++ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); + + if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) + return 0; +@@ -419,13 +502,7 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam + return 0; + } + +- /* +- * 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; +- +- schedule_delayed_work(&hub->update_work, delay); ++ ssam_hub_update(hub, event->data[0]); + + /* + * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and +@@ -435,27 +512,14 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam + return 0; + } + +-static int __maybe_unused ssam_base_hub_resume(struct device *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); +- + static int ssam_base_hub_probe(struct ssam_device *sdev) + { +- struct ssam_base_hub *hub; +- int status; ++ struct ssam_hub *hub; + + hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + +- 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; +@@ -464,37 +528,10 @@ 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_device_notifier_register(sdev, &hub->notif); +- if (status) +- return status; +- +- status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); +- if (status) +- goto err; +- +- schedule_delayed_work(&hub->update_work, 0); +- return 0; ++ hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; ++ hub->get_state = ssam_base_hub_query_state; + +-err: +- ssam_device_notifier_unregister(sdev, &hub->notif); +- cancel_delayed_work_sync(&hub->update_work); +- ssam_remove_clients(&sdev->dev); +- 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_device_notifier_unregister(sdev, &hub->notif); +- cancel_delayed_work_sync(&hub->update_work); +- ssam_remove_clients(&sdev->dev); ++ return ssam_hub_setup(sdev, hub); + } + + static const struct ssam_device_id ssam_base_hub_match[] = { +@@ -504,12 +541,12 @@ static const struct ssam_device_id ssam_base_hub_match[] = { + + static struct ssam_device_driver ssam_base_hub_driver = { + .probe = ssam_base_hub_probe, +- .remove = ssam_base_hub_remove, ++ .remove = ssam_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, ++ .pm = &ssam_hub_pm_ops, + }, + }; + +-- +2.37.1 + +From c74992ea392a3a93d52871413c3a2f97df2051f9 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:45 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Change device ID for + base hub + +Use the target category of the (base) hub as instance id in the +(virtual) hub device UID. This makes association of the hub with the +respective subsystem easier. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-11-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 09cbeee2428b..b11ce87c7184 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -43,7 +43,7 @@ static const struct software_node ssam_node_root = { + + /* 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", ++ .name = "ssam:00:00:02:11:00", + .parent = &ssam_node_root, + }; + +@@ -535,7 +535,7 @@ static int ssam_base_hub_probe(struct ssam_device *sdev) + } + + static const struct ssam_device_id ssam_base_hub_match[] = { +- { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) }, ++ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, + { }, + }; + +-- +2.37.1 + +From 7fffe2d6e96ec095bd559fbc18739755f6245619 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:46 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add KIP device hub + +Add a Surface System Aggregator Module (SSAM) client device hub for +hot-removable devices managed via the KIP subsystem. + +The KIP subsystem (full name unknown, abbreviation has been obtained +through reverse engineering) is a subsystem that manages hot-removable +SSAM client devices. Specifically, it manages HID input devices +contained in the detachable keyboard cover of the Surface Pro 8 and +Surface Pro X. + +The KIP subsystem handles a single group of devices (e.g. all devices +contained in the keyboard cover) and cannot handle devices individually. +Thus we model it as a client device hub, which (hot-)removes all devices +contained under it once removal of the hub (e.g. keyboard cover) has +been detected and (re-)adds all devices once the physical hub device has +been (re-)attached. To do this, use the previously generified SSAM +subsystem hub framework. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-12-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 103 +++++++++++++++++- + 1 file changed, 101 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index b11ce87c7184..f15cef60630f 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -551,6 +551,93 @@ static struct ssam_device_driver ssam_base_hub_driver = { + }; + + ++/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ ++ ++/* ++ * Some devices may need a bit of time to be fully usable after being ++ * (re-)connected. This delay has been determined via experimentation. ++ */ ++#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) ++ ++#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { ++ .target_category = SSAM_SSH_TC_KIP, ++ .target_id = 0x01, ++ .command_id = 0x2c, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) ++{ ++ int status; ++ u8 connected; ++ ++ status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); ++ if (status < 0) { ++ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); ++ return status; ++ } ++ ++ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; ++ return 0; ++} ++ ++static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); ++ ++ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length < 1) { ++ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); ++ return 0; ++ } ++ ++ ssam_hub_update(hub, event->data[0]); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static int ssam_kip_hub_probe(struct ssam_device *sdev) ++{ ++ struct ssam_hub *hub; ++ ++ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); ++ if (!hub) ++ return -ENOMEM; ++ ++ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ ++ hub->notif.base.fn = ssam_kip_hub_notif; ++ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, ++ hub->notif.event.id.instance = 0, ++ hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; ++ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; ++ hub->get_state = ssam_kip_get_connection_state; ++ ++ return ssam_hub_setup(sdev, hub); ++} ++ ++static const struct ssam_device_id ssam_kip_hub_match[] = { ++ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, ++ { }, ++}; ++ ++static struct ssam_device_driver ssam_kip_hub_driver = { ++ .probe = ssam_kip_hub_probe, ++ .remove = ssam_hub_remove, ++ .match_table = ssam_kip_hub_match, ++ .driver = { ++ .name = "surface_kip_hub", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_hub_pm_ops, ++ }, ++}; ++ ++ + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + + static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -673,18 +760,30 @@ static int __init ssam_device_hub_init(void) + + status = platform_driver_register(&ssam_platform_hub_driver); + if (status) +- return status; ++ goto err_platform; + + status = ssam_device_driver_register(&ssam_base_hub_driver); + if (status) +- platform_driver_unregister(&ssam_platform_hub_driver); ++ goto err_base; ++ ++ status = ssam_device_driver_register(&ssam_kip_hub_driver); ++ if (status) ++ goto err_kip; + ++ return 0; ++ ++err_kip: ++ ssam_device_driver_unregister(&ssam_base_hub_driver); ++err_base: ++ platform_driver_unregister(&ssam_platform_hub_driver); ++err_platform: + return status; + } + module_init(ssam_device_hub_init); + + static void __exit ssam_device_hub_exit(void) + { ++ ssam_device_driver_unregister(&ssam_kip_hub_driver); + ssam_device_driver_unregister(&ssam_base_hub_driver); + platform_driver_unregister(&ssam_platform_hub_driver); + } +-- +2.37.1 + +From c0f82811a24c4a1b5efb26bb80fb0667dd5ce4d0 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 27 May 2022 04:34:47 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + keyboard cover on Surface Pro 8 + +Add support for the detachable keyboard cover on the Surface Pro 8. + +The keyboard cover on the Surface Pro 8 is, unlike the keyboard covers +of earlier Surface Pro generations, handled via the Surface System +Aggregator Module (SSAM). The keyboard and touchpad (as well as other +HID input devices) of this cover are standard SSAM HID client devices +(just like keyboard and touchpad on e.g. the Surface Laptop 3 and 4), +however, some care needs to be taken as they can be physically detached +(similarly to the Surface Book 3). Specifically, the respective SSAM +client devices need to be removed when the keyboard cover has been +detached and (re-)initialized when the keyboard cover has been +(re-)attached. + +On the Surface Pro 8, detachment of the keyboard cover (and by extension +its devices) is managed via the KIP subsystem. Therefore, said devices +need to be registered under the KIP device hub, which in turn will +remove and re-create/re-initialize those devices as needed. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220527023447.2460025-13-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 37 ++++++++++++++++++- + 1 file changed, 36 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index f15cef60630f..bf3303f1aa71 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -41,6 +41,12 @@ static const struct software_node ssam_node_root = { + .name = "ssam_platform_hub", + }; + ++/* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ ++static const struct software_node ssam_node_hub_kip = { ++ .name = "ssam:00:00:01:0e:00", ++ .parent = &ssam_node_root, ++}; ++ + /* Base device hub (devices attached to Surface Book 3 base). */ + static const struct software_node ssam_node_hub_base = { + .name = "ssam:00:00:02:11:00", +@@ -155,6 +161,30 @@ static const struct software_node ssam_node_hid_base_iid6 = { + .parent = &ssam_node_hub_base, + }; + ++/* HID keyboard (KIP hub). */ ++static const struct software_node ssam_node_hid_kip_keyboard = { ++ .name = "ssam:01:15:02:01:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID pen stash (KIP hub; pen taken / stashed away evens). */ ++static const struct software_node ssam_node_hid_kip_penstash = { ++ .name = "ssam:01:15:02:02:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID touchpad (KIP hub). */ ++static const struct software_node ssam_node_hid_kip_touchpad = { ++ .name = "ssam:01:15:02:03:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ ++/* HID device instance 5 (KIP hub, unknown HID device). */ ++static const struct software_node ssam_node_hid_kip_iid5 = { ++ .name = "ssam:01:15:02:05:00", ++ .parent = &ssam_node_hub_kip, ++}; ++ + /* + * Devices for 5th- and 6th-generations models: + * - Surface Book 2, +@@ -230,10 +260,15 @@ static const struct software_node *ssam_node_group_sp7[] = { + + static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_root, ++ &ssam_node_hub_kip, + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, +- /* TODO: Add support for keyboard cover. */ ++ &ssam_node_hid_kip_keyboard, ++ &ssam_node_hid_kip_penstash, ++ &ssam_node_hid_kip_touchpad, ++ &ssam_node_hid_kip_iid5, ++ /* TODO: Add support for tablet mode switch. */ + NULL, + }; + +-- +2.37.1 + +From ef9a5571a61a0c66a7ee9f5405fe0093e05dcd30 Mon Sep 17 00:00:00 2001 +From: Tetsuo Handa +Date: Fri, 10 Jun 2022 14:41:58 +0900 +Subject: [PATCH] platform/surface: avoid flush_scheduled_work() usage + +Use local wq in order to avoid flush_scheduled_work() usage. + +Signed-off-by: Tetsuo Handa +Reviewed-by: Maximilian Luz +Tested-by: Maximilian Luz +Link: https://lore.kernel.org/r/63ec2d45-c67c-1134-f6d3-490c8ba67a01@I-love.SAKURA.ne.jp +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../platform/surface/surface_acpi_notify.c | 27 ++++++++++++++++--- + 1 file changed, 24 insertions(+), 3 deletions(-) + +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +index 7b758f8cc137..c0e12f0b9b79 100644 +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -37,6 +37,7 @@ struct san_data { + #define to_san_data(ptr, member) \ + container_of(ptr, struct san_data, member) + ++static struct workqueue_struct *san_wq; + + /* -- dGPU notifier interface. ---------------------------------------------- */ + +@@ -356,7 +357,7 @@ static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, + + memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); + +- schedule_delayed_work(&work->work, delay); ++ queue_delayed_work(san_wq, &work->work, delay); + return SSAM_NOTIF_HANDLED; + } + +@@ -861,7 +862,7 @@ static int san_remove(struct platform_device *pdev) + * We have unregistered our event sources. Now we need to ensure that + * all delayed works they may have spawned are run to completion. + */ +- flush_scheduled_work(); ++ flush_workqueue(san_wq); + + return 0; + } +@@ -881,7 +882,27 @@ static struct platform_driver surface_acpi_notify = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + }; +-module_platform_driver(surface_acpi_notify); ++ ++static int __init san_init(void) ++{ ++ int ret; ++ ++ san_wq = alloc_workqueue("san_wq", 0, 0); ++ if (!san_wq) ++ return -ENOMEM; ++ ret = platform_driver_register(&surface_acpi_notify); ++ if (ret) ++ destroy_workqueue(san_wq); ++ return ret; ++} ++module_init(san_init); ++ ++static void __exit san_exit(void) ++{ ++ platform_driver_unregister(&surface_acpi_notify); ++ destroy_workqueue(san_wq); ++} ++module_exit(san_exit); + + MODULE_AUTHOR("Maximilian Luz "); + MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); +-- +2.37.1 + +From 99089941381fc15d506ac273527e758eed824bfd Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Tue, 14 Jun 2022 21:41:17 +0200 +Subject: [PATCH] platform/surface: aggregator: Reserve more event- and + target-categories + +With the introduction of the Surface Laptop Studio, more event- and +target categories have been added. Therefore, increase the number of +reserved events and extend the enum of know target categories to +accommodate this. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220614194117.4118897-1-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/trace.h | 80 +++++++++++-------- + include/linux/surface_aggregator/serial_hub.h | 75 +++++++++-------- + 2 files changed, 85 insertions(+), 70 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h +index de64cf169060..cc9e73fbc18e 100644 +--- a/drivers/platform/surface/aggregator/trace.h ++++ b/drivers/platform/surface/aggregator/trace.h +@@ -76,7 +76,7 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); +-TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC0); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); +@@ -85,6 +85,11 @@ TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); + TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SPT); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SYS); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC1); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SHB); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_POS); + + #define SSAM_PTR_UID_LEN 9 + #define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) +@@ -229,40 +234,45 @@ static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) + + #define ssam_show_ssh_tc(rqid) \ + __print_symbolic(rqid, \ +- { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ +- { SSAM_SSH_TC_SAM, "SAM" }, \ +- { SSAM_SSH_TC_BAT, "BAT" }, \ +- { SSAM_SSH_TC_TMP, "TMP" }, \ +- { SSAM_SSH_TC_PMC, "PMC" }, \ +- { SSAM_SSH_TC_FAN, "FAN" }, \ +- { SSAM_SSH_TC_PoM, "PoM" }, \ +- { SSAM_SSH_TC_DBG, "DBG" }, \ +- { SSAM_SSH_TC_KBD, "KBD" }, \ +- { SSAM_SSH_TC_FWU, "FWU" }, \ +- { SSAM_SSH_TC_UNI, "UNI" }, \ +- { SSAM_SSH_TC_LPC, "LPC" }, \ +- { SSAM_SSH_TC_TCL, "TCL" }, \ +- { SSAM_SSH_TC_SFL, "SFL" }, \ +- { SSAM_SSH_TC_KIP, "KIP" }, \ +- { SSAM_SSH_TC_EXT, "EXT" }, \ +- { SSAM_SSH_TC_BLD, "BLD" }, \ +- { SSAM_SSH_TC_BAS, "BAS" }, \ +- { SSAM_SSH_TC_SEN, "SEN" }, \ +- { SSAM_SSH_TC_SRQ, "SRQ" }, \ +- { SSAM_SSH_TC_MCU, "MCU" }, \ +- { SSAM_SSH_TC_HID, "HID" }, \ +- { SSAM_SSH_TC_TCH, "TCH" }, \ +- { SSAM_SSH_TC_BKL, "BKL" }, \ +- { SSAM_SSH_TC_TAM, "TAM" }, \ +- { SSAM_SSH_TC_ACC, "ACC" }, \ +- { SSAM_SSH_TC_UFI, "UFI" }, \ +- { SSAM_SSH_TC_USC, "USC" }, \ +- { SSAM_SSH_TC_PEN, "PEN" }, \ +- { SSAM_SSH_TC_VID, "VID" }, \ +- { SSAM_SSH_TC_AUD, "AUD" }, \ +- { SSAM_SSH_TC_SMC, "SMC" }, \ +- { SSAM_SSH_TC_KPD, "KPD" }, \ +- { SSAM_SSH_TC_REG, "REG" } \ ++ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ ++ { SSAM_SSH_TC_SAM, "SAM" }, \ ++ { SSAM_SSH_TC_BAT, "BAT" }, \ ++ { SSAM_SSH_TC_TMP, "TMP" }, \ ++ { SSAM_SSH_TC_PMC, "PMC" }, \ ++ { SSAM_SSH_TC_FAN, "FAN" }, \ ++ { SSAM_SSH_TC_PoM, "PoM" }, \ ++ { SSAM_SSH_TC_DBG, "DBG" }, \ ++ { SSAM_SSH_TC_KBD, "KBD" }, \ ++ { SSAM_SSH_TC_FWU, "FWU" }, \ ++ { SSAM_SSH_TC_UNI, "UNI" }, \ ++ { SSAM_SSH_TC_LPC, "LPC" }, \ ++ { SSAM_SSH_TC_TCL, "TCL" }, \ ++ { SSAM_SSH_TC_SFL, "SFL" }, \ ++ { SSAM_SSH_TC_KIP, "KIP" }, \ ++ { SSAM_SSH_TC_EXT, "EXT" }, \ ++ { SSAM_SSH_TC_BLD, "BLD" }, \ ++ { SSAM_SSH_TC_BAS, "BAS" }, \ ++ { SSAM_SSH_TC_SEN, "SEN" }, \ ++ { SSAM_SSH_TC_SRQ, "SRQ" }, \ ++ { SSAM_SSH_TC_MCU, "MCU" }, \ ++ { SSAM_SSH_TC_HID, "HID" }, \ ++ { SSAM_SSH_TC_TCH, "TCH" }, \ ++ { SSAM_SSH_TC_BKL, "BKL" }, \ ++ { SSAM_SSH_TC_TAM, "TAM" }, \ ++ { SSAM_SSH_TC_ACC0, "ACC0" }, \ ++ { SSAM_SSH_TC_UFI, "UFI" }, \ ++ { SSAM_SSH_TC_USC, "USC" }, \ ++ { SSAM_SSH_TC_PEN, "PEN" }, \ ++ { SSAM_SSH_TC_VID, "VID" }, \ ++ { SSAM_SSH_TC_AUD, "AUD" }, \ ++ { SSAM_SSH_TC_SMC, "SMC" }, \ ++ { SSAM_SSH_TC_KPD, "KPD" }, \ ++ { SSAM_SSH_TC_REG, "REG" }, \ ++ { SSAM_SSH_TC_SPT, "SPT" }, \ ++ { SSAM_SSH_TC_SYS, "SYS" }, \ ++ { SSAM_SSH_TC_ACC1, "ACC1" }, \ ++ { SSAM_SSH_TC_SHB, "SMB" }, \ ++ { SSAM_SSH_TC_POS, "POS" } \ + ) + + DECLARE_EVENT_CLASS(ssam_frame_class, +diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h +index 26b95ec12733..45501b6e54e8 100644 +--- a/include/linux/surface_aggregator/serial_hub.h ++++ b/include/linux/surface_aggregator/serial_hub.h +@@ -201,7 +201,7 @@ static inline u16 ssh_crc(const u8 *buf, size_t len) + * exception of zero, which is not an event ID. Thus, this is also the + * absolute maximum number of event handlers that can be registered. + */ +-#define SSH_NUM_EVENTS 34 ++#define SSH_NUM_EVENTS 38 + + /* + * SSH_NUM_TARGETS - The number of communication targets used in the protocol. +@@ -292,40 +292,45 @@ struct ssam_span { + * Windows driver. + */ + enum ssam_ssh_tc { +- /* Category 0x00 is invalid for EC use. */ +- SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ +- SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ +- SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ +- SSAM_SSH_TC_PMC = 0x04, +- SSAM_SSH_TC_FAN = 0x05, +- SSAM_SSH_TC_PoM = 0x06, +- SSAM_SSH_TC_DBG = 0x07, +- SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ +- SSAM_SSH_TC_FWU = 0x09, +- SSAM_SSH_TC_UNI = 0x0a, +- SSAM_SSH_TC_LPC = 0x0b, +- SSAM_SSH_TC_TCL = 0x0c, +- SSAM_SSH_TC_SFL = 0x0d, +- SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ +- SSAM_SSH_TC_EXT = 0x0f, +- SSAM_SSH_TC_BLD = 0x10, +- SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ +- SSAM_SSH_TC_SEN = 0x12, +- SSAM_SSH_TC_SRQ = 0x13, +- SSAM_SSH_TC_MCU = 0x14, +- SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ +- SSAM_SSH_TC_TCH = 0x16, +- SSAM_SSH_TC_BKL = 0x17, +- SSAM_SSH_TC_TAM = 0x18, +- SSAM_SSH_TC_ACC = 0x19, +- SSAM_SSH_TC_UFI = 0x1a, +- SSAM_SSH_TC_USC = 0x1b, +- SSAM_SSH_TC_PEN = 0x1c, +- SSAM_SSH_TC_VID = 0x1d, +- SSAM_SSH_TC_AUD = 0x1e, +- SSAM_SSH_TC_SMC = 0x1f, +- SSAM_SSH_TC_KPD = 0x20, +- SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ ++ /* Category 0x00 is invalid for EC use. */ ++ SSAM_SSH_TC_SAM = 0x01, /* Generic system functionality, real-time clock. */ ++ SSAM_SSH_TC_BAT = 0x02, /* Battery/power subsystem. */ ++ SSAM_SSH_TC_TMP = 0x03, /* Thermal subsystem. */ ++ SSAM_SSH_TC_PMC = 0x04, ++ SSAM_SSH_TC_FAN = 0x05, ++ SSAM_SSH_TC_PoM = 0x06, ++ SSAM_SSH_TC_DBG = 0x07, ++ SSAM_SSH_TC_KBD = 0x08, /* Legacy keyboard (Laptop 1/2). */ ++ SSAM_SSH_TC_FWU = 0x09, ++ SSAM_SSH_TC_UNI = 0x0a, ++ SSAM_SSH_TC_LPC = 0x0b, ++ SSAM_SSH_TC_TCL = 0x0c, ++ SSAM_SSH_TC_SFL = 0x0d, ++ SSAM_SSH_TC_KIP = 0x0e, /* Manages detachable peripherals (Pro X/8 keyboard cover) */ ++ SSAM_SSH_TC_EXT = 0x0f, ++ SSAM_SSH_TC_BLD = 0x10, ++ SSAM_SSH_TC_BAS = 0x11, /* Detachment system (Surface Book 2/3). */ ++ SSAM_SSH_TC_SEN = 0x12, ++ SSAM_SSH_TC_SRQ = 0x13, ++ SSAM_SSH_TC_MCU = 0x14, ++ SSAM_SSH_TC_HID = 0x15, /* Generic HID input subsystem. */ ++ SSAM_SSH_TC_TCH = 0x16, ++ SSAM_SSH_TC_BKL = 0x17, ++ SSAM_SSH_TC_TAM = 0x18, ++ SSAM_SSH_TC_ACC0 = 0x19, ++ SSAM_SSH_TC_UFI = 0x1a, ++ SSAM_SSH_TC_USC = 0x1b, ++ SSAM_SSH_TC_PEN = 0x1c, ++ SSAM_SSH_TC_VID = 0x1d, ++ SSAM_SSH_TC_AUD = 0x1e, ++ SSAM_SSH_TC_SMC = 0x1f, ++ SSAM_SSH_TC_KPD = 0x20, ++ SSAM_SSH_TC_REG = 0x21, /* Extended event registry. */ ++ SSAM_SSH_TC_SPT = 0x22, ++ SSAM_SSH_TC_SYS = 0x23, ++ SSAM_SSH_TC_ACC1 = 0x24, ++ SSAM_SSH_TC_SHB = 0x25, ++ SSAM_SSH_TC_POS = 0x26, /* For obtaining Laptop Studio screen position. */ + }; + + +-- +2.37.1 + +From 318b0e7587fb5b3849089ea7bc84835383f22029 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 20:36:39 +0200 +Subject: [PATCH] platform/surface: aggregator: Add helper macros for requests + with argument and return value + +Add helper macros for synchronous stack-allocated Surface Aggregator +request with both argument and return value, similar to the current +argument-only and return-value-only ones. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624183642.910893-2-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + include/linux/surface_aggregator/controller.h | 125 ++++++++++++++++++ + include/linux/surface_aggregator/device.h | 36 +++++ + 2 files changed, 161 insertions(+) + +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h +index 50a2b4926c06..d11a1c6e3186 100644 +--- a/include/linux/surface_aggregator/controller.h ++++ b/include/linux/surface_aggregator/controller.h +@@ -469,6 +469,67 @@ struct ssam_request_spec_md { + return 0; \ + } + ++/** ++ * SSAM_DEFINE_SYNC_REQUEST_WR() - Define synchronous SAM request function with ++ * both argument and return value. ++ * @name: Name of the generated function. ++ * @atype: Type of the request's argument. ++ * @rtype: Type of the request's return value. ++ * @spec: Specification (&struct ssam_request_spec) defining the request. ++ * ++ * Defines a function executing the synchronous SAM request specified by @spec, ++ * with the request taking an argument of type @atype and having a return value ++ * of type @rtype. The generated function takes care of setting up the request ++ * and response structs, buffer allocation, as well as 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 ``static int name(struct ++ * ssam_controller *ctrl, const atype *arg, 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 argument is specified via the ``arg`` pointer. 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_WR(name, atype, rtype, spec...) \ ++ static int name(struct ssam_controller *ctrl, const atype *arg, rtype *ret) \ ++ { \ ++ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ ++ struct ssam_request rqst; \ ++ struct ssam_response rsp; \ ++ int status; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.target_id = s.target_id; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = s.instance_id; \ ++ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ ++ rqst.length = sizeof(atype); \ ++ rqst.payload = (u8 *)arg; \ ++ \ ++ rsp.capacity = sizeof(rtype); \ ++ rsp.length = 0; \ ++ rsp.pointer = (u8 *)ret; \ ++ \ ++ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ ++ if (status) \ ++ return status; \ ++ \ ++ if (rsp.length != sizeof(rtype)) { \ ++ struct device *dev = ssam_controller_device(ctrl); \ ++ dev_err(dev, \ ++ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ ++ sizeof(rtype), rsp.length, rqst.target_category,\ ++ rqst.command_id); \ ++ return -EIO; \ ++ } \ ++ \ ++ return 0; \ ++ } ++ + /** + * SSAM_DEFINE_SYNC_REQUEST_MD_N() - Define synchronous multi-device SAM + * request function with neither argument nor return value. +@@ -613,6 +674,70 @@ struct ssam_request_spec_md { + return 0; \ + } + ++/** ++ * SSAM_DEFINE_SYNC_REQUEST_MD_WR() - Define synchronous multi-device SAM ++ * request function with both argument and return value. ++ * @name: Name of the generated function. ++ * @atype: Type of the request's argument. ++ * @rtype: Type of the request's return value. ++ * @spec: Specification (&struct ssam_request_spec_md) defining the request. ++ * ++ * Defines a function executing the synchronous SAM request specified by @spec, ++ * with the request taking an argument of type @atype and having a return value ++ * of type @rtype. Device specifying parameters are not hard-coded, but instead ++ * must be provided to the function. The generated function takes care of ++ * setting up the request and response structs, buffer allocation, as well as ++ * 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 ``static int name(struct ++ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg, 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 argument is specified via the ``arg`` pointer. 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_WR(name, atype, rtype, spec...) \ ++ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, \ ++ const atype *arg, rtype *ret) \ ++ { \ ++ struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \ ++ struct ssam_request rqst; \ ++ struct ssam_response rsp; \ ++ int status; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.target_id = tid; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = iid; \ ++ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ ++ rqst.length = sizeof(atype); \ ++ rqst.payload = (u8 *)arg; \ ++ \ ++ rsp.capacity = sizeof(rtype); \ ++ rsp.length = 0; \ ++ rsp.pointer = (u8 *)ret; \ ++ \ ++ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, sizeof(atype)); \ ++ if (status) \ ++ return status; \ ++ \ ++ if (rsp.length != sizeof(rtype)) { \ ++ struct device *dev = ssam_controller_device(ctrl); \ ++ dev_err(dev, \ ++ "rqst: invalid response length, expected %zu, got %zu (tc: %#04x, cid: %#04x)", \ ++ sizeof(rtype), rsp.length, rqst.target_category,\ ++ rqst.command_id); \ ++ return -EIO; \ ++ } \ ++ \ ++ return 0; \ ++ } ++ + + /* -- Event notifier/callbacks. --------------------------------------------- */ + +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index c418f7f2732d..6cf7e80312d5 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -483,6 +483,42 @@ static inline void ssam_remove_clients(struct device *dev) {} + sdev->uid.instance, ret); \ + } + ++/** ++ * SSAM_DEFINE_SYNC_REQUEST_CL_WR() - Define synchronous client-device SAM ++ * request function with argument and return value. ++ * @name: Name of the generated function. ++ * @atype: Type of the request's argument. ++ * @rtype: Type of the request's return value. ++ * @spec: Specification (&struct ssam_request_spec_md) defining the request. ++ * ++ * Defines a function executing the synchronous SAM request specified by @spec, ++ * with the request taking an argument of type @atype and having a return value ++ * of type @rtype. Device specifying parameters are not hard-coded, but instead ++ * are provided via the client device, specifically its UID, supplied when ++ * calling this function. The generated function takes care of setting up the ++ * request struct, buffer allocation, as well as 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 ``static int name(struct ssam_device ++ * *sdev, const atype *arg, 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 argument is ++ * specified via the ``arg`` pointer. 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_CL_WR(name, atype, rtype, spec...) \ ++ SSAM_DEFINE_SYNC_REQUEST_MD_WR(__raw_##name, atype, rtype, spec) \ ++ static int name(struct ssam_device *sdev, const atype *arg, rtype *ret) \ ++ { \ ++ return __raw_##name(sdev->ctrl, sdev->uid.target, \ ++ sdev->uid.instance, arg, ret); \ ++ } ++ + + /* -- Helpers for client-device notifiers. ---------------------------------- */ + +-- +2.37.1 + +From 512045cafc726b6ed25380f9e8da1a03d54b468b Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 20:36:40 +0200 +Subject: [PATCH] platform/surface: Add KIP/POS tablet-mode switch driver +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add a driver providing a tablet-mode switch input device for Microsoft +Surface devices using the Surface Aggregator KIP subsystem (to manage +detachable peripherals) or POS subsystem (to obtain device posture +information). + +The KIP (full name unknown, abbreviation found through reverse +engineering) subsystem is used on the Surface Pro 8 and Surface Pro X to +manage the keyboard cover. Among other things, it provides information +on the positioning (posture) of the cover (closed, laptop-style, +detached, folded-back, ...), which can be used to implement an input +device providing the SW_TABLET_MODE event. Similarly, the POS (posture +information) subsystem provides such information on the Surface Laptop +Studio, with the difference being that the keyboard is not detachable. + +As implementing the tablet-mode switch for both subsystems is largely +similar, the driver proposed in this commit, in large, acts as a generic +tablet mode switch driver framework for the Surface Aggregator Module. +Specific implementations using this framework are provided for the KIP +and POS subsystems, adding tablet-mode switch support to the +aforementioned devices. + +A few more notes on the Surface Laptop Studio: + +A peculiarity of the Surface Laptop Studio is its "slate/tent" mode +(symbolized: user> _/\). In this mode, the screen covers the keyboard +but leaves the touchpad exposed. This is essentially a mode in-between +tablet and laptop, and it is debatable whether tablet-mode should be +enabled in this mode. We therefore let the user decide this via a module +parameter. + +In particular, tablet-mode may bring up the on-screen touch keyboard +more easily, which would be desirable in this mode. However, some +user-space software currently also decides to disable keyboard and, more +importantly, touchpad input, while the touchpad is still accessible in +the "slate/tent" mode. Furthermore, this mode shares its identifier with +"slate/flipped" mode where the screen is flipped 180° and the keyboard +points away from the user (symbolized: user> /_). In this mode we would +like to enable auto-rotation, something that user-space software may +only do when tablet-mode is enabled. We therefore default to the +slate-mode enabling the tablet-mode switch. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624183642.910893-3-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + .../sysfs-bus-surface_aggregator-tabletsw | 57 ++ + MAINTAINERS | 6 + + drivers/platform/surface/Kconfig | 23 + + drivers/platform/surface/Makefile | 1 + + .../surface/surface_aggregator_tabletsw.c | 533 ++++++++++++++++++ + 5 files changed, 620 insertions(+) + create mode 100644 Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw + create mode 100644 drivers/platform/surface/surface_aggregator_tabletsw.c + +diff --git a/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw b/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw +new file mode 100644 +index 000000000000..74cd9d754e60 +--- /dev/null ++++ b/Documentation/ABI/testing/sysfs-bus-surface_aggregator-tabletsw +@@ -0,0 +1,57 @@ ++What: /sys/bus/surface_aggregator/devices/01:0e:01:00:01/state ++Date: July 2022 ++KernelVersion: 5.20 ++Contact: Maximilian Luz ++Description: ++ This attribute returns a string with the current type-cover ++ or device posture, as indicated by the embedded controller. ++ Currently returned posture states are: ++ ++ - "disconnected": The type-cover has been disconnected. ++ ++ - "closed": The type-cover has been folded closed and lies on ++ top of the display. ++ ++ - "laptop": The type-cover is open and in laptop-mode, i.e., ++ ready for normal use. ++ ++ - "folded-canvas": The type-cover has been folded back ++ part-ways, but does not lie flush with the back side of the ++ device. In general, this means that the kick-stand is used ++ and extended atop of the cover. ++ ++ - "folded-back": The type cover has been fully folded back and ++ lies flush with the back side of the device. ++ ++ - "": The current state is unknown to the driver, for ++ example due to newer as-of-yet unsupported hardware. ++ ++ New states may be introduced with new hardware. Users therefore ++ must not rely on this list of states being exhaustive and ++ gracefully handle unknown states. ++ ++What: /sys/bus/surface_aggregator/devices/01:26:01:00:01/state ++Date: July 2022 ++KernelVersion: 5.20 ++Contact: Maximilian Luz ++Description: ++ This attribute returns a string with the current device posture, as indicated by the embedded controller. Currently ++ returned posture states are: ++ ++ - "closed": The lid of the device is closed. ++ ++ - "laptop": The lid of the device is opened and the device ++ operates as a normal laptop. ++ ++ - "slate": The screen covers the keyboard or has been flipped ++ back and the device operates mainly based on touch input. ++ ++ - "tablet": The device operates as tablet and exclusively ++ relies on touch input (or external peripherals). ++ ++ - "": The current state is unknown to the driver, for ++ example due to newer as-of-yet unsupported hardware. ++ ++ New states may be introduced with new hardware. Users therefore ++ must not rely on this list of states being exhaustive and ++ gracefully handle unknown states. +diff --git a/MAINTAINERS b/MAINTAINERS +index 64379c699903..11b84de77ab8 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13304,6 +13304,12 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch] + F: include/linux/cciss*.h + F: include/uapi/linux/cciss*.h + ++MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH ++M: Maximilian Luz ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/platform/surface/surface_aggregator_tablet_switch.c ++ + MICROSOFT SURFACE BATTERY AND AC DRIVERS + M: Maximilian Luz + L: linux-pm@vger.kernel.org +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index eb79fbed8059..b152e930cc84 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -99,6 +99,29 @@ config SURFACE_AGGREGATOR_REGISTRY + the respective client devices. Drivers for these devices still need to + be selected via the other options. + ++config SURFACE_AGGREGATOR_TABLET_SWITCH ++ tristate "Surface Aggregator Generic Tablet-Mode Switch Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ depends on INPUT ++ help ++ Provides a tablet-mode switch input device on Microsoft Surface models ++ using the KIP subsystem for detachable keyboards (e.g. keyboard covers) ++ or the POS subsystem for device/screen posture changes. ++ ++ The KIP subsystem is used on newer Surface generations to handle ++ detachable input peripherals, specifically the keyboard cover (containing ++ keyboard and touchpad) on the Surface Pro 8 and Surface Pro X. The POS ++ subsystem is used for device posture change notifications on the Surface ++ Laptop Studio. This module provides a driver to let user-space know when ++ the device should be considered in tablet-mode due to the keyboard cover ++ being detached or folded back (essentially signaling when the keyboard is ++ not available for input). It does so by creating a tablet-mode switch ++ input device, sending the standard SW_TABLET_MODE event on mode change. ++ ++ Select M or Y here, if you want to provide tablet-mode switch input ++ events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 0fc9cd3e4dd9..18b27898543e 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -10,6 +10,7 @@ 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_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +new file mode 100644 +index 000000000000..596ca6c80681 +--- /dev/null ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Surface System Aggregator Module (SSAM) tablet mode switch driver. ++ * ++ * Copyright (C) 2022 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++/* -- SSAM generic tablet switch driver framework. -------------------------- */ ++ ++struct ssam_tablet_sw; ++ ++struct ssam_tablet_sw_ops { ++ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); ++ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); ++ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); ++}; ++ ++struct ssam_tablet_sw { ++ struct ssam_device *sdev; ++ ++ u32 state; ++ struct work_struct update_work; ++ struct input_dev *mode_switch; ++ ++ struct ssam_tablet_sw_ops ops; ++ struct ssam_event_notifier notif; ++}; ++ ++struct ssam_tablet_sw_desc { ++ struct { ++ const char *name; ++ const char *phys; ++ } dev; ++ ++ struct { ++ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); ++ int (*get_state)(struct ssam_tablet_sw *sw, u32 *state); ++ const char *(*state_name)(struct ssam_tablet_sw *sw, u32 state); ++ bool (*state_is_tablet_mode)(struct ssam_tablet_sw *sw, u32 state); ++ } ops; ++ ++ struct { ++ struct ssam_event_registry reg; ++ struct ssam_event_id id; ++ enum ssam_event_mask mask; ++ u8 flags; ++ } event; ++}; ++ ++static ssize_t state_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ ++ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); ++ const char *state = sw->ops.state_name(sw, sw->state); ++ ++ return sysfs_emit(buf, "%s\n", state); ++} ++static DEVICE_ATTR_RO(state); ++ ++static struct attribute *ssam_tablet_sw_attrs[] = { ++ &dev_attr_state.attr, ++ NULL, ++}; ++ ++static const struct attribute_group ssam_tablet_sw_group = { ++ .attrs = ssam_tablet_sw_attrs, ++}; ++ ++static void ssam_tablet_sw_update_workfn(struct work_struct *work) ++{ ++ struct ssam_tablet_sw *sw = container_of(work, struct ssam_tablet_sw, update_work); ++ int tablet, status; ++ u32 state; ++ ++ status = sw->ops.get_state(sw, &state); ++ if (status) ++ return; ++ ++ if (sw->state == state) ++ return; ++ sw->state = state; ++ ++ /* Send SW_TABLET_MODE event. */ ++ tablet = sw->ops.state_is_tablet_mode(sw, state); ++ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); ++ input_sync(sw->mode_switch); ++} ++ ++static int __maybe_unused ssam_tablet_sw_resume(struct device *dev) ++{ ++ struct ssam_tablet_sw *sw = dev_get_drvdata(dev); ++ ++ schedule_work(&sw->update_work); ++ return 0; ++} ++static SIMPLE_DEV_PM_OPS(ssam_tablet_sw_pm_ops, NULL, ssam_tablet_sw_resume); ++ ++static int ssam_tablet_sw_probe(struct ssam_device *sdev) ++{ ++ const struct ssam_tablet_sw_desc *desc; ++ struct ssam_tablet_sw *sw; ++ int tablet, status; ++ ++ desc = ssam_device_get_match_data(sdev); ++ if (!desc) { ++ WARN(1, "no driver match data specified"); ++ return -EINVAL; ++ } ++ ++ sw = devm_kzalloc(&sdev->dev, sizeof(*sw), GFP_KERNEL); ++ if (!sw) ++ return -ENOMEM; ++ ++ sw->sdev = sdev; ++ ++ sw->ops.get_state = desc->ops.get_state; ++ sw->ops.state_name = desc->ops.state_name; ++ sw->ops.state_is_tablet_mode = desc->ops.state_is_tablet_mode; ++ ++ INIT_WORK(&sw->update_work, ssam_tablet_sw_update_workfn); ++ ++ ssam_device_set_drvdata(sdev, sw); ++ ++ /* Get initial state. */ ++ status = sw->ops.get_state(sw, &sw->state); ++ if (status) ++ return status; ++ ++ /* Set up tablet mode switch. */ ++ sw->mode_switch = devm_input_allocate_device(&sdev->dev); ++ if (!sw->mode_switch) ++ return -ENOMEM; ++ ++ sw->mode_switch->name = desc->dev.name; ++ sw->mode_switch->phys = desc->dev.phys; ++ sw->mode_switch->id.bustype = BUS_HOST; ++ sw->mode_switch->dev.parent = &sdev->dev; ++ ++ tablet = sw->ops.state_is_tablet_mode(sw, sw->state); ++ input_set_capability(sw->mode_switch, EV_SW, SW_TABLET_MODE); ++ input_report_switch(sw->mode_switch, SW_TABLET_MODE, tablet); ++ ++ status = input_register_device(sw->mode_switch); ++ if (status) ++ return status; ++ ++ /* Set up notifier. */ ++ sw->notif.base.priority = 0; ++ sw->notif.base.fn = desc->ops.notify; ++ sw->notif.event.reg = desc->event.reg; ++ sw->notif.event.id = desc->event.id; ++ sw->notif.event.mask = desc->event.mask; ++ sw->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_device_notifier_register(sdev, &sw->notif); ++ if (status) ++ return status; ++ ++ status = sysfs_create_group(&sdev->dev.kobj, &ssam_tablet_sw_group); ++ if (status) ++ goto err; ++ ++ /* We might have missed events during setup, so check again. */ ++ schedule_work(&sw->update_work); ++ return 0; ++ ++err: ++ ssam_device_notifier_unregister(sdev, &sw->notif); ++ cancel_work_sync(&sw->update_work); ++ return status; ++} ++ ++static void ssam_tablet_sw_remove(struct ssam_device *sdev) ++{ ++ struct ssam_tablet_sw *sw = ssam_device_get_drvdata(sdev); ++ ++ sysfs_remove_group(&sdev->dev.kobj, &ssam_tablet_sw_group); ++ ++ ssam_device_notifier_unregister(sdev, &sw->notif); ++ cancel_work_sync(&sw->update_work); ++} ++ ++ ++/* -- SSAM KIP tablet switch implementation. -------------------------------- */ ++ ++#define SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED 0x1d ++ ++enum ssam_kip_cover_state { ++ SSAM_KIP_COVER_STATE_DISCONNECTED = 0x01, ++ SSAM_KIP_COVER_STATE_CLOSED = 0x02, ++ SSAM_KIP_COVER_STATE_LAPTOP = 0x03, ++ SSAM_KIP_COVER_STATE_FOLDED_CANVAS = 0x04, ++ SSAM_KIP_COVER_STATE_FOLDED_BACK = 0x05, ++}; ++ ++static const char *ssam_kip_cover_state_name(struct ssam_tablet_sw *sw, u32 state) ++{ ++ switch (state) { ++ case SSAM_KIP_COVER_STATE_DISCONNECTED: ++ return "disconnected"; ++ ++ case SSAM_KIP_COVER_STATE_CLOSED: ++ return "closed"; ++ ++ case SSAM_KIP_COVER_STATE_LAPTOP: ++ return "laptop"; ++ ++ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: ++ return "folded-canvas"; ++ ++ case SSAM_KIP_COVER_STATE_FOLDED_BACK: ++ return "folded-back"; ++ ++ default: ++ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %u\n", state); ++ return ""; ++ } ++} ++ ++static bool ssam_kip_cover_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) ++{ ++ switch (state) { ++ case SSAM_KIP_COVER_STATE_DISCONNECTED: ++ case SSAM_KIP_COVER_STATE_FOLDED_CANVAS: ++ case SSAM_KIP_COVER_STATE_FOLDED_BACK: ++ return true; ++ ++ case SSAM_KIP_COVER_STATE_CLOSED: ++ case SSAM_KIP_COVER_STATE_LAPTOP: ++ return false; ++ ++ default: ++ dev_warn(&sw->sdev->dev, "unknown KIP cover state: %d\n", sw->state); ++ return true; ++ } ++} ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_cover_state, u8, { ++ .target_category = SSAM_SSH_TC_KIP, ++ .target_id = 0x01, ++ .command_id = 0x1d, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_kip_get_cover_state(struct ssam_tablet_sw *sw, u32 *state) ++{ ++ int status; ++ u8 raw; ++ ++ status = ssam_retry(__ssam_kip_get_cover_state, sw->sdev->ctrl, &raw); ++ if (status < 0) { ++ dev_err(&sw->sdev->dev, "failed to query KIP lid state: %d\n", status); ++ return status; ++ } ++ ++ *state = raw; ++ return 0; ++} ++ ++static u32 ssam_kip_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); ++ ++ if (event->command_id != SSAM_EVENT_KIP_CID_COVER_STATE_CHANGED) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length < 1) ++ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); ++ ++ schedule_work(&sw->update_work); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static const struct ssam_tablet_sw_desc ssam_kip_sw_desc = { ++ .dev = { ++ .name = "Microsoft Surface KIP Tablet Mode Switch", ++ .phys = "ssam/01:0e:01:00:01/input0", ++ }, ++ .ops = { ++ .notify = ssam_kip_sw_notif, ++ .get_state = ssam_kip_get_cover_state, ++ .state_name = ssam_kip_cover_state_name, ++ .state_is_tablet_mode = ssam_kip_cover_state_is_tablet_mode, ++ }, ++ .event = { ++ .reg = SSAM_EVENT_REGISTRY_SAM, ++ .id = { ++ .target_category = SSAM_SSH_TC_KIP, ++ .instance = 0, ++ }, ++ .mask = SSAM_EVENT_MASK_TARGET, ++ }, ++}; ++ ++ ++/* -- SSAM POS tablet switch implementation. -------------------------------- */ ++ ++static bool tablet_mode_in_slate_state = true; ++module_param(tablet_mode_in_slate_state, bool, 0644); ++MODULE_PARM_DESC(tablet_mode_in_slate_state, "Enable tablet mode in slate device posture, default is 'true'"); ++ ++#define SSAM_EVENT_POS_CID_POSTURE_CHANGED 0x03 ++#define SSAM_POS_MAX_SOURCES 4 ++ ++enum ssam_pos_state { ++ SSAM_POS_POSTURE_LID_CLOSED = 0x00, ++ SSAM_POS_POSTURE_LAPTOP = 0x01, ++ SSAM_POS_POSTURE_SLATE = 0x02, ++ SSAM_POS_POSTURE_TABLET = 0x03, ++}; ++ ++struct ssam_sources_list { ++ __le32 count; ++ __le32 id[SSAM_POS_MAX_SOURCES]; ++} __packed; ++ ++static const char *ssam_pos_state_name(struct ssam_tablet_sw *sw, u32 state) ++{ ++ switch (state) { ++ case SSAM_POS_POSTURE_LID_CLOSED: ++ return "closed"; ++ ++ case SSAM_POS_POSTURE_LAPTOP: ++ return "laptop"; ++ ++ case SSAM_POS_POSTURE_SLATE: ++ return "slate"; ++ ++ case SSAM_POS_POSTURE_TABLET: ++ return "tablet"; ++ ++ default: ++ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); ++ return ""; ++ } ++} ++ ++static bool ssam_pos_state_is_tablet_mode(struct ssam_tablet_sw *sw, u32 state) ++{ ++ switch (state) { ++ case SSAM_POS_POSTURE_LAPTOP: ++ case SSAM_POS_POSTURE_LID_CLOSED: ++ return false; ++ ++ case SSAM_POS_POSTURE_SLATE: ++ return tablet_mode_in_slate_state; ++ ++ case SSAM_POS_POSTURE_TABLET: ++ return true; ++ ++ default: ++ dev_warn(&sw->sdev->dev, "unknown device posture: %u\n", state); ++ return true; ++ } ++} ++ ++static int ssam_pos_get_sources_list(struct ssam_tablet_sw *sw, struct ssam_sources_list *sources) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status; ++ ++ rqst.target_category = SSAM_SSH_TC_POS; ++ rqst.target_id = 0x01; ++ rqst.command_id = 0x01; ++ rqst.instance_id = 0x00; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = 0; ++ rqst.payload = NULL; ++ ++ rsp.capacity = sizeof(*sources); ++ rsp.length = 0; ++ rsp.pointer = (u8 *)sources; ++ ++ status = ssam_retry(ssam_request_sync_onstack, sw->sdev->ctrl, &rqst, &rsp, 0); ++ if (status) ++ return status; ++ ++ /* We need at least the 'sources->count' field. */ ++ if (rsp.length < sizeof(__le32)) { ++ dev_err(&sw->sdev->dev, "received source list response is too small\n"); ++ return -EPROTO; ++ } ++ ++ /* Make sure 'sources->count' matches with the response length. */ ++ if (get_unaligned_le32(&sources->count) * sizeof(__le32) + sizeof(__le32) != rsp.length) { ++ dev_err(&sw->sdev->dev, "mismatch between number of sources and response size\n"); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) ++{ ++ struct ssam_sources_list sources = {}; ++ int status; ++ ++ status = ssam_pos_get_sources_list(sw, &sources); ++ if (status) ++ return status; ++ ++ if (sources.count == 0) { ++ dev_err(&sw->sdev->dev, "no posture sources found\n"); ++ return -ENODEV; ++ } ++ ++ /* ++ * We currently don't know what to do with more than one posture ++ * source. At the moment, only one source seems to be used/provided. ++ * The WARN_ON() here should hopefully let us know quickly once there ++ * is a device that provides multiple sources, at which point we can ++ * then try to figure out how to handle them. ++ */ ++ WARN_ON(sources.count > 1); ++ ++ *source_id = get_unaligned_le32(&sources.id[0]); ++ return 0; ++} ++ ++SSAM_DEFINE_SYNC_REQUEST_WR(__ssam_pos_get_posture_for_source, __le32, __le32, { ++ .target_category = SSAM_SSH_TC_POS, ++ .target_id = 0x01, ++ .command_id = 0x02, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_pos_get_posture_for_source(struct ssam_tablet_sw *sw, u32 source_id, u32 *posture) ++{ ++ __le32 source_le = cpu_to_le32(source_id); ++ __le32 rspval_le = 0; ++ int status; ++ ++ status = ssam_retry(__ssam_pos_get_posture_for_source, sw->sdev->ctrl, ++ &source_le, &rspval_le); ++ if (status) ++ return status; ++ ++ *posture = le32_to_cpu(rspval_le); ++ return 0; ++} ++ ++static int ssam_pos_get_posture(struct ssam_tablet_sw *sw, u32 *state) ++{ ++ u32 source_id; ++ int status; ++ ++ status = ssam_pos_get_source(sw, &source_id); ++ if (status) { ++ dev_err(&sw->sdev->dev, "failed to get posture source ID: %d\n", status); ++ return status; ++ } ++ ++ status = ssam_pos_get_posture_for_source(sw, source_id, state); ++ if (status) { ++ dev_err(&sw->sdev->dev, "failed to get posture value for source %u: %d\n", ++ source_id, status); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static u32 ssam_pos_sw_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_tablet_sw *sw = container_of(nf, struct ssam_tablet_sw, notif); ++ ++ if (event->command_id != SSAM_EVENT_POS_CID_POSTURE_CHANGED) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length != sizeof(__le32) * 3) ++ dev_warn(&sw->sdev->dev, "unexpected payload size: %u\n", event->length); ++ ++ schedule_work(&sw->update_work); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static const struct ssam_tablet_sw_desc ssam_pos_sw_desc = { ++ .dev = { ++ .name = "Microsoft Surface POS Tablet Mode Switch", ++ .phys = "ssam/01:26:01:00:01/input0", ++ }, ++ .ops = { ++ .notify = ssam_pos_sw_notif, ++ .get_state = ssam_pos_get_posture, ++ .state_name = ssam_pos_state_name, ++ .state_is_tablet_mode = ssam_pos_state_is_tablet_mode, ++ }, ++ .event = { ++ .reg = SSAM_EVENT_REGISTRY_SAM, ++ .id = { ++ .target_category = SSAM_SSH_TC_POS, ++ .instance = 0, ++ }, ++ .mask = SSAM_EVENT_MASK_TARGET, ++ }, ++}; ++ ++ ++/* -- Driver registration. -------------------------------------------------- */ ++ ++static const struct ssam_device_id ssam_tablet_sw_match[] = { ++ { SSAM_SDEV(KIP, 0x01, 0x00, 0x01), (unsigned long)&ssam_kip_sw_desc }, ++ { SSAM_SDEV(POS, 0x01, 0x00, 0x01), (unsigned long)&ssam_pos_sw_desc }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_tablet_sw_match); ++ ++static struct ssam_device_driver ssam_tablet_sw_driver = { ++ .probe = ssam_tablet_sw_probe, ++ .remove = ssam_tablet_sw_remove, ++ .match_table = ssam_tablet_sw_match, ++ .driver = { ++ .name = "surface_aggregator_tablet_mode_switch", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_tablet_sw_pm_ops, ++ }, ++}; ++module_ssam_device_driver(ssam_tablet_sw_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Tablet mode switch driver for Surface devices using the Surface Aggregator Module"); ++MODULE_LICENSE("GPL"); +-- +2.37.1 + +From b4980b9b4fea5724a15e45584f45b5d3280cc530 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 20:36:41 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet + mode switch on Surface Pro 8 + +Add a KIP subsystem tablet-mode switch device for the Surface Pro 8. +The respective driver for this device provides SW_TABLET_MODE input +events for user-space based on the state of the keyboard cover (e.g. +detached, folded-back, normal/laptop mode). + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624183642.910893-4-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index bf3303f1aa71..8f249df673a4 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -77,6 +77,12 @@ static const struct software_node ssam_node_tmp_pprof = { + .parent = &ssam_node_root, + }; + ++/* Tablet-mode switch via KIP subsystem. */ ++static const struct software_node ssam_node_kip_tablet_switch = { ++ .name = "ssam:01:0e:01:00:01", ++ .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", +@@ -264,11 +270,11 @@ static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, ++ &ssam_node_kip_tablet_switch, + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_iid5, +- /* TODO: Add support for tablet mode switch. */ + NULL, + }; + +-- +2.37.1 + +From 0961eab47ed4dce567fd3ffd8be1573f9569282d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 20:36:42 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for tablet + mode switch on Surface Laptop Studio + +Add a POS subsystem tablet-mode switch device for the Surface Laptop +Studio. The respective driver for this device provides SW_TABLET_MODE +input events for user-space based on the posture of the screen. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624183642.910893-5-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +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 8f249df673a4..f1c5905f1c16 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -191,6 +191,12 @@ static const struct software_node ssam_node_hid_kip_iid5 = { + .parent = &ssam_node_hub_kip, + }; + ++/* Tablet-mode switch via POS subsystem. */ ++static const struct software_node ssam_node_pos_tablet_switch = { ++ .name = "ssam:01:26:01:00:01", ++ .parent = &ssam_node_root, ++}; ++ + /* + * Devices for 5th- and 6th-generations models: + * - Surface Book 2, +@@ -237,6 +243,7 @@ static const struct software_node *ssam_node_group_sls[] = { + &ssam_node_bat_ac, + &ssam_node_bat_main, + &ssam_node_tmp_pprof, ++ &ssam_node_pos_tablet_switch, + &ssam_node_hid_tid1_keyboard, + &ssam_node_hid_tid1_penstash, + &ssam_node_hid_tid1_touchpad, +-- +2.37.1 + +From 725529e652ccbc578cd2f6e453cc86bb3294f2d4 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 22:57:58 +0200 +Subject: [PATCH] platform/surface: aggregator: Move device registry helper + functions to core module + +Move helper functions for client device registration to the core module. +This simplifies addition of future DT/OF support and also allows us to +split out the device hub drivers into their own module. + +At the same time, also improve device node validation a bit by not +silently skipping devices with invalid device UID specifiers. Further, +ensure proper lifetime management for the firmware/software nodes +associated with the added devices. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624205800.1355621-2-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/bus.c | 149 ++++++++++++++++-- + .../surface/surface_aggregator_registry.c | 75 +-------- + include/linux/surface_aggregator/device.h | 52 ++++++ + 3 files changed, 187 insertions(+), 89 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c +index abbbb5b08b07..e0b0381a2834 100644 +--- a/drivers/platform/surface/aggregator/bus.c ++++ b/drivers/platform/surface/aggregator/bus.c +@@ -6,6 +6,7 @@ + */ + + #include ++#include + #include + + #include +@@ -14,6 +15,9 @@ + #include "bus.h" + #include "controller.h" + ++ ++/* -- Device and bus functions. --------------------------------------------- */ ++ + static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, + char *buf) + { +@@ -46,6 +50,7 @@ static void ssam_device_release(struct device *dev) + struct ssam_device *sdev = to_ssam_device(dev); + + ssam_controller_put(sdev->ctrl); ++ fwnode_handle_put(sdev->dev.fwnode); + kfree(sdev); + } + +@@ -363,6 +368,134 @@ void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) + } + EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); + ++ ++/* -- Bus registration. ----------------------------------------------------- */ ++ ++/** ++ * ssam_bus_register() - Register and set-up the SSAM client device bus. ++ */ ++int ssam_bus_register(void) ++{ ++ return bus_register(&ssam_bus_type); ++} ++ ++/** ++ * ssam_bus_unregister() - Unregister the SSAM client device bus. ++ */ ++void ssam_bus_unregister(void) ++{ ++ return bus_unregister(&ssam_bus_type); ++} ++ ++ ++/* -- Helpers for controller and hub devices. ------------------------------- */ ++ ++static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid) ++{ ++ u8 d, tc, tid, iid, fn; ++ int n; ++ ++ n = sscanf(str, "%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_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid) ++{ ++ const char *str = fwnode_get_name(node); ++ ++ /* ++ * To simplify definitions of firmware nodes, we set the device name ++ * based on the UID of the device, prefixed with "ssam:". ++ */ ++ if (strncmp(str, "ssam:", strlen("ssam:")) != 0) ++ return -ENODEV; ++ ++ str += strlen("ssam:"); ++ return ssam_device_uid_from_string(str, uid); ++} ++ ++static int ssam_add_client_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_get_uid_for_node(node, &uid); ++ if (status) ++ return status; ++ ++ sdev = ssam_device_alloc(ctrl, uid); ++ if (!sdev) ++ return -ENOMEM; ++ ++ sdev->dev.parent = parent; ++ sdev->dev.fwnode = fwnode_handle_get(node); ++ ++ status = ssam_device_add(sdev); ++ if (status) ++ ssam_device_put(sdev); ++ ++ return status; ++} ++ ++/** ++ * __ssam_register_clients() - Register client devices defined under the ++ * given firmware node as children of the given device. ++ * @parent: The parent device under which clients should be registered. ++ * @ctrl: The controller with which client should be registered. ++ * @node: The firmware node holding definitions of the devices to be added. ++ * ++ * Register all clients that have been defined as children of the given root ++ * firmware node as children of the given parent device. The respective child ++ * firmware nodes will be associated with the correspondingly created child ++ * devices. ++ * ++ * The given controller will be used to instantiate the new devices. See ++ * ssam_device_add() for details. ++ * ++ * Note that, generally, the use of either ssam_device_register_clients() or ++ * ssam_register_clients() should be preferred as they directly use the ++ * firmware node and/or controller associated with the given device. This ++ * function is only intended for use when different device specifications (e.g. ++ * ACPI and firmware nodes) need to be combined (as is done in the platform hub ++ * of the device registry). ++ * ++ * Return: Returns zero on success, nonzero on failure. ++ */ ++int __ssam_register_clients(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 -ENODEV, the node does not specify any SSAM ++ * device, so ignore it and continue with the next one. ++ */ ++ status = ssam_add_client_device(parent, ctrl, child); ++ if (status && status != -ENODEV) ++ goto err; ++ } ++ ++ return 0; ++err: ++ ssam_remove_clients(parent); ++ return status; ++} ++EXPORT_SYMBOL_GPL(__ssam_register_clients); ++ + static int ssam_remove_device(struct device *dev, void *_data) + { + struct ssam_device *sdev = to_ssam_device(dev); +@@ -387,19 +520,3 @@ void ssam_remove_clients(struct device *dev) + device_for_each_child_reverse(dev, NULL, ssam_remove_device); + } + EXPORT_SYMBOL_GPL(ssam_remove_clients); +- +-/** +- * ssam_bus_register() - Register and set-up the SSAM client device bus. +- */ +-int ssam_bus_register(void) +-{ +- return bus_register(&ssam_bus_type); +-} +- +-/** +- * ssam_bus_unregister() - Unregister the SSAM client device bus. +- */ +-void ssam_bus_unregister(void) +-{ +- return bus_unregister(&ssam_bus_type); +-} +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index f1c5905f1c16..c680792a037e 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -286,76 +286,6 @@ static const struct software_node *ssam_node_group_sp8[] = { + }; + + +-/* -- 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_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_register_clients(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_remove_clients(parent); +- return status; +-} +- +- + /* -- SSAM generic subsystem hub driver framework. -------------------------- */ + + enum ssam_hub_state { +@@ -385,7 +315,6 @@ struct ssam_hub { + static void ssam_hub_update_workfn(struct work_struct *work) + { + struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); +- struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev); + enum ssam_hub_state state; + int status = 0; + +@@ -425,7 +354,7 @@ static void ssam_hub_update_workfn(struct work_struct *work) + hub->state = state; + + if (hub->state == SSAM_HUB_CONNECTED) +- status = ssam_hub_register_clients(&hub->sdev->dev, hub->sdev->ctrl, node); ++ status = ssam_device_register_clients(hub->sdev); + else + ssam_remove_clients(&hub->sdev->dev); + +@@ -769,7 +698,7 @@ static int ssam_platform_hub_probe(struct platform_device *pdev) + + set_secondary_fwnode(&pdev->dev, root); + +- status = ssam_hub_register_clients(&pdev->dev, ctrl, root); ++ status = __ssam_register_clients(&pdev->dev, ctrl, root); + if (status) { + set_secondary_fwnode(&pdev->dev, NULL); + software_node_unregister_node_group(nodes); +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +index 6cf7e80312d5..46c45d1b6368 100644 +--- a/include/linux/surface_aggregator/device.h ++++ b/include/linux/surface_aggregator/device.h +@@ -15,6 +15,7 @@ + + #include + #include ++#include + #include + + #include +@@ -375,11 +376,62 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d); + /* -- Helpers for controller and hub devices. ------------------------------- */ + + #ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++ ++int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node); + void ssam_remove_clients(struct device *dev); ++ + #else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static inline int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl, ++ struct fwnode_handle *node) ++{ ++ return 0; ++} ++ + static inline void ssam_remove_clients(struct device *dev) {} ++ + #endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ + ++/** ++ * ssam_register_clients() - Register all client devices defined under the ++ * given parent device. ++ * @dev: The parent device under which clients should be registered. ++ * @ctrl: The controller with which client should be registered. ++ * ++ * Register all clients that have via firmware nodes been defined as children ++ * of the given (parent) device. The respective child firmware nodes will be ++ * associated with the correspondingly created child devices. ++ * ++ * The given controller will be used to instantiate the new devices. See ++ * ssam_device_add() for details. ++ * ++ * Return: Returns zero on success, nonzero on failure. ++ */ ++static inline int ssam_register_clients(struct device *dev, struct ssam_controller *ctrl) ++{ ++ return __ssam_register_clients(dev, ctrl, dev_fwnode(dev)); ++} ++ ++/** ++ * ssam_device_register_clients() - Register all client devices defined under ++ * the given SSAM parent device. ++ * @sdev: The parent device under which clients should be registered. ++ * ++ * Register all clients that have via firmware nodes been defined as children ++ * of the given (parent) device. The respective child firmware nodes will be ++ * associated with the correspondingly created child devices. ++ * ++ * The controller used by the parent device will be used to instantiate the new ++ * devices. See ssam_device_add() for details. ++ * ++ * Return: Returns zero on success, nonzero on failure. ++ */ ++static inline int ssam_device_register_clients(struct ssam_device *sdev) ++{ ++ return ssam_register_clients(&sdev->dev, sdev->ctrl); ++} ++ + + /* -- Helpers for client-device requests. ----------------------------------- */ + +-- +2.37.1 + +From d3e38dda072f2cfa87b8074b4efe1dfc006345c9 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 22:57:59 +0200 +Subject: [PATCH] platform/surface: aggregator: Move subsystem hub drivers to + their own module + +Split out subsystem device hub drivers into their own module. This +allows us to load the hub drivers separately from the registry, which +will help future DT/OF support. + +While doing so, also remove a small bit of code duplication. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624205800.1355621-3-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + MAINTAINERS | 6 + + drivers/platform/surface/Kconfig | 35 +- + drivers/platform/surface/Makefile | 1 + + .../platform/surface/surface_aggregator_hub.c | 371 ++++++++++++++++++ + .../surface/surface_aggregator_registry.c | 371 +----------------- + 5 files changed, 410 insertions(+), 374 deletions(-) + create mode 100644 drivers/platform/surface/surface_aggregator_hub.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index 11b84de77ab8..a29042434d9e 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13381,6 +13381,12 @@ F: include/linux/surface_acpi_notify.h + F: include/linux/surface_aggregator/ + F: include/uapi/linux/surface_aggregator/ + ++MICROSOFT SURFACE SYSTEM AGGREGATOR HUB DRIVER ++M: Maximilian Luz ++L: platform-driver-x86@vger.kernel.org ++S: Maintained ++F: drivers/platform/surface/surface_aggregator_hub.c ++ + MICROTEK X6 SCANNER + M: Oliver Neukum + S: Maintained +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b152e930cc84..b629e82af97c 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -72,18 +72,45 @@ config SURFACE_AGGREGATOR_CDEV + The provided interface is intended for debugging and development only, + and should not be used otherwise. + ++config SURFACE_AGGREGATOR_HUB ++ tristate "Surface System Aggregator Module Subsystem Device Hubs" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_AGGREGATOR_BUS ++ help ++ Device-hub drivers for Surface System Aggregator Module (SSAM) subsystem ++ devices. ++ ++ Provides subsystem hub drivers which manage client devices on various ++ SSAM subsystems. In some subsystems, notably the BAS subsystem managing ++ devices contained in the base of the Surface Book 3 and the KIP subsystem ++ managing type-cover devices in the Surface Pro 8 and Surface Pro X, ++ devices can be (hot-)removed. Hub devices and drivers are required to ++ manage these subdevices. ++ ++ Devices managed via these hubs are: ++ - Battery/AC devices (Surface Book 3). ++ - HID input devices (7th-generation and later models with detachable ++ input devices). ++ ++ 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 mentioned above will not be instantiated ++ and thus any functionality provided by them will be missing, even when ++ drivers for these devices are present. This module only provides the ++ respective subsystem hubs. Both drivers and device specification (e.g. ++ via the Surface Aggregator Registry) for these devices still need to be ++ selected via other options. ++ + 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. ++ Device-registry 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. ++ Such devices are instead provided and managed via this registry. + + Devices provided via this registry are: + - Platform profile (performance-/cooling-mode) device (5th- and later +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 18b27898543e..53344330939b 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -9,6 +9,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_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o +diff --git a/drivers/platform/surface/surface_aggregator_hub.c b/drivers/platform/surface/surface_aggregator_hub.c +new file mode 100644 +index 000000000000..43061514be38 +--- /dev/null ++++ b/drivers/platform/surface/surface_aggregator_hub.c +@@ -0,0 +1,371 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Driver for Surface System Aggregator Module (SSAM) subsystem device hubs. ++ * ++ * Provides a driver for SSAM subsystems device hubs. This driver performs ++ * instantiation of the devices managed by said hubs and takes care of ++ * (hot-)removal. ++ * ++ * Copyright (C) 2020-2022 Maximilian Luz ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- SSAM generic subsystem hub driver framework. -------------------------- */ ++ ++enum ssam_hub_state { ++ SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ ++ SSAM_HUB_CONNECTED, ++ SSAM_HUB_DISCONNECTED, ++}; ++ ++enum ssam_hub_flags { ++ SSAM_HUB_HOT_REMOVED, ++}; ++ ++struct ssam_hub; ++ ++struct ssam_hub_ops { ++ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); ++}; ++ ++struct ssam_hub { ++ struct ssam_device *sdev; ++ ++ enum ssam_hub_state state; ++ unsigned long flags; ++ ++ struct delayed_work update_work; ++ unsigned long connect_delay; ++ ++ struct ssam_event_notifier notif; ++ struct ssam_hub_ops ops; ++}; ++ ++struct ssam_hub_desc { ++ struct { ++ struct ssam_event_registry reg; ++ struct ssam_event_id id; ++ enum ssam_event_mask mask; ++ } event; ++ ++ struct { ++ u32 (*notify)(struct ssam_event_notifier *nf, const struct ssam_event *event); ++ int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); ++ } ops; ++ ++ unsigned long connect_delay_ms; ++}; ++ ++static void ssam_hub_update_workfn(struct work_struct *work) ++{ ++ struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); ++ enum ssam_hub_state state; ++ int status = 0; ++ ++ status = hub->ops.get_state(hub, &state); ++ if (status) ++ return; ++ ++ /* ++ * There is a small possibility that hub devices were hot-removed and ++ * re-added before we were able to remove them here. In that case, both ++ * the state returned by get_state() and the state of the hub will ++ * equal SSAM_HUB_CONNECTED and we would bail early below, which would ++ * leave child devices without proper (re-)initialization and the ++ * hot-remove flag set. ++ * ++ * Therefore, we check whether devices have been hot-removed via an ++ * additional flag on the hub and, in this case, override the returned ++ * hub state. In case of a missed disconnect (i.e. get_state returned ++ * "connected"), we further need to re-schedule this work (with the ++ * appropriate delay) as the actual connect work submission might have ++ * been merged with this one. ++ * ++ * This then leads to one of two cases: Either we submit an unnecessary ++ * work item (which will get ignored via either the queue or the state ++ * checks) or, in the unlikely case that the work is actually required, ++ * double the normal connect delay. ++ */ ++ if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { ++ if (state == SSAM_HUB_CONNECTED) ++ schedule_delayed_work(&hub->update_work, hub->connect_delay); ++ ++ state = SSAM_HUB_DISCONNECTED; ++ } ++ ++ if (hub->state == state) ++ return; ++ hub->state = state; ++ ++ if (hub->state == SSAM_HUB_CONNECTED) ++ status = ssam_device_register_clients(hub->sdev); ++ else ++ ssam_remove_clients(&hub->sdev->dev); ++ ++ if (status) ++ dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); ++} ++ ++static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (is_ssam_device(dev)) ++ ssam_device_mark_hot_removed(sdev); ++ ++ return 0; ++} ++ ++static void ssam_hub_update(struct ssam_hub *hub, bool connected) ++{ ++ unsigned long delay; ++ ++ /* Mark devices as hot-removed before we remove any. */ ++ if (!connected) { ++ set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); ++ device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); ++ } ++ ++ /* ++ * Delay update when the base/keyboard cover is being connected to give ++ * devices/EC some time to set up. ++ */ ++ delay = connected ? hub->connect_delay : 0; ++ ++ schedule_delayed_work(&hub->update_work, delay); ++} ++ ++static int __maybe_unused ssam_hub_resume(struct device *dev) ++{ ++ struct ssam_hub *hub = dev_get_drvdata(dev); ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++} ++static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); ++ ++static int ssam_hub_probe(struct ssam_device *sdev) ++{ ++ const struct ssam_hub_desc *desc; ++ struct ssam_hub *hub; ++ int status; ++ ++ desc = ssam_device_get_match_data(sdev); ++ if (!desc) { ++ WARN(1, "no driver match data specified"); ++ return -EINVAL; ++ } ++ ++ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); ++ if (!hub) ++ return -ENOMEM; ++ ++ hub->sdev = sdev; ++ hub->state = SSAM_HUB_UNINITIALIZED; ++ ++ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ ++ hub->notif.base.fn = desc->ops.notify; ++ hub->notif.event.reg = desc->event.reg; ++ hub->notif.event.id = desc->event.id; ++ hub->notif.event.mask = desc->event.mask; ++ hub->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ hub->connect_delay = msecs_to_jiffies(desc->connect_delay_ms); ++ hub->ops.get_state = desc->ops.get_state; ++ ++ INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); ++ ++ ssam_device_set_drvdata(sdev, hub); ++ ++ status = ssam_device_notifier_register(sdev, &hub->notif); ++ if (status) ++ return status; ++ ++ schedule_delayed_work(&hub->update_work, 0); ++ return 0; ++} ++ ++static void ssam_hub_remove(struct ssam_device *sdev) ++{ ++ struct ssam_hub *hub = ssam_device_get_drvdata(sdev); ++ ++ ssam_device_notifier_unregister(sdev, &hub->notif); ++ cancel_delayed_work_sync(&hub->update_work); ++ ssam_remove_clients(&sdev->dev); ++} ++ ++ ++/* -- SSAM base-subsystem 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 2500 ++ ++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_hub *hub, enum ssam_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_HUB_CONNECTED; ++ else ++ *state = SSAM_HUB_DISCONNECTED; ++ ++ return 0; ++} ++ ++static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); ++ ++ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) ++ return 0; ++ ++ if (event->length < 1) { ++ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); ++ return 0; ++ } ++ ++ ssam_hub_update(hub, event->data[0]); ++ ++ /* ++ * 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 const struct ssam_hub_desc base_hub = { ++ .event = { ++ .reg = SSAM_EVENT_REGISTRY_SAM, ++ .id = { ++ .target_category = SSAM_SSH_TC_BAS, ++ .instance = 0, ++ }, ++ .mask = SSAM_EVENT_MASK_NONE, ++ }, ++ .ops = { ++ .notify = ssam_base_hub_notif, ++ .get_state = ssam_base_hub_query_state, ++ }, ++ .connect_delay_ms = SSAM_BASE_UPDATE_CONNECT_DELAY, ++}; ++ ++ ++/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ ++ ++/* ++ * Some devices may need a bit of time to be fully usable after being ++ * (re-)connected. This delay has been determined via experimentation. ++ */ ++#define SSAM_KIP_UPDATE_CONNECT_DELAY 250 ++ ++#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c ++ ++SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_query_state, u8, { ++ .target_category = SSAM_SSH_TC_KIP, ++ .target_id = 0x01, ++ .command_id = 0x2c, ++ .instance_id = 0x00, ++}); ++ ++static int ssam_kip_hub_query_state(struct ssam_hub *hub, enum ssam_hub_state *state) ++{ ++ int status; ++ u8 connected; ++ ++ status = ssam_retry(__ssam_kip_query_state, hub->sdev->ctrl, &connected); ++ if (status < 0) { ++ dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); ++ return status; ++ } ++ ++ *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; ++ return 0; ++} ++ ++static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); ++ ++ if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) ++ return 0; /* Return "unhandled". */ ++ ++ if (event->length < 1) { ++ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); ++ return 0; ++ } ++ ++ ssam_hub_update(hub, event->data[0]); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static const struct ssam_hub_desc kip_hub = { ++ .event = { ++ .reg = SSAM_EVENT_REGISTRY_SAM, ++ .id = { ++ .target_category = SSAM_SSH_TC_KIP, ++ .instance = 0, ++ }, ++ .mask = SSAM_EVENT_MASK_TARGET, ++ }, ++ .ops = { ++ .notify = ssam_kip_hub_notif, ++ .get_state = ssam_kip_hub_query_state, ++ }, ++ .connect_delay_ms = SSAM_KIP_UPDATE_CONNECT_DELAY, ++}; ++ ++ ++/* -- Driver registration. -------------------------------------------------- */ ++ ++static const struct ssam_device_id ssam_hub_match[] = { ++ { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00), (unsigned long)&kip_hub }, ++ { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00), (unsigned long)&base_hub }, ++ { } ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_hub_match); ++ ++static struct ssam_device_driver ssam_subsystem_hub_driver = { ++ .probe = ssam_hub_probe, ++ .remove = ssam_hub_remove, ++ .match_table = ssam_hub_match, ++ .driver = { ++ .name = "surface_aggregator_subsystem_hub", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ .pm = &ssam_hub_pm_ops, ++ }, ++}; ++module_ssam_device_driver(ssam_subsystem_hub_driver); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Subsystem device hub driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index c680792a037e..0cbb7f3a6b2d 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -11,14 +11,11 @@ + + #include + #include +-#include + #include + #include + #include + #include +-#include + +-#include + #include + + +@@ -286,335 +283,6 @@ static const struct software_node *ssam_node_group_sp8[] = { + }; + + +-/* -- SSAM generic subsystem hub driver framework. -------------------------- */ +- +-enum ssam_hub_state { +- SSAM_HUB_UNINITIALIZED, /* Only set during initialization. */ +- SSAM_HUB_CONNECTED, +- SSAM_HUB_DISCONNECTED, +-}; +- +-enum ssam_hub_flags { +- SSAM_HUB_HOT_REMOVED, +-}; +- +-struct ssam_hub { +- struct ssam_device *sdev; +- +- enum ssam_hub_state state; +- unsigned long flags; +- +- struct delayed_work update_work; +- unsigned long connect_delay; +- +- struct ssam_event_notifier notif; +- +- int (*get_state)(struct ssam_hub *hub, enum ssam_hub_state *state); +-}; +- +-static void ssam_hub_update_workfn(struct work_struct *work) +-{ +- struct ssam_hub *hub = container_of(work, struct ssam_hub, update_work.work); +- enum ssam_hub_state state; +- int status = 0; +- +- status = hub->get_state(hub, &state); +- if (status) +- return; +- +- /* +- * There is a small possibility that hub devices were hot-removed and +- * re-added before we were able to remove them here. In that case, both +- * the state returned by get_state() and the state of the hub will +- * equal SSAM_HUB_CONNECTED and we would bail early below, which would +- * leave child devices without proper (re-)initialization and the +- * hot-remove flag set. +- * +- * Therefore, we check whether devices have been hot-removed via an +- * additional flag on the hub and, in this case, override the returned +- * hub state. In case of a missed disconnect (i.e. get_state returned +- * "connected"), we further need to re-schedule this work (with the +- * appropriate delay) as the actual connect work submission might have +- * been merged with this one. +- * +- * This then leads to one of two cases: Either we submit an unnecessary +- * work item (which will get ignored via either the queue or the state +- * checks) or, in the unlikely case that the work is actually required, +- * double the normal connect delay. +- */ +- if (test_and_clear_bit(SSAM_HUB_HOT_REMOVED, &hub->flags)) { +- if (state == SSAM_HUB_CONNECTED) +- schedule_delayed_work(&hub->update_work, hub->connect_delay); +- +- state = SSAM_HUB_DISCONNECTED; +- } +- +- if (hub->state == state) +- return; +- hub->state = state; +- +- if (hub->state == SSAM_HUB_CONNECTED) +- status = ssam_device_register_clients(hub->sdev); +- else +- ssam_remove_clients(&hub->sdev->dev); +- +- if (status) +- dev_err(&hub->sdev->dev, "failed to update hub child devices: %d\n", status); +-} +- +-static int ssam_hub_mark_hot_removed(struct device *dev, void *_data) +-{ +- struct ssam_device *sdev = to_ssam_device(dev); +- +- if (is_ssam_device(dev)) +- ssam_device_mark_hot_removed(sdev); +- +- return 0; +-} +- +-static void ssam_hub_update(struct ssam_hub *hub, bool connected) +-{ +- unsigned long delay; +- +- /* Mark devices as hot-removed before we remove any. */ +- if (!connected) { +- set_bit(SSAM_HUB_HOT_REMOVED, &hub->flags); +- device_for_each_child_reverse(&hub->sdev->dev, NULL, ssam_hub_mark_hot_removed); +- } +- +- /* +- * Delay update when the base/keyboard cover is being connected to give +- * devices/EC some time to set up. +- */ +- delay = connected ? hub->connect_delay : 0; +- +- schedule_delayed_work(&hub->update_work, delay); +-} +- +-static int __maybe_unused ssam_hub_resume(struct device *dev) +-{ +- struct ssam_hub *hub = dev_get_drvdata(dev); +- +- schedule_delayed_work(&hub->update_work, 0); +- return 0; +-} +-static SIMPLE_DEV_PM_OPS(ssam_hub_pm_ops, NULL, ssam_hub_resume); +- +-static int ssam_hub_setup(struct ssam_device *sdev, struct ssam_hub *hub) +-{ +- int status; +- +- hub->sdev = sdev; +- hub->state = SSAM_HUB_UNINITIALIZED; +- +- INIT_DELAYED_WORK(&hub->update_work, ssam_hub_update_workfn); +- +- ssam_device_set_drvdata(sdev, hub); +- +- status = ssam_device_notifier_register(sdev, &hub->notif); +- if (status) +- return status; +- +- schedule_delayed_work(&hub->update_work, 0); +- return 0; +-} +- +-static void ssam_hub_remove(struct ssam_device *sdev) +-{ +- struct ssam_hub *hub = ssam_device_get_drvdata(sdev); +- +- ssam_device_notifier_unregister(sdev, &hub->notif); +- cancel_delayed_work_sync(&hub->update_work); +- ssam_remove_clients(&sdev->dev); +-} +- +- +-/* -- 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) +- +-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_hub *hub, enum ssam_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_HUB_CONNECTED; +- else +- *state = SSAM_HUB_DISCONNECTED; +- +- return 0; +-} +- +-static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +-{ +- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); +- +- if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION) +- return 0; +- +- if (event->length < 1) { +- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); +- return 0; +- } +- +- ssam_hub_update(hub, event->data[0]); +- +- /* +- * 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 ssam_base_hub_probe(struct ssam_device *sdev) +-{ +- struct ssam_hub *hub; +- +- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); +- if (!hub) +- return -ENOMEM; +- +- 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; +- +- hub->connect_delay = SSAM_BASE_UPDATE_CONNECT_DELAY; +- hub->get_state = ssam_base_hub_query_state; +- +- return ssam_hub_setup(sdev, hub); +-} +- +-static const struct ssam_device_id ssam_base_hub_match[] = { +- { SSAM_VDEV(HUB, 0x02, SSAM_SSH_TC_BAS, 0x00) }, +- { }, +-}; +- +-static struct ssam_device_driver ssam_base_hub_driver = { +- .probe = ssam_base_hub_probe, +- .remove = ssam_hub_remove, +- .match_table = ssam_base_hub_match, +- .driver = { +- .name = "surface_aggregator_base_hub", +- .probe_type = PROBE_PREFER_ASYNCHRONOUS, +- .pm = &ssam_hub_pm_ops, +- }, +-}; +- +- +-/* -- SSAM KIP-subsystem hub driver. ---------------------------------------- */ +- +-/* +- * Some devices may need a bit of time to be fully usable after being +- * (re-)connected. This delay has been determined via experimentation. +- */ +-#define SSAM_KIP_UPDATE_CONNECT_DELAY msecs_to_jiffies(250) +- +-#define SSAM_EVENT_KIP_CID_CONNECTION 0x2c +- +-SSAM_DEFINE_SYNC_REQUEST_R(__ssam_kip_get_connection_state, u8, { +- .target_category = SSAM_SSH_TC_KIP, +- .target_id = 0x01, +- .command_id = 0x2c, +- .instance_id = 0x00, +-}); +- +-static int ssam_kip_get_connection_state(struct ssam_hub *hub, enum ssam_hub_state *state) +-{ +- int status; +- u8 connected; +- +- status = ssam_retry(__ssam_kip_get_connection_state, hub->sdev->ctrl, &connected); +- if (status < 0) { +- dev_err(&hub->sdev->dev, "failed to query KIP connection state: %d\n", status); +- return status; +- } +- +- *state = connected ? SSAM_HUB_CONNECTED : SSAM_HUB_DISCONNECTED; +- return 0; +-} +- +-static u32 ssam_kip_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event) +-{ +- struct ssam_hub *hub = container_of(nf, struct ssam_hub, notif); +- +- if (event->command_id != SSAM_EVENT_KIP_CID_CONNECTION) +- return 0; /* Return "unhandled". */ +- +- if (event->length < 1) { +- dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length); +- return 0; +- } +- +- ssam_hub_update(hub, event->data[0]); +- return SSAM_NOTIF_HANDLED; +-} +- +-static int ssam_kip_hub_probe(struct ssam_device *sdev) +-{ +- struct ssam_hub *hub; +- +- hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); +- if (!hub) +- return -ENOMEM; +- +- hub->notif.base.priority = INT_MAX; /* This notifier should run first. */ +- hub->notif.base.fn = ssam_kip_hub_notif; +- hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; +- hub->notif.event.id.target_category = SSAM_SSH_TC_KIP, +- hub->notif.event.id.instance = 0, +- hub->notif.event.mask = SSAM_EVENT_MASK_TARGET; +- hub->notif.event.flags = SSAM_EVENT_SEQUENCED; +- +- hub->connect_delay = SSAM_KIP_UPDATE_CONNECT_DELAY; +- hub->get_state = ssam_kip_get_connection_state; +- +- return ssam_hub_setup(sdev, hub); +-} +- +-static const struct ssam_device_id ssam_kip_hub_match[] = { +- { SSAM_VDEV(HUB, 0x01, SSAM_SSH_TC_KIP, 0x00) }, +- { }, +-}; +- +-static struct ssam_device_driver ssam_kip_hub_driver = { +- .probe = ssam_kip_hub_probe, +- .remove = ssam_hub_remove, +- .match_table = ssam_kip_hub_match, +- .driver = { +- .name = "surface_kip_hub", +- .probe_type = PROBE_PREFER_ASYNCHRONOUS, +- .pm = &ssam_hub_pm_ops, +- }, +-}; +- +- + /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ + + static const struct acpi_device_id ssam_platform_hub_match[] = { +@@ -727,44 +395,7 @@ static struct platform_driver ssam_platform_hub_driver = { + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + }; +- +- +-/* -- Module initialization. ------------------------------------------------ */ +- +-static int __init ssam_device_hub_init(void) +-{ +- int status; +- +- status = platform_driver_register(&ssam_platform_hub_driver); +- if (status) +- goto err_platform; +- +- status = ssam_device_driver_register(&ssam_base_hub_driver); +- if (status) +- goto err_base; +- +- status = ssam_device_driver_register(&ssam_kip_hub_driver); +- if (status) +- goto err_kip; +- +- return 0; +- +-err_kip: +- ssam_device_driver_unregister(&ssam_base_hub_driver); +-err_base: +- platform_driver_unregister(&ssam_platform_hub_driver); +-err_platform: +- return status; +-} +-module_init(ssam_device_hub_init); +- +-static void __exit ssam_device_hub_exit(void) +-{ +- ssam_device_driver_unregister(&ssam_kip_hub_driver); +- ssam_device_driver_unregister(&ssam_base_hub_driver); +- platform_driver_unregister(&ssam_platform_hub_driver); +-} +-module_exit(ssam_device_hub_exit); ++module_platform_driver(ssam_platform_hub_driver); + + MODULE_AUTHOR("Maximilian Luz "); + MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); +-- +2.37.1 + +From 114e28179e83a80f1128056b7d91cc151b24f6ad Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 24 Jun 2022 22:58:00 +0200 +Subject: [PATCH] platform/surface: Update copyright year of various drivers + +Update the copyright of various Surface drivers to the current year. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220624205800.1355621-4-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/aggregator/Kconfig | 2 +- + drivers/platform/surface/aggregator/Makefile | 2 +- + drivers/platform/surface/aggregator/bus.c | 2 +- + drivers/platform/surface/aggregator/bus.h | 2 +- + drivers/platform/surface/aggregator/controller.c | 2 +- + drivers/platform/surface/aggregator/controller.h | 2 +- + drivers/platform/surface/aggregator/core.c | 2 +- + drivers/platform/surface/aggregator/ssh_msgb.h | 2 +- + drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +- + drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +- + drivers/platform/surface/aggregator/ssh_parser.c | 2 +- + drivers/platform/surface/aggregator/ssh_parser.h | 2 +- + drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +- + drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +- + drivers/platform/surface/aggregator/trace.h | 2 +- + drivers/platform/surface/surface_acpi_notify.c | 2 +- + drivers/platform/surface/surface_aggregator_cdev.c | 2 +- + drivers/platform/surface/surface_aggregator_registry.c | 2 +- + drivers/platform/surface/surface_dtx.c | 2 +- + drivers/platform/surface/surface_gpe.c | 2 +- + drivers/platform/surface/surface_hotplug.c | 2 +- + drivers/platform/surface/surface_platform_profile.c | 2 +- + 22 files changed, 22 insertions(+), 22 deletions(-) + +diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig +index cab020324256..c114f9dd5fe1 100644 +--- a/drivers/platform/surface/aggregator/Kconfig ++++ b/drivers/platform/surface/aggregator/Kconfig +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0+ +-# Copyright (C) 2019-2021 Maximilian Luz ++# Copyright (C) 2019-2022 Maximilian Luz + + menuconfig SURFACE_AGGREGATOR + tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" +diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile +index c0d550eda5cd..fdf664a217f9 100644 +--- a/drivers/platform/surface/aggregator/Makefile ++++ b/drivers/platform/surface/aggregator/Makefile +@@ -1,5 +1,5 @@ + # SPDX-License-Identifier: GPL-2.0+ +-# Copyright (C) 2019-2021 Maximilian Luz ++# Copyright (C) 2019-2022 Maximilian Luz + + # For include/trace/define_trace.h to include trace.h + CFLAGS_core.o = -I$(src) +diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c +index e0b0381a2834..de539938896e 100644 +--- a/drivers/platform/surface/aggregator/bus.c ++++ b/drivers/platform/surface/aggregator/bus.c +@@ -2,7 +2,7 @@ + /* + * Surface System Aggregator Module bus and device integration. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h +index 6964ee84e79c..5b4dbf21906c 100644 +--- a/drivers/platform/surface/aggregator/bus.h ++++ b/drivers/platform/surface/aggregator/bus.h +@@ -2,7 +2,7 @@ + /* + * Surface System Aggregator Module bus and device integration. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_BUS_H +diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c +index 6de834b52b63..43e765199137 100644 +--- a/drivers/platform/surface/aggregator/controller.c ++++ b/drivers/platform/surface/aggregator/controller.c +@@ -2,7 +2,7 @@ + /* + * Main SSAM/SSH controller structure and functionality. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h +index a0963c3562ff..f0d987abc51e 100644 +--- a/drivers/platform/surface/aggregator/controller.h ++++ b/drivers/platform/surface/aggregator/controller.h +@@ -2,7 +2,7 @@ + /* + * Main SSAM/SSH controller structure and functionality. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_CONTROLLER_H +diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c +index a62c5dfe42d6..1a6373dea109 100644 +--- a/drivers/platform/surface/aggregator/core.c ++++ b/drivers/platform/surface/aggregator/core.c +@@ -7,7 +7,7 @@ + * Handles communication via requests as well as enabling, disabling, and + * relaying of events. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h +index e562958ffdf0..f3ecad92eefd 100644 +--- a/drivers/platform/surface/aggregator/ssh_msgb.h ++++ b/drivers/platform/surface/aggregator/ssh_msgb.h +@@ -2,7 +2,7 @@ + /* + * SSH message builder functions. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H +diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c +index 8a4451c1ffe5..6748fe4ac5d5 100644 +--- a/drivers/platform/surface/aggregator/ssh_packet_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c +@@ -2,7 +2,7 @@ + /* + * SSH packet transport layer. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h +index 2eb329f0b91a..64633522f971 100644 +--- a/drivers/platform/surface/aggregator/ssh_packet_layer.h ++++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h +@@ -2,7 +2,7 @@ + /* + * SSH packet transport layer. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H +diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c +index b77912f8f13b..a6f668694365 100644 +--- a/drivers/platform/surface/aggregator/ssh_parser.c ++++ b/drivers/platform/surface/aggregator/ssh_parser.c +@@ -2,7 +2,7 @@ + /* + * SSH message parser. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h +index 3bd6e180fd16..801d8fa69fb5 100644 +--- a/drivers/platform/surface/aggregator/ssh_parser.h ++++ b/drivers/platform/surface/aggregator/ssh_parser.h +@@ -2,7 +2,7 @@ + /* + * SSH message parser. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H +diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c +index 790f7f0eee98..f5565570f16c 100644 +--- a/drivers/platform/surface/aggregator/ssh_request_layer.c ++++ b/drivers/platform/surface/aggregator/ssh_request_layer.c +@@ -2,7 +2,7 @@ + /* + * SSH request transport layer. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h +index 9c3cbae2d4bd..4e387a031351 100644 +--- a/drivers/platform/surface/aggregator/ssh_request_layer.h ++++ b/drivers/platform/surface/aggregator/ssh_request_layer.h +@@ -2,7 +2,7 @@ + /* + * SSH request transport layer. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H +diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h +index cc9e73fbc18e..2a2c17771d01 100644 +--- a/drivers/platform/surface/aggregator/trace.h ++++ b/drivers/platform/surface/aggregator/trace.h +@@ -2,7 +2,7 @@ + /* + * Trace points for SSAM/SSH. + * +- * Copyright (C) 2020-2021 Maximilian Luz ++ * Copyright (C) 2020-2022 Maximilian Luz + */ + + #undef TRACE_SYSTEM +diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c +index c0e12f0b9b79..44e317970557 100644 +--- a/drivers/platform/surface/surface_acpi_notify.c ++++ b/drivers/platform/surface/surface_acpi_notify.c +@@ -8,7 +8,7 @@ + * notifications sent from ACPI via the SAN interface by providing them to any + * registered external driver. + * +- * Copyright (C) 2019-2020 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c +index 30fb50fde450..492c82e69182 100644 +--- a/drivers/platform/surface/surface_aggregator_cdev.c ++++ b/drivers/platform/surface/surface_aggregator_cdev.c +@@ -3,7 +3,7 @@ + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator + * misc device. Intended for debugging and development. + * +- * Copyright (C) 2020-2021 Maximilian Luz ++ * Copyright (C) 2020-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 0cbb7f3a6b2d..d5655f6a4a41 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -6,7 +6,7 @@ + * cannot be auto-detected. Provides device-hubs and performs instantiation + * for these devices. + * +- * Copyright (C) 2020-2021 Maximilian Luz ++ * Copyright (C) 2020-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c +index 1203b9a82993..ed36944467f9 100644 +--- a/drivers/platform/surface/surface_dtx.c ++++ b/drivers/platform/surface/surface_dtx.c +@@ -8,7 +8,7 @@ + * 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 ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index ec66fde28e75..27365cbe1ee9 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -4,7 +4,7 @@ + * properly configuring the respective GPEs. Required for wakeup via lid on + * newer Intel-based Microsoft Surface devices. + * +- * Copyright (C) 2020 Maximilian Luz ++ * Copyright (C) 2020-2022 Maximilian Luz + */ + + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +diff --git a/drivers/platform/surface/surface_hotplug.c b/drivers/platform/surface/surface_hotplug.c +index cfcc15cfbacb..f004a2495201 100644 +--- a/drivers/platform/surface/surface_hotplug.c ++++ b/drivers/platform/surface/surface_hotplug.c +@@ -10,7 +10,7 @@ + * Event signaling is handled via ACPI, which will generate the appropriate + * device-check notifications to be picked up by the PCIe hot-plug driver. + * +- * Copyright (C) 2019-2021 Maximilian Luz ++ * Copyright (C) 2019-2022 Maximilian Luz + */ + + #include +diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c +index 6373d3b5eb7f..fbf2e11fd6ce 100644 +--- a/drivers/platform/surface/surface_platform_profile.c ++++ b/drivers/platform/surface/surface_platform_profile.c +@@ -3,7 +3,7 @@ + * Surface Platform Profile / Performance Mode driver for Surface System + * Aggregator Module (thermal subsystem). + * +- * Copyright (C) 2021 Maximilian Luz ++ * Copyright (C) 2021-2022 Maximilian Luz + */ + + #include +-- +2.37.1 + +From a73e0c964ed06bc1294ab0862a6b795fcf73911d Mon Sep 17 00:00:00 2001 +From: Lukas Bulwahn +Date: Wed, 13 Jul 2022 06:09:16 +0200 +Subject: [PATCH] MAINTAINERS: repair file entry in MICROSOFT SURFACE + AGGREGATOR TABLET-MODE SWITCH + +Commit 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch +driver") adds the section MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH +with a file entry, but the file that is added with this commit is actually +named slightly differently. + + file entry name: drivers/platform/surface/surface_aggregator_tablet_switch.c + added file name: drivers/platform/surface/surface_aggregator_tabletsw.c + +Hence, ./scripts/get_maintainer.pl --self-test=patterns complains about a +broken reference. + +Repair this file entry to the actual file name added with the commit above. + +Fixes: 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch driver") +Signed-off-by: Lukas Bulwahn +Reviewed-by: Andy Shevchenko +Reviewed-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220713040916.1767-1-lukas.bulwahn@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + MAINTAINERS | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/MAINTAINERS b/MAINTAINERS +index a29042434d9e..c967eb4675b7 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -13308,7 +13308,7 @@ MICROSOFT SURFACE AGGREGATOR TABLET-MODE SWITCH + M: Maximilian Luz + L: platform-driver-x86@vger.kernel.org + S: Maintained +-F: drivers/platform/surface/surface_aggregator_tablet_switch.c ++F: drivers/platform/surface/surface_aggregator_tabletsw.c + + MICROSOFT SURFACE BATTERY AND AC DRIVERS + M: Maximilian Luz +-- +2.37.1 + +From 0ba68391040ba804f381cf1e80fed98ff9810016 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 17 Jul 2022 14:07:35 +0200 +Subject: [PATCH] platform/surface: tabletsw: Fix __le32 integer access + +The sources.count field is a __le32 inside a packed struct. So use the +proper functions to access it. + +Reported-by: kernel test robot +Fixes: 9f794056db5b ("platform/surface: Add KIP/POS tablet-mode switch driver") +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220717120735.2052160-1-luzmaximilian@gmail.com +Reviewed-by: Hans de Goede +Signed-off-by: Hans de Goede +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_tabletsw.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_tabletsw.c b/drivers/platform/surface/surface_aggregator_tabletsw.c +index 596ca6c80681..27d95a6a7851 100644 +--- a/drivers/platform/surface/surface_aggregator_tabletsw.c ++++ b/drivers/platform/surface/surface_aggregator_tabletsw.c +@@ -410,7 +410,7 @@ static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) + if (status) + return status; + +- if (sources.count == 0) { ++ if (get_unaligned_le32(&sources.count) == 0) { + dev_err(&sw->sdev->dev, "no posture sources found\n"); + return -ENODEV; + } +@@ -422,7 +422,7 @@ static int ssam_pos_get_source(struct ssam_tablet_sw *sw, u32 *source_id) + * is a device that provides multiple sources, at which point we can + * then try to figure out how to handle them. + */ +- WARN_ON(sources.count > 1); ++ WARN_ON(get_unaligned_le32(&sources.count) > 1); + + *source_id = get_unaligned_le32(&sources.id[0]); + return 0; +-- +2.37.1 + +From ff91dba8ef6eab54f9025cd35d52028cb75cbf3e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 8 Jul 2022 03:34:44 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + Surface Laptop Go 2 + +The Surface Laptop Go 2 seems to have the same SAM client devices as the +Surface Laptop Go 1, so re-use its node group. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index d5655f6a4a41..93ab62eb393d 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -325,6 +325,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Laptop Go 1 */ + { "MSHW0118", (unsigned long)ssam_node_group_slg1 }, + ++ /* Surface Laptop Go 2 */ ++ { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, ++ + /* Surface Laptop Studio */ + { "MSHW0123", (unsigned long)ssam_node_group_sls }, + +-- +2.37.1 + +From e399ffd43d7419256f29b8834211ed6685f6ca2d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jun 2022 20:42:00 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device + nodes based on their function + +Rename HID device nodes based on their function. In particular, these +are nodes for firmware updates via the CFU mechanism (component firmware +update), HID based sensors, and a USB-C UCSI client. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 93ab62eb393d..7d82398f55b1 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -104,14 +104,14 @@ static const struct software_node ssam_node_hid_tid1_touchpad = { + .parent = &ssam_node_root, + }; + +-/* HID device instance 6 (TID1, unknown HID device). */ +-static const struct software_node ssam_node_hid_tid1_iid6 = { ++/* HID device instance 6 (TID1, HID sensor collection). */ ++static const struct software_node ssam_node_hid_tid1_sensors = { + .name = "ssam:01:15:01:06:00", + .parent = &ssam_node_root, + }; + +-/* HID device instance 7 (TID1, unknown HID device). */ +-static const struct software_node ssam_node_hid_tid1_iid7 = { ++/* HID device instance 7 (TID1, UCM UCSI HID client). */ ++static const struct software_node ssam_node_hid_tid1_ucm_ucsi = { + .name = "ssam:01:15:01:07:00", + .parent = &ssam_node_root, + }; +@@ -182,8 +182,8 @@ static const struct software_node ssam_node_hid_kip_touchpad = { + .parent = &ssam_node_hub_kip, + }; + +-/* HID device instance 5 (KIP hub, unknown HID device). */ +-static const struct software_node ssam_node_hid_kip_iid5 = { ++/* HID device instance 5 (KIP hub, type-cover firmware update). */ ++static const struct software_node ssam_node_hid_kip_fwupd = { + .name = "ssam:01:15:02:05:00", + .parent = &ssam_node_hub_kip, + }; +@@ -244,8 +244,8 @@ static const struct software_node *ssam_node_group_sls[] = { + &ssam_node_hid_tid1_keyboard, + &ssam_node_hid_tid1_penstash, + &ssam_node_hid_tid1_touchpad, +- &ssam_node_hid_tid1_iid6, +- &ssam_node_hid_tid1_iid7, ++ &ssam_node_hid_tid1_sensors, ++ &ssam_node_hid_tid1_ucm_ucsi, + &ssam_node_hid_tid1_sysctrl, + NULL, + }; +@@ -278,7 +278,7 @@ static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_hid_kip_keyboard, + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, +- &ssam_node_hid_kip_iid5, ++ &ssam_node_hid_kip_fwupd, + NULL, + }; + +-- +2.37.1 + +From 1d687b69d2e5eadb47710d4cc2b811a130787120 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jun 2022 20:52:47 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Rename HID device + nodes based on new findings + +On Windows, the HID devices with target ID 1 are grouped as "Surface Hot +Plug - SAM". Rename their device nodes in the registry to reflect that +and update the comments accordingly. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 36 +++++++++---------- + 1 file changed, 18 insertions(+), 18 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 7d82398f55b1..9970f89b1411 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -86,38 +86,38 @@ static const struct software_node ssam_node_bas_dtx = { + .parent = &ssam_node_root, + }; + +-/* HID keyboard (TID1). */ +-static const struct software_node ssam_node_hid_tid1_keyboard = { ++/* HID keyboard (SAM, TID=1). */ ++static const struct software_node ssam_node_hid_sam_keyboard = { + .name = "ssam:01:15:01:01:00", + .parent = &ssam_node_root, + }; + +-/* HID pen stash (TID1; pen taken / stashed away evens). */ +-static const struct software_node ssam_node_hid_tid1_penstash = { ++/* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ ++static const struct software_node ssam_node_hid_sam_penstash = { + .name = "ssam:01:15:01:02:00", + .parent = &ssam_node_root, + }; + +-/* HID touchpad (TID1). */ +-static const struct software_node ssam_node_hid_tid1_touchpad = { ++/* HID touchpad (SAM, TID=1). */ ++static const struct software_node ssam_node_hid_sam_touchpad = { + .name = "ssam:01:15:01:03:00", + .parent = &ssam_node_root, + }; + +-/* HID device instance 6 (TID1, HID sensor collection). */ +-static const struct software_node ssam_node_hid_tid1_sensors = { ++/* HID device instance 6 (SAM, TID=1, HID sensor collection). */ ++static const struct software_node ssam_node_hid_sam_sensors = { + .name = "ssam:01:15:01:06:00", + .parent = &ssam_node_root, + }; + +-/* HID device instance 7 (TID1, UCM UCSI HID client). */ +-static const struct software_node ssam_node_hid_tid1_ucm_ucsi = { ++/* HID device instance 7 (SAM, TID=1, UCM UCSI HID client). */ ++static const struct software_node ssam_node_hid_sam_ucm_ucsi = { + .name = "ssam:01:15:01:07:00", + .parent = &ssam_node_root, + }; + +-/* HID system controls (TID1). */ +-static const struct software_node ssam_node_hid_tid1_sysctrl = { ++/* HID system controls (SAM, TID=1). */ ++static const struct software_node ssam_node_hid_sam_sysctrl = { + .name = "ssam:01:15:01:08:00", + .parent = &ssam_node_root, + }; +@@ -241,12 +241,12 @@ static const struct software_node *ssam_node_group_sls[] = { + &ssam_node_bat_main, + &ssam_node_tmp_pprof, + &ssam_node_pos_tablet_switch, +- &ssam_node_hid_tid1_keyboard, +- &ssam_node_hid_tid1_penstash, +- &ssam_node_hid_tid1_touchpad, +- &ssam_node_hid_tid1_sensors, +- &ssam_node_hid_tid1_ucm_ucsi, +- &ssam_node_hid_tid1_sysctrl, ++ &ssam_node_hid_sam_keyboard, ++ &ssam_node_hid_sam_penstash, ++ &ssam_node_hid_sam_touchpad, ++ &ssam_node_hid_sam_sensors, ++ &ssam_node_hid_sam_ucm_ucsi, ++ &ssam_node_hid_sam_sysctrl, + NULL, + }; + +-- +2.37.1 + +From 89fac2c107f3738bb8a67144827d35ab3e2d92eb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jun 2022 20:54:59 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add HID devices for + sensors and UCSI client to SP8 + +Add software nodes for the HID sensor collection and the UCM UCSI HID +client to the Surface Pro 8. In contrast to the type-cover devices, +these devices are directly attached to the SAM controller, without any +hub. + +This enables support for HID-based sensors, including the ones used for +automatic screen rotation, on the Surface Pro 8. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 9970f89b1411..585911020cea 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -279,6 +279,8 @@ static const struct software_node *ssam_node_group_sp8[] = { + &ssam_node_hid_kip_penstash, + &ssam_node_hid_kip_touchpad, + &ssam_node_hid_kip_fwupd, ++ &ssam_node_hid_sam_sensors, ++ &ssam_node_hid_sam_ucm_ucsi, + NULL, + }; + +-- +2.37.1 + diff --git a/patches/5.19/0006-surface-sam-over-hid.patch b/patches/5.19/0006-surface-sam-over-hid.patch new file mode 100644 index 000000000..cf2ae8faa --- /dev/null +++ b/patches/5.19/0006-surface-sam-over-hid.patch @@ -0,0 +1,335 @@ +From 155fdf1df300409bfc1536e135759d34fae35304 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +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 +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 08b561f0709d..d7c397bce0f0 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -619,6 +619,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, +@@ -720,6 +742,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.37.1 + +From cbe209d65b48663c40abdf82c7c5d668df442094 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +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 b629e82af97c..68656e8f309e 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++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_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939b..7efcd0cdb532 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.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 +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 ++#include ++#include ++#include ++ ++ ++#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 "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.37.1 + diff --git a/patches/5.19/0007-surface-button.patch b/patches/5.19/0007-surface-button.patch new file mode 100644 index 000000000..23c4a5ca2 --- /dev/null +++ b/patches/5.19/0007-surface-button.patch @@ -0,0 +1,149 @@ +From b4993715b5baeaf0f9bad2e5f7633f2d7c6dd4b8 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index 480476121c01..36e1bf7b7a01 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -495,8 +495,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { + * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned + * devices use MSHW0040 for power and volume buttons, however the way they + * have to be addressed differs. Make sure that we only load this drivers +- * for the correct devices by checking the OEM Platform Revision provided by +- * the _DSM method. ++ * for the correct devices by checking if the OEM Platform Revision DSM call ++ * exists. + */ + #define MSHW0040_DSM_REVISION 0x01 + #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision +@@ -507,31 +507,14 @@ static const guid_t MSHW0040_DSM_UUID = + static int soc_device_check_MSHW0040(struct device *dev) + { + acpi_handle handle = ACPI_HANDLE(dev); +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, NULL, +- ACPI_TYPE_INTEGER); +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- /* +- * If the revision is zero here, the _DSM evaluation has failed. This +- * indicates that we have a Pro 4 or Book 1 and this driver should not +- * be used. +- */ +- if (oem_platform_rev == 0) +- return -ENODEV; ++ bool exists; + +- dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + +- return 0; ++ return exists ? 0 : -ENODEV; + } + + /* +-- +2.37.1 + +From 0abe57af1226e43b82a5e65f65ab791ff7e1e71a Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 242fb690dcaf..30eea54dbb47 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.37.1 + diff --git a/patches/5.19/0008-surface-typecover.patch b/patches/5.19/0008-surface-typecover.patch new file mode 100644 index 000000000..66014f149 --- /dev/null +++ b/patches/5.19/0008-surface-typecover.patch @@ -0,0 +1,533 @@ +From 450c62edbdd074441f6f2e57d7cc0fa08a274a36 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +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 6bb3890b0f2c..c28349e90156 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -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) +@@ -71,12 +75,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_SEPARATE_APP_REPORT BIT(19) + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) + + #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, +@@ -168,6 +175,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, +@@ -211,6 +220,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 +@@ -386,6 +396,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 ++ }, + { } + }; + +@@ -1695,6 +1715,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; +@@ -1718,6 +1801,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); + +@@ -1747,15 +1833,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) +@@ -1807,6 +1897,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); +@@ -2180,6 +2271,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.37.1 + +From 726b2dcdf1c82585355518446ca7b812604a3b37 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index c28349e90156..61142639be26 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -76,6 +76,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -83,6 +84,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -398,6 +401,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1357,6 +1361,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1384,6 +1391,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1405,6 +1427,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1412,6 +1435,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1421,11 +1457,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1578,6 +1624,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1627,6 +1709,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1715,30 +1804,6 @@ 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); +@@ -1747,8 +1812,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* 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) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1874,13 +1940,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1889,6 +1966,10 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + #endif +@@ -1896,6 +1977,21 @@ static int mt_resume(struct hid_device *hdev) + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.37.1 + diff --git a/patches/5.19/0009-surface-battery.patch b/patches/5.19/0009-surface-battery.patch new file mode 100644 index 000000000..e560092a9 --- /dev/null +++ b/patches/5.19/0009-surface-battery.patch @@ -0,0 +1,48 @@ +From d080c0c82936fd7721d7b57cd7a48e9fc2f573c0 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Wed, 25 May 2022 14:20:10 +0200 +Subject: [PATCH] HID: hid-input: add Surface Go battery quirk + +Similar to the Surface Go (1), the (Elantech) touchscreen/digitizer in +the Surface Go 2 mistakenly reports the battery of the stylus. Instead +of over the touchsreen device, battery information is provided via +bluetooth and the touchscreen device reports an empty battery. + +Apply the HID_BATTERY_QUIRK_IGNORE quirk to to ignore this battery and +prevent the erroneous low battery warnings. + +Signed-off-by: Maximilian Luz +Patchset: surface-battery +--- + drivers/hid/hid-ids.h | 1 + + drivers/hid/hid-input.c | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +index d9eb676abe96..9c4e92a9c646 100644 +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -413,6 +413,7 @@ + #define USB_DEVICE_ID_ASUS_UX550VE_TOUCHSCREEN 0x2544 + #define USB_DEVICE_ID_ASUS_UX550_TOUCHSCREEN 0x2706 + #define I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN 0x261A ++#define I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN 0x2A1C + + #define USB_VENDOR_ID_ELECOM 0x056e + #define USB_DEVICE_ID_ELECOM_BM084 0x0061 +diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c +index c6b27aab9041..48c1c02c69f4 100644 +--- a/drivers/hid/hid-input.c ++++ b/drivers/hid/hid-input.c +@@ -381,6 +381,8 @@ static const struct hid_device_id hid_battery_quirks[] = { + HID_BATTERY_QUIRK_IGNORE }, + { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO_TOUCHSCREEN), + HID_BATTERY_QUIRK_IGNORE }, ++ { HID_I2C_DEVICE(USB_VENDOR_ID_ELAN, I2C_DEVICE_ID_SURFACE_GO2_TOUCHSCREEN), ++ HID_BATTERY_QUIRK_IGNORE }, + {} + }; + +-- +2.37.1 + diff --git a/patches/5.19/0010-surface-gpe.patch b/patches/5.19/0010-surface-gpe.patch new file mode 100644 index 000000000..a7e325599 --- /dev/null +++ b/patches/5.19/0010-surface-gpe.patch @@ -0,0 +1,43 @@ +From 7f275e2a793b9878ad4fea60ce4f857e012dc464 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 21 Jul 2022 14:11:20 +0200 +Subject: [PATCH] platform/surface: gpe: Add support for 13" Intel version of + Surface Laptop 4 + +The 13" Intel version of the Surface Laptop 4 uses the same GPE as the +Surface Laptop Studio for wakeups via the lid. Set it up accordingly. + +Signed-off-by: Maximilian Luz +Link: https://lore.kernel.org/r/20220721121120.2002430-1-luzmaximilian@gmail.com +Signed-off-by: Hans de Goede +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index 27365cbe1ee9..c219b840d491 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -171,6 +171,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4D, + }, ++ { ++ .ident = "Surface Laptop 4 (Intel 13\")", ++ .matches = { ++ /* ++ * We match for SKU here due to different variants: The ++ * AMD (15") version does not rely on GPEs. ++ */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1950:1951"), ++ }, ++ .driver_data = (void *)lid_device_props_l4B, ++ }, + { + .ident = "Surface Laptop Studio", + .matches = { +-- +2.37.1 + diff --git a/patches/5.19/0011-cameras.patch b/patches/5.19/0011-cameras.patch new file mode 100644 index 000000000..c16656a8b --- /dev/null +++ b/patches/5.19/0011-cameras.patch @@ -0,0 +1,923 @@ +From b1695f491b209fed475663802411b83beaef1a41 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 762b61f67e6c..2c0f39a7f2a1 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2122,6 +2122,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.37.1 + +From 26234d5e448a43d01da8555b7e1fd8c2cecd1321 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 825b524e81f3..095d566c058f 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -37,6 +37,12 @@ + #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) + #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) ++#define IS_INTEL_IPU(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9a19 || \ ++ (pdev)->device == 0x9a39 || \ ++ (pdev)->device == 0x4e19 || \ ++ (pdev)->device == 0x465d || \ ++ (pdev)->device == 0x1919)) + #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9d3e)) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) +@@ -310,12 +316,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_gfx = 1; + static int dmar_map_ipts = 1; ++static int dmar_map_ipu = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + + #define IDENTMAP_GFX 2 + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPU 8 + #define IDENTMAP_IPTS 16 + + int intel_iommu_gfx_mapped; +@@ -2664,6 +2672,9 @@ static int device_def_domain_type(struct device *dev) + if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) + return IOMMU_DOMAIN_IDENTITY; + ++ if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; ++ + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; + } +@@ -3102,6 +3113,9 @@ static int __init init_dmars(void) + if (!dmar_map_gfx) + iommu_identity_mapping |= IDENTMAP_GFX; + ++ if (!dmar_map_ipu) ++ iommu_identity_mapping |= IDENTMAP_IPU; ++ + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; + +@@ -4933,6 +4947,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + dmar_map_gfx = 0; + } + ++static void quirk_iommu_ipu(struct pci_dev *dev) ++{ ++ if (!IS_INTEL_IPU(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for integrated Intel IPU\n"); ++ dmar_map_ipu = 0; ++} ++ + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4944,6 +4970,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) + pci_info(dev, "Passthrough IOMMU for IPTS\n"); + dmar_map_ipts = 0; + } ++ + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4979,6 +5006,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPU dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); ++ + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); + +-- +2.37.1 + +From 5554608114331d9b088353fbe407749444e7c1a0 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 22f61b47f9e5..e1de1ff40bba 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -45,6 +45,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; ++ } ++ + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.37.1 + +From 4684d8a03463ebf1d2b85094f11cb7ac9ba6f1c3 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 28 Oct 2021 21:55:16 +0100 +Subject: [PATCH] media: i2c: Add driver for DW9719 VCM + +Add a driver for the DW9719 VCM. The driver creates a v4l2 subdevice +and registers a control to set the desired focus. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + MAINTAINERS | 7 + + drivers/media/i2c/Kconfig | 11 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/dw9719.c | 427 +++++++++++++++++++++++++++++++++++++ + 4 files changed, 446 insertions(+) + create mode 100644 drivers/media/i2c/dw9719.c + +diff --git a/MAINTAINERS b/MAINTAINERS +index c967eb4675b7..92ae23d4e27a 100644 +--- a/MAINTAINERS ++++ b/MAINTAINERS +@@ -6163,6 +6163,13 @@ T: git git://linuxtv.org/media_tree.git + F: Documentation/devicetree/bindings/media/i2c/dongwoon,dw9714.txt + F: drivers/media/i2c/dw9714.c + ++DONGWOON DW9719 LENS VOICE COIL DRIVER ++M: Daniel Scally ++L: linux-media@vger.kernel.org ++S: Maintained ++T: git git://linuxtv.org/media_tree.git ++F: drivers/media/i2c/dw9719.c ++ + DONGWOON DW9768 LENS VOICE COIL DRIVER + M: Dongchun Zhu + L: linux-media@vger.kernel.org +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 2b20aa6c37b1..49d1c59334e3 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -806,6 +806,17 @@ config VIDEO_DW9714 + capability. This is designed for linear control of + voice coil motors, controlled via I2C serial interface. + ++config VIDEO_DW9719 ++ tristate "DW9719 lens voice coil support" ++ depends on I2C && VIDEO_V4L2 ++ select MEDIA_CONTROLLER ++ select VIDEO_V4L2_SUBDEV_API ++ select V4L2_ASYNC ++ help ++ This is a driver for the DW9719 camera lens voice coil. ++ This is designed for linear control of voice coil motors, ++ controlled via I2C serial interface. ++ + config VIDEO_DW9768 + tristate "DW9768 lens voice coil support" + depends on I2C && VIDEO_DEV +diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile +index 3e1696963e7f..9dfda069e006 100644 +--- a/drivers/media/i2c/Makefile ++++ b/drivers/media/i2c/Makefile +@@ -29,6 +29,7 @@ obj-$(CONFIG_VIDEO_CS5345) += cs5345.o + obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o + obj-$(CONFIG_VIDEO_CX25840) += cx25840/ + obj-$(CONFIG_VIDEO_DW9714) += dw9714.o ++obj-$(CONFIG_VIDEO_DW9719) += dw9719.o + obj-$(CONFIG_VIDEO_DW9768) += dw9768.o + obj-$(CONFIG_VIDEO_DW9807_VCM) += dw9807-vcm.o + obj-$(CONFIG_VIDEO_ET8EK8) += et8ek8/ +diff --git a/drivers/media/i2c/dw9719.c b/drivers/media/i2c/dw9719.c +new file mode 100644 +index 000000000000..8451c75b696b +--- /dev/null ++++ b/drivers/media/i2c/dw9719.c +@@ -0,0 +1,427 @@ ++// SPDX-License-Identifier: GPL-2.0 ++// Copyright (c) 2012 Intel Corporation ++ ++/* ++ * Based on linux/modules/camera/drivers/media/i2c/imx/dw9719.c in this repo: ++ * https://github.com/ZenfoneArea/android_kernel_asus_zenfone5 ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#define DW9719_MAX_FOCUS_POS 1023 ++#define DW9719_CTRL_STEPS 16 ++#define DW9719_CTRL_DELAY_US 1000 ++#define DELAY_MAX_PER_STEP_NS (1000000 * 1023) ++ ++#define DW9719_INFO 0 ++#define DW9719_ID 0xF1 ++#define DW9719_CONTROL 2 ++#define DW9719_VCM_CURRENT 3 ++ ++#define DW9719_MODE 6 ++#define DW9719_VCM_FREQ 7 ++ ++#define DW9719_MODE_SAC3 0x40 ++#define DW9719_DEFAULT_VCM_FREQ 0x60 ++#define DW9719_ENABLE_RINGING 0x02 ++ ++#define NUM_REGULATORS 2 ++ ++#define to_dw9719_device(x) container_of(x, struct dw9719_device, sd) ++ ++struct dw9719_device { ++ struct device *dev; ++ struct i2c_client *client; ++ struct regulator_bulk_data regulators[NUM_REGULATORS]; ++ struct v4l2_subdev sd; ++ ++ struct dw9719_v4l2_ctrls { ++ struct v4l2_ctrl_handler handler; ++ struct v4l2_ctrl *focus; ++ } ctrls; ++}; ++ ++static int dw9719_i2c_rd8(struct i2c_client *client, u8 reg, u8 *val) ++{ ++ struct i2c_msg msg[2]; ++ u8 buf[2] = { reg }; ++ int ret; ++ ++ msg[0].addr = client->addr; ++ msg[0].flags = 0; ++ msg[0].len = 1; ++ msg[0].buf = buf; ++ ++ msg[1].addr = client->addr; ++ msg[1].flags = I2C_M_RD; ++ msg[1].len = 1; ++ msg[1].buf = &buf[1]; ++ *val = 0; ++ ++ ret = i2c_transfer(client->adapter, msg, 2); ++ if (ret < 0) ++ return ret; ++ ++ *val = buf[1]; ++ ++ return 0; ++} ++ ++static int dw9719_i2c_wr8(struct i2c_client *client, u8 reg, u8 val) ++{ ++ struct i2c_msg msg; ++ int ret; ++ ++ u8 buf[2] = { reg, val }; ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_i2c_wr16(struct i2c_client *client, u8 reg, u16 val) ++{ ++ struct i2c_msg msg; ++ u8 buf[3] = { reg }; ++ int ret; ++ ++ put_unaligned_be16(val, buf + 1); ++ ++ msg.addr = client->addr; ++ msg.flags = 0; ++ msg.len = sizeof(buf); ++ msg.buf = buf; ++ ++ ret = i2c_transfer(client->adapter, &msg, 1); ++ ++ return ret < 0 ? ret : 0; ++} ++ ++static int dw9719_detect(struct dw9719_device *dw9719) ++{ ++ int ret; ++ u8 val; ++ ++ ret = dw9719_i2c_rd8(dw9719->client, DW9719_INFO, &val); ++ if (ret < 0) ++ return ret; ++ ++ if (val != DW9719_ID) { ++ dev_err(dw9719->dev, "Failed to detect correct id\n"); ++ ret = -ENXIO; ++ } ++ ++ return 0; ++} ++ ++static int dw9719_power_down(struct dw9719_device *dw9719) ++{ ++ return regulator_bulk_disable(NUM_REGULATORS, dw9719->regulators); ++} ++ ++static int dw9719_power_up(struct dw9719_device *dw9719) ++{ ++ int ret; ++ ++ ret = regulator_bulk_enable(NUM_REGULATORS, dw9719->regulators); ++ if (ret) ++ return ret; ++ ++ /* Jiggle SCL pin to wake up device */ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, 1); ++ ++ /* Need 100us to transit from SHUTDOWN to STANDBY*/ ++ usleep_range(100, 1000); ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_CONTROL, ++ DW9719_ENABLE_RINGING); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_MODE, DW9719_MODE_SAC3); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ ret = dw9719_i2c_wr8(dw9719->client, DW9719_VCM_FREQ, ++ DW9719_DEFAULT_VCM_FREQ); ++ if (ret < 0) ++ goto fail_powerdown; ++ ++ return 0; ++ ++fail_powerdown: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_t_focus_abs(struct dw9719_device *dw9719, s32 value) ++{ ++ int ret; ++ ++ value = clamp(value, 0, DW9719_MAX_FOCUS_POS); ++ ret = dw9719_i2c_wr16(dw9719->client, DW9719_VCM_CURRENT, value); ++ if (ret < 0) ++ return ret; ++ ++ return 0; ++} ++ ++static int dw9719_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct dw9719_device *dw9719 = container_of(ctrl->handler, ++ struct dw9719_device, ++ ctrls.handler); ++ int ret; ++ ++ /* Only apply changes to the controls if the device is powered up */ ++ if (!pm_runtime_get_if_in_use(dw9719->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_FOCUS_ABSOLUTE: ++ ret = dw9719_t_focus_abs(dw9719, ctrl->val); ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ pm_runtime_put(dw9719->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops dw9719_ctrl_ops = { ++ .s_ctrl = dw9719_set_ctrl, ++}; ++ ++static int __maybe_unused dw9719_suspend(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int ret; ++ int val; ++ ++ for (val = dw9719->ctrls.focus->val; val >= 0; ++ val -= DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ return ret; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return dw9719_power_down(dw9719); ++} ++ ++static int __maybe_unused dw9719_resume(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct dw9719_device *dw9719 = to_dw9719_device(sd); ++ int current_focus = dw9719->ctrls.focus->val; ++ int ret; ++ int val; ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ return ret; ++ ++ for (val = current_focus % DW9719_CTRL_STEPS; val < current_focus; ++ val += DW9719_CTRL_STEPS) { ++ ret = dw9719_t_focus_abs(dw9719, val); ++ if (ret) ++ goto err_power_down; ++ ++ usleep_range(DW9719_CTRL_DELAY_US, DW9719_CTRL_DELAY_US + 10); ++ } ++ ++ return 0; ++ ++err_power_down: ++ dw9719_power_down(dw9719); ++ return ret; ++} ++ ++static int dw9719_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ return pm_runtime_resume_and_get(sd->dev); ++} ++ ++static int dw9719_close(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ pm_runtime_put(sd->dev); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_internal_ops dw9719_internal_ops = { ++ .open = dw9719_open, ++ .close = dw9719_close, ++}; ++ ++static int dw9719_init_controls(struct dw9719_device *dw9719) ++{ ++ const struct v4l2_ctrl_ops *ops = &dw9719_ctrl_ops; ++ int ret; ++ ++ ret = v4l2_ctrl_handler_init(&dw9719->ctrls.handler, 1); ++ if (ret) ++ return ret; ++ ++ dw9719->ctrls.focus = v4l2_ctrl_new_std(&dw9719->ctrls.handler, ops, ++ V4L2_CID_FOCUS_ABSOLUTE, 0, ++ DW9719_MAX_FOCUS_POS, 1, 0); ++ ++ if (dw9719->ctrls.handler.error) { ++ dev_err(dw9719->dev, "Error initialising v4l2 ctrls\n"); ++ ret = dw9719->ctrls.handler.error; ++ goto err_free_handler; ++ } ++ ++ dw9719->sd.ctrl_handler = &dw9719->ctrls.handler; ++ ++ return ret; ++ ++err_free_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ return ret; ++} ++ ++static const struct v4l2_subdev_ops dw9719_ops = { }; ++ ++static int dw9719_probe(struct i2c_client *client) ++{ ++ struct dw9719_device *dw9719; ++ int ret; ++ ++ dw9719 = devm_kzalloc(&client->dev, sizeof(*dw9719), GFP_KERNEL); ++ if (!dw9719) ++ return -ENOMEM; ++ ++ dw9719->client = client; ++ dw9719->dev = &client->dev; ++ ++ dw9719->regulators[0].supply = "vdd"; ++ /* ++ * The DW9719 has only the 1 VDD voltage input, but some PMICs such as ++ * the TPS68470 PMIC have I2C passthrough capability, to disconnect the ++ * sensor's I2C pins from the I2C bus when the sensors VSIO (Sensor-IO) ++ * is off, because some sensors then short these pins to ground; ++ * and the DW9719 might sit behind this passthrough, this it needs to ++ * enable VSIO as that will also enable the I2C passthrough. ++ */ ++ dw9719->regulators[1].supply = "vsio"; ++ ++ ret = devm_regulator_bulk_get(&client->dev, NUM_REGULATORS, ++ dw9719->regulators); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, "getting regulators\n"); ++ ++ v4l2_i2c_subdev_init(&dw9719->sd, client, &dw9719_ops); ++ dw9719->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ dw9719->sd.internal_ops = &dw9719_internal_ops; ++ ++ ret = dw9719_init_controls(dw9719); ++ if (ret) ++ return ret; ++ ++ ret = media_entity_pads_init(&dw9719->sd.entity, 0, NULL); ++ if (ret < 0) ++ goto err_free_ctrl_handler; ++ ++ dw9719->sd.entity.function = MEDIA_ENT_F_LENS; ++ ++ /* ++ * 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 the lens ++ * will work. ++ */ ++ ++ ret = dw9719_power_up(dw9719); ++ if (ret) ++ goto err_cleanup_media; ++ ++ ret = dw9719_detect(dw9719); ++ 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(&dw9719->sd); ++ if (ret < 0) ++ 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: ++ dw9719_power_down(dw9719); ++err_cleanup_media: ++ media_entity_cleanup(&dw9719->sd.entity); ++err_free_ctrl_handler: ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ ++ return ret; ++} ++ ++static int dw9719_remove(struct i2c_client *client) ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct dw9719_device *dw9719 = container_of(sd, struct dw9719_device, ++ sd); ++ ++ pm_runtime_disable(&client->dev); ++ v4l2_async_unregister_subdev(sd); ++ v4l2_ctrl_handler_free(&dw9719->ctrls.handler); ++ media_entity_cleanup(&dw9719->sd.entity); ++ ++ return 0; ++} ++ ++static const struct i2c_device_id dw9719_id_table[] = { ++ { "dw9719" }, ++ { } ++}; ++MODULE_DEVICE_TABLE(i2c, dw9719_id_table); ++ ++static const struct dev_pm_ops dw9719_pm_ops = { ++ SET_RUNTIME_PM_OPS(dw9719_suspend, dw9719_resume, NULL) ++}; ++ ++static struct i2c_driver dw9719_i2c_driver = { ++ .driver = { ++ .name = "dw9719", ++ .pm = &dw9719_pm_ops, ++ }, ++ .probe_new = dw9719_probe, ++ .remove = dw9719_remove, ++ .id_table = dw9719_id_table, ++}; ++module_i2c_driver(dw9719_i2c_driver); ++ ++MODULE_AUTHOR("Daniel Scally "); ++MODULE_DESCRIPTION("DW9719 VCM Driver"); ++MODULE_LICENSE("GPL"); +-- +2.37.1 + +From 9480734a6ad3d5d296e475a811ff42da0a5b3394 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 4 May 2022 23:21:45 +0100 +Subject: [PATCH] media: ipu3-cio2: Move functionality from .complete() to + .bound() + +Creating links and registering subdev nodes during the .complete() +callback has the unfortunate effect of preventing all cameras that +connect to a notifier from working if any one of their drivers fails +to probe. Moving the functionality from .complete() to .bound() allows +those camera sensor drivers that did probe correctly to work regardless. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 65 +++++++------------ + 1 file changed, 23 insertions(+), 42 deletions(-) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index dbdbdb648a0d..d0715144bf3e 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1383,7 +1383,10 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, + { + struct cio2_device *cio2 = to_cio2_device(notifier); + struct sensor_async_subdev *s_asd = to_sensor_asd(asd); ++ struct device *dev = &cio2->pci_dev->dev; + struct cio2_queue *q; ++ unsigned int pad; ++ int ret; + + if (cio2->queue[s_asd->csi2.port].sensor) + return -EBUSY; +@@ -1394,7 +1397,26 @@ static int cio2_notifier_bound(struct v4l2_async_notifier *notifier, + q->sensor = sd; + q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); + +- return 0; ++ for (pad = 0; pad < q->sensor->entity.num_pads; pad++) ++ if (q->sensor->entity.pads[pad].flags & ++ MEDIA_PAD_FL_SOURCE) ++ break; ++ ++ if (pad == q->sensor->entity.num_pads) { ++ dev_err(dev, "failed to find src pad for %s\n", ++ q->sensor->name); ++ return -ENXIO; ++ } ++ ++ ret = media_create_pad_link(&q->sensor->entity, pad, &q->subdev.entity, ++ CIO2_PAD_SINK, 0); ++ if (ret) { ++ dev_err(dev, "failed to create link for %s\n", ++ q->sensor->name); ++ return ret; ++ } ++ ++ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); + } + + /* The .unbind callback */ +@@ -1408,50 +1430,9 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, + cio2->queue[s_asd->csi2.port].sensor = NULL; + } + +-/* .complete() is called after all subdevices have been located */ +-static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) +-{ +- struct cio2_device *cio2 = to_cio2_device(notifier); +- struct device *dev = &cio2->pci_dev->dev; +- struct sensor_async_subdev *s_asd; +- struct v4l2_async_subdev *asd; +- struct cio2_queue *q; +- unsigned int pad; +- int ret; +- +- list_for_each_entry(asd, &cio2->notifier.asd_list, asd_list) { +- s_asd = to_sensor_asd(asd); +- q = &cio2->queue[s_asd->csi2.port]; +- +- for (pad = 0; pad < q->sensor->entity.num_pads; pad++) +- if (q->sensor->entity.pads[pad].flags & +- MEDIA_PAD_FL_SOURCE) +- break; +- +- if (pad == q->sensor->entity.num_pads) { +- dev_err(dev, "failed to find src pad for %s\n", +- q->sensor->name); +- return -ENXIO; +- } +- +- ret = media_create_pad_link( +- &q->sensor->entity, pad, +- &q->subdev.entity, CIO2_PAD_SINK, +- 0); +- if (ret) { +- dev_err(dev, "failed to create link for %s\n", +- q->sensor->name); +- return ret; +- } +- } +- +- return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); +-} +- + static const struct v4l2_async_notifier_operations cio2_async_ops = { + .bound = cio2_notifier_bound, + .unbind = cio2_notifier_unbind, +- .complete = cio2_notifier_complete, + }; + + static int cio2_parse_firmware(struct cio2_device *cio2) +-- +2.37.1 + +From b286de979999d9415c2e1919e02e213ee654742f Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Jun 2022 22:15:56 +0100 +Subject: [PATCH] media: ipu3-cio2: Re-add .complete() to ipu3-cio2 + +Removing the .complete() callback had some unintended consequences. +Because the VCM driver is not directly linked to the ipu3-cio2 +driver .bound() never gets called for it, which means its devnode +is never created if it probes late. Because .complete() waits for +any sub-notifiers to also be complete it is captured in that call. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/pci/intel/ipu3/ipu3-cio2-main.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +index d0715144bf3e..3a25dfc696b2 100644 +--- a/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c ++++ b/drivers/media/pci/intel/ipu3/ipu3-cio2-main.c +@@ -1430,9 +1430,18 @@ static void cio2_notifier_unbind(struct v4l2_async_notifier *notifier, + cio2->queue[s_asd->csi2.port].sensor = NULL; + } + ++/* .complete() is called after all subdevices have been located */ ++static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) ++{ ++ struct cio2_device *cio2 = to_cio2_device(notifier); ++ ++ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); ++} ++ + static const struct v4l2_async_notifier_operations cio2_async_ops = { + .bound = cio2_notifier_bound, + .unbind = cio2_notifier_unbind, ++ .complete = cio2_notifier_complete, + }; + + static int cio2_parse_firmware(struct cio2_device *cio2) +-- +2.37.1 + +From d7c45282141834a8d539e87575a6082ce7da624e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Fri, 15 Jul 2022 23:48:00 +0200 +Subject: [PATCH] drivers/media/i2c: Fix DW9719 dependencies + +It should depend on VIDEO_DEV instead of VIDEO_V4L2. + +Signed-off-by: Maximilian Luz +Patchset: cameras +--- + drivers/media/i2c/Kconfig | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 49d1c59334e3..d3f27554a1e3 100644 +--- a/drivers/media/i2c/Kconfig ++++ b/drivers/media/i2c/Kconfig +@@ -808,7 +808,7 @@ config VIDEO_DW9714 + + config VIDEO_DW9719 + tristate "DW9719 lens voice coil support" +- depends on I2C && VIDEO_V4L2 ++ depends on I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_ASYNC +-- +2.37.1 + diff --git a/patches/5.19/0012-amd-gpio.patch b/patches/5.19/0012-amd-gpio.patch new file mode 100644 index 000000000..3ef3b2c37 --- /dev/null +++ b/patches/5.19/0012-amd-gpio.patch @@ -0,0 +1,109 @@ +From b04835b46c5126b87c7098a0a1a0cdbdf6c28c74 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 907cc98b1938..0116d27b29ea 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include + + #include + #include +@@ -1234,6 +1235,17 @@ static void __init mp_config_acpi_legacy_irqs(void) + } + } + ++static const struct dmi_system_id surface_quirk[] __initconst = { ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") ++ }, ++ }, ++ {} ++}; ++ + /* + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1289,6 +1301,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); + ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } ++ + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.37.1 + +From 8e33cd037d4a68a5feabf796ea0b61e4ec018eeb Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index 0116d27b29ea..af102c6f8e5b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1237,12 +1237,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, ++ { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") ++ }, ++ }, + {} + }; + +-- +2.37.1 +